I am using an object that matches all the fields in my form. I then use the default binding to populate the object in my action, like this;
public ActionResult GetDivisionData(DivisionObj FormData)
My DivisionObj initializes all it's values to string.empty in the constructor.
The problem is that when the binder populates the model from the posted form data, any data that is not posted is set to null in the object, eventhough I initialized the object to contain empty strings.
Is there a way to change this so that unposted data will be an empty string.
This is default behavior of the DefaultModelBinder, more specifically the DataAnnotations framework. ConvertEmptyStringToNull is by default, set to true. You can create your own model binder and replace the default model binder.
public class EmptyStringModelBaseBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
bindingContext.ModelMetadata.ConvertEmptyStringToNull = false;
return base.BindModel(controllerContext, bindingContext);
}
}
Then in global...
ModelBinders.Binders.DefaultBinder = new EmptyStringModelBaseBinder();
Though I do wish they had a static way of setting this for the default modelbinder. Maybe in v3 :)
Alternatively, You can also use the [DisplayFormat(ConvertEmptyStringToNull=false)] attribute to set this on a per-property basis.
You can always use [Bind(Exclude="PropertyName1,PropertyName2,PropertyName3")] to exclude some properties from binding:
public ActionResult GetDivisionData([Bind(Exclude="PropertyName1,PropertyName2,PropertyName3")]DivisionObj FormData)
If you really have to have String.Empty in String properties, you can use this binder:
public class EmptyStringModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
if (propertyDescriptor.PropertyType == typeof(String))
propertyDescriptor.SetValue(bindingContext.Model,propertyDescriptor.GetValue(bindingContext.Model) ?? String.Empty);
}
}
You'll also have to run this in global.asax:
ModelBinders.Binders.DefaultBinder = new EmptyStringModelBinder();
I can only confirm that I see the same results as you. Your options are:
One way is to exclude properties as LukLed explained. But this will lead to code duplication and you will have to do it on every controller action every time DivisionObj (or any other Model class that you wish to decorate) appears as action parameter. Soo it's a bit cumbersome...
I am currently in the process of having multiple issues with various custom properties, some that need to be instantiated in contructors, some that need value only known at runtime and others that are also special in some way.
I have determined that for me it's best to go with Custom Model Binder and do most of this stuff there.
Related
Have the method below in my model:
public bool ShowShipping() { return Modalities.Scheduled.Has(Item.Modality); }
But previously it was a property like this:
public bool ShowShipping { get { return Modalities.Scheduled.Has(Item.Modality); } }
Upon accessing the page, the entire model is populated with data which includes the Item property. Item contains data that needs to be displayed on the view, but no data that needs to be posted back. So on post back (yes, the post action takes the model as a parameter) the Item property is left null.
This should not be a problem because there is only one line of code that accesses ShowShipping, which is on the view. So I am expecting that it will never be accessed except when Item is populated. However on post back I get an error which occurs before the first line of my post action is hit and it shows a null reference error in ShowShipping. So I have to assume the error is happening as it serializes form data into a new instance of the model... but why would it call this property in serialization when the only place in the entire solution that accesses it is one line in the view?
In System.Web.Mvc version 5.2.3.0, the DefaultModelBinder does perform validation, arguably violating separation of concerns, and there isn't a way to shut it off entirely via any setting or configuration. Other SO posts mention turning off the implicit required attribute for value types with the following line of code in your Global.asax.cs, Application_Start() method...
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
See: https://stackoverflow.com/a/2224651 (which references a forum with an answer directly from the asp.net team).
However, that is not enough. All model class getters are still executed because of the code inside the DefaultModelBinder.BindProperty(...) method. From the source code...
https://github.com/mono/aspnetwebstack/blob/master/src/System.Web.Mvc/DefaultModelBinder.cs
215 // call into the property's model binder
216 IModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType);
217 object originalPropertyValue = propertyDescriptor.GetValue(bindingContext.Model);
218 ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
219 propertyMetadata.Model = originalPropertyValue;
220 ModelBindingContext innerBindingContext = new ModelBindingContext()
221 {
222 ModelMetadata = propertyMetadata,
223 ModelName = fullPropertyKey,
224 ModelState = bindingContext.ModelState,
225 ValueProvider = bindingContext.ValueProvider
226 };
227 object newPropertyValue = GetPropertyValue(controllerContext, innerBindingContext, propertyDescriptor, propertyBinder);
Line 217 is the offender. It calls the getter prior to setting the value from the request (the ultimate purpose of this method), apparently so that it can pass the original value in the ModelBindingContext parameter to the GetPropertyValue(...) method on line 227. I could not find any reason for this.
I use calculated properties extensively in my model classes that certainly throw exceptions if the property expression relies on data that has not been previously set since that would indicate a bug elsewhere in the code. The DefaultModelBinder behavior spoils that design.
To solve the problem in my case, I wrote a custom model binder that overrides the BindProperty(...) method and removes the call to the getters. This code is just a copy of the original source, minus lines 217 and 219. I also removed lines 243 through 259 since I am not using model validation, and that code references a private method to which the derived class does not have access (another problematic design of the DefaultModelBinder.BindProperty(...) method). Here is the custom model binder.
public class NoGetterModelBinder : DefaultModelBinder {
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) {
string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
if (!bindingContext.ValueProvider.ContainsPrefix(fullPropertyKey)) return;
IModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType);
ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
ModelBindingContext innerBindingContext = new ModelBindingContext() {
ModelMetadata = propertyMetadata,
ModelName = fullPropertyKey,
ModelState = bindingContext.ModelState,
ValueProvider = bindingContext.ValueProvider,
};
object newPropertyValue = GetPropertyValue(controllerContext, innerBindingContext, propertyDescriptor, propertyBinder);
propertyMetadata.Model = newPropertyValue;
ModelState modelState = bindingContext.ModelState[fullPropertyKey];
if (modelState == null || modelState.Errors.Count == 0) {
if (OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, newPropertyValue)) {
SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
}
} else {
SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
}
}
}
You can place that class anywhere in your web project, I just put it in Global.asax.cs. Then, again in Global.asax.cs, in Application_Start(), add the following line of code to make it the default model binder for all classes...
ModelBinders.Binders.DefaultBinder = new NoGetterModelBinder();
This will prevent getters from being called on your model classes.
I'm developing something like wizard with steps (controllers) and uses DerivedModel1, DerivedModel2,etc which is inherits from BaseModel and extends them with extra properties.
Models - only data, without business logic. All logic performed by services in controllers action for example _step1Service.GetRelated(model.id).
Now I want to not just validate Model (for this case there is ValidationAttribute) but fix invalid data in BaseModel:
public class BaseModel
{
public DateTime StartDate {get;set;}
}
StartDate should be greater than today. User can select invalid date and instead of validation error application should fix this value (reset to default?).
In my first attempt I added service for validating/correcting StartDate and call in each Action:
public ActionResult Index(DerivedModel1 model)
{
_svc.fixModel(model);
if(!ModelState.IsValid)
{
return View();
}
... do stuff with valid data
}
But don't like that, because have to add this line to each controller and action.
Then I add this correction to StartDate setter. It's looks better, but this breaks popular MVC paradigm that all logic should be in controller (or maybe i misunderstood something?)
I was thinking about possible solutions of this problem: ActionFilterAttribute, custom ModelBinder? But not sure is this right way and whether it work.
What you think about that?
you must implement IModelBinder to achieve this.
first define your custom model binder like this:
public class MyCustomModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// Get the raw attempted value from the value provider
DateTime incomingDate = (DateTime) bindingContext.ValueProvider.GetValue("datefield").AttemptedValue;
//validate and correct date here ...
return new BaseModel{ DateMember = incomingDate };
}
}
then register your custom model binder such:
protected void Application_Start()
{
ModelBinders.Binders.Add(typeof (BaseModel), new MyCustomModelBinder());
}
and your controller :
public ActionResult YourAction([ModelBinder(typeof(MyCustomModelBinder )] BaseModel model)
{
return Content("Ok");
}
There's a difference between validation and business rules. Objects can (and often should) be responsible to make sure they are in a valid state themselves.
I have an action method that takes several optional parameters.
This ASP.NET MVC actionmethod looks simple enough but isn't working as I want....
[HttpPost]
public ActionResult UpdateOrder(OrderItem OrderItem, Address ShippingAddress)
{
if (ShippingAddress != null) {
// we have a shipping address
}
}
An Address object is always created for ShippingAddress because - well - thats the way model binders work. Even if ShippingAddress.Address1, ShippingAddress.City etc. fields are absent from the Form an object will still be created and passed to the action.
I want a way to make a model binder that returns null for the model if it is deemed to be empty.
A first attempt goes as follows
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
base.OnModelUpdated(controllerContext, bindingContext);
// get the address to validate
var address = (Address)bindingContext.Model;
// if the address is quintessentially null then return null for the model binder
if (address.Address1 == null && address.CountryCode == null && address.City == null)
{
bindingContext.Model = null;
}
}
Unfortunately this simple solution doesn't work and I get the following error:
InvalidOperationException -This property setter is obsolete, because its value is derived from ModelMetadata.Model now.
Is there a way I can make the overall 'Model' from a custom ModelBinder to return null?
Have you tried setting the default parameter to null? You may also need to set the type to nullable as well, but I'm not 100% sure if it's needed, but that's how I use it.
For example:
public ActionResult UpdateOrder(OrderItem OrderItem, Address? shippingAddress = null)
I should probably note that this requires .NET 4, but then, you didn't specify which version you're running on.
I have a BaseViewModel that my View Models all inherit from.
public class MagazineViewModel : BaseOutputViewMode
{
public string TitleOfPublication { get; set; }
}
In my controller I use a factory method to give the corret View Model back based on an input:
// e.g. viewModel contains an instance of MagazineViewModel
BaseOutputViewModel viewModel = BaseOutputViewModel.GetOutputViewModel(output);
When I use TryUpdateModel to try and bind to a FormCollection which I know contains a "TitleOfPublication" key, its never set in my view model:
if (!TryUpdateModel(viewModel, form))
I think this is something to do with the DefaultModelBinder using the BaseOutputViewModel to bind FormCollection keys to - it doesn't contain a "TitleOfPublication", the derived MagazineViewModel does.
I'm trying to roll my own model binder, to override the DefaultModelBinder's BindModel behavior. Its all wired in correctly and I can debug into it straight after the TryUpdateModel call:
public class TestModelBinder : DefaultModelBinder, IFilteredModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// Tried the following without success ....
// 1. Quick hardcoded test
// bindingContext.ModelType = typeof(MagazineViewModel);
// 2. Set ModelMetadata, hardcoded test again
// bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(MagazineViewModel));
// 3. Replace the entire context
// ModelBindingContext context2 = new ModelBindingContext();
// context2.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(MagazineViewModel));
// context2.ModelName = bindingContext.ModelName;
// context2.ModelState = bindingContext.ModelState;
// context2.ValueProvider = bindingContext.ValueProvider;
// bindingContext = context2;
}
}
But I'm not sure how to work with the bindingContext? What needs to be updated so that I can tell the DefaultModelBinder to bind using the derived View Model properties?
Or have I just totally mis-understood this!
I did try overriding CreateModel - much like the DerivedTypeModelBinder in MvcContrib, but I think because I'm giving the binder an instance of a model to work with, CreateModel is never called. Used Reflector on the Mvc DLL, theres a "BindComplexModel" that calls CreateModel only if the model is null:
if (model == null)
{
model = this.CreateModel(controllerContext, bindingContext, modelType);
}
Any pointers greatfully received!
Cheers
OK - finally got to the bottom of this!
In actual fact there was nothing wrong with my model binder, the problem ultimately led back to a couple of input tags that had no name/id:
<input id="" name="" type="text">
The crux was this test in DefaultModelBinder:
// Simple model = int, string, etc.; determined by calling TypeConverter.CanConvertFrom(typeof(string))
// or by seeing if a value in the request exactly matches the name of the model we're binding.
// Complex type = everything else.
if (!performedFallback) {
ValueProviderResult vpResult =
bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (vpResult != null) {
return BindSimpleModel(controllerContext, bindingContext, vpResult);
}
}
With no id/name, the form collection has a key of "" which means that the GetValue correctly returned the value for that field, continuing to bind as a simple model.
When an id/name are added, the form collection contains no key of "", (which is now the name of my model as we're using TryUpdateModel). This meant the DefaultModelBinder correctly treated my model as complexm successfully binding properties in my derived type!
Cheers
I'm creating a custom model binder in an Mvc application and I want to parse a string to an enumeration value and assign it to the model property. I have got it working overriding the BindProperty method, but I also noticed that there is a SetProperty method.
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
switch (propertyDescriptor.Name)
{
case "EnumProperty":
BindEnumProperty(controllerContext, bindingContext);
break;
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
private static void BindEnumProperty(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var formValue = controllerContext.HttpContext.Request.Form["formValue"];
if (String.IsNullOrEmpty(formValue))
{
throw new ArgumentException();
}
var model = (MyModel)bindingContext.Model;
model.EnumProperty = (EnumType)Enum.Parse(typeof(EnumType), formValue);
}
I’m not sure what the difference is between the two and whether I am doing this in the recommended way.
First of all, BindProperty is not a part of IModelBinder but, a protected method in DefaultModelBinder. You can access it only if you are sub-classing the DefaultModelBinder.
The following points should answer your question:
BindProperty uses the IModelBinder
interface it gets from the
PropertyType of the
propertyDescriptor argument. This
allows you to inject custom
properties into the property
metadata.
BindProperty properly
handles validation. It (also) calls the
SetProperty method only if the
new value is valid.
So if you want proper validation (using the annotation attributes) you must definitely call BindProperty. By calling SetProperty you bypass all the built-in validation mechanisms.
You should check out the source code of DefaultModelBinder the see what each method does, since the intellisense provides only limited information.
I think SetProperty takes actual value to set as the last parameter.