Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to filter form data with custom model binder

Tags:

asp.net-mvc

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 );
    }
}
like image 371
Josh Close Avatar asked Dec 16 '09 16:12

Josh Close


People also ask

What interface and method needs to be implemented for a custom model binder?

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 does a model binder work?

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 the fundamentals that a developer should consider when working to wire up model binding correctly?

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.

What is model binder in asp net core?

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.


2 Answers

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.

like image 95
Craig Stuntz Avatar answered Sep 27 '22 17:09

Craig Stuntz


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;
        }
    }
like image 28
lomaxx Avatar answered Sep 27 '22 17:09

lomaxx