How can I prevent default bindings for complex types?

I have a model binder for the user type Money . The binding works fine, but the built-in binding / validation does not work and is marked as invalid.

My binder looks like this:

 public class MoneyModelBinder : DefaultModelBinder { protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) { var money = (Money)bindingContext.Model; var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Amount").AttemptedValue; var currencyCode = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Iso3LetterCode").AttemptedValue; Money parsedValue; if (String.IsNullOrEmpty(value)) { money.Amount = null; return; } var currency = Currency.FromIso3LetterCode(currencyCode); if(!Money.TryParse(value, currency, out parsedValue)) { bindingContext.ModelState.AddModelError("Amount", string.Format("Unable to parse {0} as money", value)); } else { money.Amount = parsedValue.Amount; money.Currency = parsedValue.Currency; } } } 

When the user enters a value of type "45,000" in the text box, my binder selects the value correctly, analyzes it and sets it into the model.

The problem I have is that the default check is then run in The value '45,000' is not valid for Amount states, which, like the decimal type, makes sense, but I already bound the data. How can I prevent the default data bindings that I have already processed?

I'm not sure if this matters, but I use Html.EditorFor with and an editor that looks like this:

 @model Money <div class="input-prepend"> <span class="add-on">@Model.Currency.Symbol</span> @Html.TextBoxFor(m => m.Amount, new{ placeholder=string.Format("{0}", Model.Currency), @class="input-mini", Value=String.Format("{0:n0}", Model.Amount) }) @Html.HiddenFor(x => x.Iso3LetterCode) </div> 
+6
source share
3 answers

You can simply mark the Amount property as read only:

 [ReadOnly(true)] public decimal? Amount { get; set; } 
+5
source

Provided that your ModelBinder code is in the same project, you can change the access modifier for setting properties for the internal one. Thus, the DefaultModelBinder could not be seen by the installer.

 public class MyViewModel{ public Money Stash { get; internal set; } } 

Another way could be just using a field

 public class MyViewModel{ public Money Stash; } 

The reason for this is that DefaultModelBinder only binds read and write properties. Both of the above proposals will not allow these conditions to be satisfied.

+2
source

There are several ways to do this from my experience (for example, binding a default binder, but, for example, clearing errors in a property), but a way to do this, which makes your intent clear to anyone who supports your code, to override unwanted behavior, then there is:

 protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor) { if (propertyDescriptor.Name != "Amount") { base.BindProperty(controllerContext, bindingContext, propertyDescriptor); } } 

It may also make sense to actually do your own binding in the else block here, but it's up to you.

+1
source

All Articles