ASP.NET MVC3 ValueProvider drops string input to a double property

704 views Asked by At

I'm attempting to validate the input of a text box which corresponds to a property of type double in my model. If the user inputs "foo" I want to know about it so I can display an error. However, the ValueProvider is dropping the value silently (no errors are added to the ModelState).

In a normal submission, I fill in "2" for the text box corresponding to myDouble and submit the form. Inspecting controllerContext.HttpContext.Request.Form shows that myDouble=2, among other correct inputs. bindingContext.ValueProvider.GetValue("myDouble") == 2, as expected. The bindingContext.ModelState.Count == 6 and bindingContext.ModelState["myDouble"].Errors.Count == 0. Everything is good and the model binds as expected.

Then I fill in "foo" for the text box corresponding to myDouble and submitted the form. Inspecting controllerContext.HttpContext.Request.Form shows that myDouble=foo, which is what I expected. However, bindingContext.ValueProvider.GetValue("myDouble") == null and bindingContext.ModelState.Count == 5 (The exact number isn't important, but it's one less than before). Looking at the ValueProvider, is as if myDouble was never submitted and the model binding occurs as if it wasn't. This makes it difficult to differentiate between a bad input and no input.

Is this the expected behavior of ValueProvider? Is there a way to get ValueProvider to report when conversion fails without implementing a custom ValueProvider? Thanks!

2

There are 2 answers

5
Erik Funkenbusch On BEST ANSWER

Part of the problem here is that your model has a type of double.

The problem is that double cannot be null, and as such will default to a value of 0, thus on submit.. if the ValueProvider returns null, the value of the field will still be 0 and validation will pass.

You should make the double nullable, by using double? and then add a Required attribute to the property. If the type is not required, then you can add a regular expression validator.

0
CrazyWebDeveloper On

You can implement custom model binding logic using by implementing IModelBinder. This will put the data validation logic at the model binding level - thus being usable for any type of ValueProvider. In your situation, the model binder would determine that when myDouble = "foo" is not a double and add an exception to the ModelState errors showing the invalid value.

public class CustomDoubleBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
        if (bindingContext == null)
        {
            throw new ArgumentNullException("bindingContext");
        }
        decimal tempDouble = 0m;
        if (bindingContext.ValueProvider.GetValue(bindingContext.ModelName) != null)
        {
            if (double.TryParse(bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue, out tempDecimal))
            {
                bindingContext.ModelState[bindingContext.ModelName].Errors.Add("Error parsing double value: " + bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue);
            }
        }

        return tempDouble;
    }
}

Having created this custom model binder, you will then need to register it in the Global.asax:

protected void Application_Start()
{
   ModelBinders.Binders[typeof(double)] = new CustomDoubleBinder();
}