How to filter form data with custom model binder
I have a bunch of forms where currency values are entered and I want them to be able to enter "$1,234.56". By default, the model binders won't parse that into a decimal.
What I am thinking of doing is creating a custom model binder the inherits DefaultModelBinder, override the BindProperty method, check if the property descriptor type is decimal and if it is, just strip out the $ and , from the values.
Is this the best approach?
Code:
public class CustomModelBinder : DefaultModelBinder
{
protected override void BindProperty( ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor )
{
if( propertyDescriptor.PropertyType == typeof( decimal ) || propertyDescriptor.PropertyType == typeof( decimal? ) )
{
var newValue = Regex.Replace( bindingContext.ValueProvider[propertyDescriptor.Name].AttemptedValue, @"[$,]", "", RegexOptions.Compiled );
bindingContext.ValueProvider[propertyDescriptor.Name] = new ValueProviderResult( newValue, newValue, bindingContext.ValueProvider[propertyDescriptor.Name].Culture );
}
base.BindProperty( controllerContext, bindingContext, propertyDescriptor );
}
}
Update
This is what I ended up doing:
public class CustomModelBinder : DataAnnotationsModelBinder
{
protected override void BindProperty( ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescr开发者_StackOverflow中文版iptor )
{
if( propertyDescriptor.PropertyType == typeof( decimal ) || propertyDescriptor.PropertyType == typeof( decimal? ) )
{
decimal newValue;
decimal.TryParse( bindingContext.ValueProvider[propertyDescriptor.Name].AttemptedValue, NumberStyles.Currency, null, out newValue );
bindingContext.ValueProvider[propertyDescriptor.Name] = new ValueProviderResult( newValue, newValue.ToString(), bindingContext.ValueProvider[propertyDescriptor.Name].Culture );
}
base.BindProperty( controllerContext, bindingContext, propertyDescriptor );
}
}
It's reasonable to do it in the binder. However, I think that Decimal.Parse
with the currency format provider or number style (see the docs) would be more reliable than stripping the "$" and calling base
. For starters, it would handle non-US currency, which might be an issue for you some day.
In MVC3 you can just register a custom modelbinder that implements the IModelBinder interface specifically for decimal types and then tell it to handle currency or decimal by using the ModelMetaData.DataTypeName property on the bindingContext.
I've modified the sample provided by Phil Haack in his article to demonstrate how it could be done:
public class DecimalModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var modelState = new ModelState { Value = valueResult };
decimal actualValue = 0;
try
{
if(bindingContext.ModelMetadata.DataTypeName == DataType.Currency.ToString())
decimal.TryParse(valueResult.AttemptedValue, NumberStyles.Currency, null, out actualValue);
else
actualValue = Convert.ToDecimal(valueResult.AttemptedValue,CultureInfo.CurrentCulture);
}
catch (FormatException e)
{
modelState.Errors.Add(e);
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return actualValue;
}
}
You could create your own ValidationAttribute which checks if value has correct format. Then you could look if property is decorated with this attribute and bind it in proper way. Attribute doesn't need to be ValidationAttibute, but it seems like good idea.
精彩评论