I have a model binder for a custom type Money
. The binder works fine, but the built in binding/validation is kicking in and marking data 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 a user types a value like "45,000" into a textbox my binder correctly picks up the value, parses it and sets it into the model.
The problem I have, is that the default validation then kicks in a states The value '45,000' is not valid for Amount
, which as it's a decimal type makes sense, but I've already bound the data. How can I prevent the default binders binding data which I've already handled?
I'm not sure if this make any difference, but I'm using Html.EditorFor
with and 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>
You can just mark the Amount
property as read only:
[ReadOnly(true)]
public decimal? Amount { get; set; }
Provided that your ModelBinder code resides in the same project, you could change the property setter access modifier to internal. This way the DefaultModelBinder couldn't see the setter.
public class MyViewModel{
public Money Stash { get; internal set; }
}
Another way could be simply the usage of a field
public class MyViewModel{
public Money Stash;
}
The reasoning behind this is that DefaultModelBinder only binds read-write properties. Both suggestions above will prevent those conditions from being satisfied.
There are several ways of doing this from my experience (allowing the default binder to bind, but clearing the errors on the property, for instance), but the way of doing this which makes your intent clear to anyone maintaining your code would be to override the undesired behavior, i.e.:
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name != "Amount")
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
It might also make sense to actually do your custom binding in the else block here, but that's for you to decide.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With