MVC invoke model binder directly on a single object
Asked Answered
P

2

8

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

Prostatitis answered 29/9, 2011 at 15:9 Comment(0)
D
9

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

Detent answered 1/10, 2013 at 7:22 Comment(1)
FWIW, here's why I'm glad to find this magic. I'm implementing a "wizard" framework (i.e., a way to split up a single long form into multiple smaller forms with "next page" and "previous page" buttons). I want each individual page to POST back to a common controller action, but each page has its own strongly-typed view model, complete with validation attributes. Since I have one common controller action, I don't know which view model to bind to until I've looked to see which page is posting back to me.Lyublin
A
2

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;

}

Ascendant answered 29/9, 2011 at 16:15 Comment(2)
What do you get out of it? Smells like bad design. Seems like a bad design for binding to multiple model types, but it has a good performance usage. You could inject everything up front which gets initialised, then do a guard clause and return early, not using the injected objects that have been set up. Or, after the guard clauses, once you're sure you want to use an object, have UpdateModel resolve the dependency. It's messy, but if you must make the design trade for performance, it's a possibility.Infusible
If you're implementing a webhook, it will give you multiple different models but you can only specify one URL, you can only determine what type of model it is from one of the fields such as alert_name. So if you can not do model binding manually, you need to prase the model by yourself.Dimetric

© 2022 - 2024 — McMap. All rights reserved.