Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC invoke model binder directly on a single object

Is there a way that I can invoke the model binder for a single object?

I don't want/need a custom model binder - I just want to do something like this:

MyViewModel1 vModel1 = new MyViewModel1();
InvokeModelBinder(vModel1);

MyViewModel2 vModel2= new MyViewModel2();
InvokeModelBinder(vModel2);

And when I'm done, the properties of both vModel1 and vModel2 have been bound to what's in the incoming request. Because of the way that our controller/action is being written, I don't necessarily want to list vModel1 and vModel2 in the action method's input list (since there will end up being a potentially long list of view models to optionally bind against).

like image 662
Kras Avatar asked Sep 29 '11 15:09

Kras


2 Answers

Use Controller.UpdateModel:

MyViewModel1 vModel1 = new MyViewModel1();
UpdateModel(vModel1);

Update

Note if ModelState in controller has validation errors (related to model passed in action), UpdateModel (with any model) throws excetion, despite UpdateModel success and vModel1 is updated. Therefore errors in ModelState should be removed, or put UpdateModel in try/catch and just ignore excetion

like image 189
Sel Avatar answered Nov 05 '22 02:11

Sel


This is wrong on many levels IMHO:

  1. This is not how ASP.NET MVC is designed to work.
  2. Your actions do not define a clear contract of what data they expect.
  3. What do you get out of it? Smells like bad design.

Model binding is driven by reflection. Before an action is invoked it will reflect the method parameters list and for each object and its properties it will invoke a model binder to find a value for each property from the various value providers (form POST values provider, url parameters, etc). During model binding the ModelState validation is done as well.

So by not using the default ASP.NET MVC to do this you are losing all that.

Even if you were to manually get hold of a model binder like that:

IModelBinder modelBinder = ModelBinders.Binders.GetBinder(typeof(MyObject));
MyObject myObject = (MyObject ) modelBinder.BindModel(this.ControllerContext, ** ModelBindingContext HERE**);

You can see that you need to initalize a ModelBindingContext, something that ASP.NET MVC will do internally based on the current property it is reflecting. Here is the snipped from the ASP.NET MVC source code:

protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) {
// collect all of the necessary binding properties
Type parameterType = parameterDescriptor.ParameterType;
IModelBinder binder = GetModelBinder(parameterDescriptor);
IDictionary<string, ValueProviderResult> valueProvider = controllerContext.Controller.ValueProvider;
string parameterName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;
Predicate<string> propertyFilter = GetPropertyFilter(parameterDescriptor);

// finally, call into the binder
ModelBindingContext bindingContext = new ModelBindingContext() {
    FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
    ModelName = parameterName,
    ModelState = controllerContext.Controller.ViewData.ModelState,
    ModelType = parameterType,
    PropertyFilter = propertyFilter,
    ValueProvider = valueProvider
};
object result = binder.BindModel(controllerContext, bindingContext);
return result;

}

like image 20
Ivan Zlatev Avatar answered Nov 05 '22 03:11

Ivan Zlatev