Question

I'm having difficulty getting a Custom Model Binder working when the request contains a flat collection of form variables.

I've got a ViewModel class that contains a nested ViewModel, e.g.

public class ViewModel1
{
    public long Id { get; set; }
    public ViewModel2 NestedType { get; set; }
}

public class ViewModel2
{
    public string Name { get; set; }
    public string Surname { get; set; }
}

My problem is, if I use Fiddler to submit a request with a form variable of NestedType.Name, then my custom model binder executes fine, however, the request I'm having to deal with is out of my control, in this case, it's posted via an ajax request from a JQGrid instance and is 'flat' i.e.

Id=5
Name=firstname
Surname=surname

not

Id=5
NestedType.Name=firstname
NestedType.Surname=surname

Is there any way I can get this to work?

Thanks in advance.

EDIT:

To clarify a bit, my controller action looks like this:

public ActionResult GridEditRow(ViewModel1 viewModel)

As mentioned below, I'd rather the custom binder fired prior to executing the controller code instead of calling TryUpdateModel().

Was it helpful?

Solution

Try this:

var viewModel = new ViewModel1();
UpdateModel(viewModel.NestedType);

OTHER TIPS

Not the best solution but you can add another parameter for that type:

public ActionResult GridEditRow(ViewModel1 viewModel, ViewModel2 viewModel2)

This should bind Name property even if it doesn't have the correct prefix.

May be you can change you ActionResult in following way:

public ActionResult GridEditRow(int id, string firstname, string surname)
{
  VewModel2 model2 = new VewModel2
{
Name  = firstname,
Surname = surname
}

  VewModel1 model1 = new VewModel1
{
Id = id,
NestedType  = model2 
}

return View(model1 )  

}

I have faced with the similar problem. And as you have supposed one of the possible decisions is a custom model binder. Although the question is old I shall place my answer here (because why not?). So the code of the custom model binder is following.

public class CustomModelBinder : DefaultModelBinder {
        protected override void BindProperty(
            ControllerContext controllerContext, 
            ModelBindingContext bindingContext, 
            System.ComponentModel.PropertyDescriptor propertyDescriptor) {
                if (bindingContext.ModelType == typeof(ViewModel1)) {
                    var name = propertyDescriptor.Name;

                    // Try to implement default binding.
                    // Also one could try base.BindProperty(...).
                    var value = bindingContext.ValueProvider.GetValue(name);
                    if (value != null)
                        propertyDescriptor.SetValue(bindingContext.Model, value.ConvertTo(propertyDescriptor.PropertyType));

                    // If the default binding is not working then search for nested values.
                    else {
                        value = bindingContext.ValueProvider.GetValue("NestedType." + name);
                        if(value != null)
                            propertyDescriptor.SetValue(bindingContext.Model, value.ConvertTo(propertyDescriptor.PropertyType));
                    }
                } else
                    base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
        }
    }

Registration of the binder in Global.asax:

ModelBinders.Binders.Add(typeof(ViewModel1), new CustomModelBinder());

The other possibility of registering is using attributes in controller's actions.

I suppose that this decision is much more universal than others but also is slower (because of the reflection).

The ref which has helped me: http://dotnetslackers.com/articles/aspnet/Understanding-ASP-NET-MVC-Model-Binding.aspx .

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top