DefaultModelBinder Problem with nested levels + other binders

I have what I think is a somewhat normal situation where I need to bind form records to the "order" model. This model has several levels of information:

Order.Billing.FirstName Order.Billing.Address.City Order.Billing.Address.Country 

Using DefaultModelBinder, if I POST the form for an action that takes this order model as a parameter, the following JustWork (TM) fields:

 <%=Html.TextBox("Billing.FirstName")%> <%=Html.TextBox("Billing.Address.City")%> 

In this field there is no:

 <%=Html.TextBox("Billing.Address.Country")%> 

I have wrinkles with the country. In our case, Address.Country returns an instance of the Country class (ISO2 / 3 / name / code logic). This is not a string. Not surprisingly, it does not work by default.

My first thought was to create a CountryModelBinder (inherit DefaultModelBinder) and ModelBinders.Binders. Add it to the country type. When I do this, CountryModelBinder will never be called in the scenerio above.

My second thought was to create an AddressModelBinder (inherit DefaultModelBinder) and bind it to our address type. Although the call is called, the call to SetProperty for "Country" has an empty value, although the field "Billing.Address.Country" is posted on the form.

After some intervention, it seems that the model binding behavior calls CreateModel calls when the model is the top-level class that the action requires and all other binders have their own BindPropery / SetProperty called child properties.

In other words, if I create model bindings for Order, OrderAddress (Billing), Address, and Country. For an action that takes an order, only OrderModelBinder.CreateModel is called. ORDERAddress and Address.BindProperty / SetProperty are called for some things, and sometimes the argument of the value of SetProperty is empty when it is explicitly placed in a name that matches other field property mappings.

Just add code to OrderModelBinder to pull Billing.Address.Country from Request.Form. But I have several models that use Address, and they all look broken.

What am I missing here? Is there a way to get CountryModelBinder to actually get the call in this case? I would think that CountryModelBinder should be called when Billing.Address.Country is mapped to the Country property of the binding address.

+7
asp.net-mvc nested defaultmodelbinder
source share
1 answer

I tried doing what you did here, it looks like on MVC3 it really works if I provide a model binding for this type.

This is just a proof of concept, showing that it WORKS, and it cannot be considered as even close to the code of the level of production:

Models:

 public class SimpleModel { public string Value { get; set; } public int Other { get; set; } } public class ComplexModel { public SimpleModel Complexity {get;set;} public string StrVal { get; set; } } 

some binder:

 public class MBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { if ( bindingContext.ModelType == typeof(SimpleModel)) { var simpleModel= new SimpleModel(); simpleModel.Other = 1; simpleModel.Value = controllerContext.HttpContext.Request.Form["Complexity"]; return cm; } return null; } } 

in global asax:

 ModelBinders.Binders.Add(typeof (SimpleModel), new MBinder()); 

code in view:

  @model ComplexModel @using ( Html.BeginForm() ) { <fieldset> @Html.LabelFor(x => x.Complexity) @Html.TextBoxFor(x => x.Complexity) </fieldset> <fieldset> @Html.LabelFor(x => x.StrVal) <br /> @Html.EditorFor(x => x.StrVal) </fieldset> <input type="submit" /> } 

Controller:

 public ActionResult Index() { return View(); } [HttpPost] public ActionResult Index(ComplexModel model) { return RedirectToAction("Index"); } 

BTW in MVC 3 the best option would be to use the IModelBinderProvider interface, but I just wanted to show something that worked.

0
source share

All Articles