2

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().

Apogee
  • 276
  • 1
  • 11

4 Answers4

1

Try this:

var viewModel = new ViewModel1();
UpdateModel(viewModel.NestedType);
karaxuna
  • 26,752
  • 13
  • 82
  • 117
  • Thanks for that, but I was hoping I could get it to work during the model binding prior to my controller action code. I've edited my question a bit. – Apogee Aug 12 '13 at 14:07
  • Went with this one in the end, not perfect but the cleanest solution I could find. Cheers. – Apogee Aug 23 '13 at 10:09
1

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.

Ufuk Hacıoğulları
  • 37,978
  • 12
  • 114
  • 156
  • Thanks for the suggestion. I could do this, but I'd have to subsequently assign viewmodel.NestedType = viewModel2 in the action, which is a bit clumsy. I was hoping a custom model binder could wrap the functionality in one place as this isn't the only place in the application that this nested viewmodel appears. Thanks for the help though :) – Apogee Aug 12 '13 at 18:31
0

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 )  

}
Andrey Gubal
  • 3,481
  • 2
  • 18
  • 21
  • Yeah I could do that, the problem being that those viewmodels were only examples, the real ones have a lot more properties, so the method signature would be a tad unwieldy. Plus the nested type is nested within other viewmodel types elsewhere in the application. Thanks for the reply though. – Apogee Aug 12 '13 at 18:28
0

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 .

Hoborg
  • 891
  • 12
  • 21