Doing custom binding for a specific data-type in a ModelBinder - asp.net-mvc

I'm in the process of creating my own custom ModelBinder that inherits from DefaultModelBinder, and manually binds XElement-typed properties.
Now it seems that I have to override the 'BindProperty' method, like so:
protected override void BindProperty(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.PropertyType == typeof(XElement))
{
// code here to take the POST-ed form value and put it in the property as an XElement instance
}
else
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
What code should I be using to:
A) get the value of the property from the posted Form values?
B) inject this value into the property?
I tried running Reflector on the DefaultModelBinder class to see how it does it, but the code was very confusing.
I need someone who's done this before to walk me through it.

The bindingContext parameter contains a ValueProvider property that is already populated with the values from the request. The idea is that you pull the values from that.
It is simply a dictionary of values, so you can index into it using the name of the field you would like to bind.
The easiest way to understand what's goint on is to apply your custom ModelBinder and then set a breakpoint in your code and inspect what data you got while in the debugger.

Related

How to override ASP.Net MVC's default model binder so that an empty value bound to a non-nullable value type won't trigger a model validation error

Something I've found quite frustrating in ASP.Net MVC is that the default model binder implicitly applies the Required annotation when binding an empty (string or null) value to a non-nullable value type instead of simply leaving the target with its default value, or at least providing an option to allow that to be the default behaviour.
Given a scenario where it's inconvenient to change the target property type on the model to a nullable value, what's the shortest amount of code I can employ to allow the default model binder to simply skip its attempt to bind an empty value to a non-nullable value type? I'm assuming I'll need to subclass DefaultModelBinder, but I'm not sure what I need to override to achieve the desired behaviour.
example:
<input type="text" name="MyField"/>
Submit without a value:
public ActionResult MyAction(MyModel model)
{
// do stuff
}
public class MyModel
{
public int MyField { get; set; }
}
The property MyField should be allowed to retain its default value of 0 seeing as an empty value was posted from the form.
Assume that I can't simply change the property type a Nullable<int>.
How about something like this? (Disclaimer: Not tested to any degree of confidence)
public class NonRequiredModelBinder : DefaultModelBinder
{
protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
{
var result = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
if (result == null && propertyDescriptor.PropertyType.IsValueType)
return Activator.CreateInstance(propertyDescriptor.PropertyType);
return result;
}
}
The idea -- in theory -- is to determine what value the DefaultModelBinder assigned to the property, check if it was a null value and then assign it to the default value of the ValueType that is being bound.
This should prevent the binder from adding ModelState errors, and still would not affect the validation of other attributes such as [Range]
I would recommend taking this a step further and creating your own attribute (i.e., NonRequiredAttribute). Then in your custom model binder you could check to see if the property has the new NonRequired attribute, and execute this custom code only in the case that it does.

Is it possible to bind into Key/Value dictionary inputs in format InputName/InputValue

Is it possible to bind into Key/Value dictionary inputs in format InputName/InputValue?
If you want the values in the form come and sit as List<KeyValuePair<string,string>> in action methods you have to for custom model binder.
public class CustomModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var form = controllerContext.HttpContext.Request.Form;
return form.Cast<string>().Select(s => new KeyValuePair<string,string>(s, form[s])).ToList();
}
}
[HttpPost]
public ActionResult Save(List<KeyValuePair<string,string>> form)
{
...
}
If you are fine with other options you can go for FormCollection as #Tommy suggested. By the way FormCollection is a built-in dictionary class that contains all the values submitted by the form.
You can also create a simple class having properties with names as the form field names and the default model binder automatically instantiate and set the values for the class and provide to the action. This is the approach people usually follow.
MVC already does this if you pass the right parameter to your action method. Note - it is highly recommended to use strongly typed views and models in your action methods.
With that said, if your method signature uses the following:
public ActionResult MyMethod(FormsCollection MyFormValues)
Then in your code, you can use
var myFormValue = MyFormValues("InputName");
Where InputName is the key used from the FormsCollection which will give you the value that was set from the HTML.

Overriding BindModel and BindProperty in CustomModelBinder, but BindProperty never hit

I have a custom model binder with
public override object BindModel(controllerContext, bindingContext)
that checks session for an object and creates it if it isn't there, then returns the object.
In that same model binder I have:
protected override void BindProperty(controllerContext, bindingContext, propertyDescriptor) , and for some reason this is NEVER hit on a postback.
BindModel() is always hit, but breakpoint on first line of BindProperty is never reached.
If i comment out the BindModel() override, the BindProperty() WILL be hit.
Any idea why?
are you calling base.BindModel(args) in your overridden BindModel()? If not, the DefaultModelBinder won't try to bind the properties and won't call BindProperty.

How to update custom ModelBinder to work with altered ModelBindingContext.ValueProvider interface in ASP.NET MVC RC2

I have a custom model binder that takes a comma separated list and cleans out any empty values, then passes it along to the default model binder. This worked in ASP.NET MVC Preview 2, but when I upgraded to RC2, the below won't compile because the interface of ValueProvider only has a GetValue() method, no [] accessor. Is what I'm doing below possible through some other mechanism in the binding context? I'd rather not have to create a full blown model binder for such a simple situation. The main goal is when the values are bound to a List<SomeEnum>, any empty values are skipped.
public class EnumListModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var result = bindingContext.ValueProvider[bindingContext.ModelName];
string[] rawValues = (string[])result.RawValue;
var newValues = new List<string>();
foreach (string value in rawValues)
{
if (!String.IsNullOrEmpty(value))
{
newValues.Add(value);
}
}
string newValuesAttempted = String.Join(",", newValues.ToArray());
// overwrite the ValueProviderResult with the cleaned up csv list
// this is the part I'm not sure how to implement using the interface
bindingContext.ValueProvider[bindingContext.ModelName] =
new ValueProviderResult(newValues.ToArray(), newValuesAttempted, result.Culture);
return System.Web.Mvc.ModelBinders.Binders.DefaultBinder.BindModel(controllerContext, bindingContext);
}
}
What, exactly, is wrong with using GetValue() instead of [] here? It does the same thing. But ValueProvider is an interface now, and interfaces can't have indexers. Hence, GetValue().
Change your code to:
var result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
I'm a little surprised that the cast on the next line ever worked, though. Seems highly dependent on what the user actually submits and what the particular property type is. That's unrelated to your question, though.
There wasn't much of a solution here now that the ValueProvider collection is readonly. Instead I ended up using a custom model binder
Is there a way to have the DefaultModelBinder ignore empty items when binding to a List<Enum>

Binding ViewModels With Other ViewModelBinders

I have a viewmodel (lets call it HouseVM) but it contains another viewmodel inside of it (KitchenVM). I've already created a custom model binder for KitchenVM. Now I'm creating the HouseVM modelbinder. How can I access the model binding I've already done for KitchenVM within the HouseVM model binder?
NOTE: I have seen this post
Option # 1
You could have your model binder for the HouseVM inherit from your custom binder for the KitchenVM. This would allow binding of Kitchen VM (or related) properties) to still be bound by that binder. Something like:
public class HouseViewModelBinder : KitchenViewModelBinder
{
protected override void BindProperty( ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor )
{
if (propertyDescriptor.PropertyType == typeof(KitchenVM))
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
// bind the other properties here
}
}
Option # 2
This post by Jimmy Bogard may be another good way to implement your various customized model binders, allowing each type to bind to its appropriate model.

Resources