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 propertyDescriptor )
{
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 );
}
}
For creating a custom model binder class, we have to inherit from IModelBinder interface. This interface has an async method named "BindModelAsync" and it has a parameter of type ModelBindingContext. The ModelBindingContext class provides the context that model on which the binder acts.
How Model Binding Works. Model binding is a simplistic way to correlate C# code with an HTTP request. The model binding applies to transforming the HTTP request data in the query's form string and form collection of the action method parameters. We can consider these parameters to be primitive type or complex type.
What are fundamentals that a developer should consider when working to wire up model binding correctly? If sending an HTTP request that contains a form, the form should have the corresponding properties in html tags to ensure the correct attribute matches the property in the model.
Model binding allows controller actions to work directly with model types (passed in as method arguments), rather than HTTP requests. Mapping between incoming request data and application models is handled by model binders.
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;
}
}
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