All Articles

ASP.NET MVC - Default Model Binder with HTML Validation

This post demonstrates how we can handle gracefully the HTTP request validation which exists in ASP.NET, when using ASP.NET MVC. Request validation is a feature in ASP.NET that examines an HTTP request and determines whether it contains potentially dangerous content. For example, if a user tries to input some malicious code in a field using the script element, the ASP.NET throws a "potentially dangerous value was detected" error and stops page processing. Without this validation, the site is vulnerable to this exploit typically referred as a cross-site scripting (XSS) attack.

Consider this form

 

image

Here is the error when the user tries to submit a field with HTML elements, pressing the Save button

 

image

We can disable this validation at a field, action, controller, or application level. But we have to think about the consequences of disabling it and being vulnerable to an XSS attack. Instead of disabling it, I want to continue validating the potential dangerous requests, but rather than throw the error and stop processing, I want to add a validation message for the field that is violating the validation.

 

image

In ASP.NET MVC, we can do this using a custom default model binder. The model binding is the mechanism that automatically populates the controller action parameters, taking care of the details of property mappings and type conversions. The default model binder in ASP.NET MVC is the class DefaultModelBinder . The good news is that we can have a custom default model binder. In our case, we want to inherit the DefaultModelBinder, overriding the main method BindModel, and handle the exception HttpRequestValidationException which is thrown when a potential dangerous request is detected

public class DefaultModelBinderWithHtmlValidation : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        try
        {
            return base.BindModel(controllerContext, bindingContext);
        }
        catch (HttpRequestValidationException)
        {
            Trace.TraceWarning("Ilegal characters were found in field {0}", bindingContext.ModelMetadata.DisplayName ?? bindingContext.ModelName);
            bindingContext.ModelState.AddModelError(bindingContext.ModelName, string.Format("Ilegal characters were found in field {0}", bindingContext.ModelMetadata.DisplayName ?? bindingContext.ModelName));
        }

        //Cast the value provider to an IUnvalidatedValueProvider, which allows to skip validation
        IUnvalidatedValueProvider provider = bindingContext.ValueProvider as IUnvalidatedValueProvider;
        if (provider == null) return null;

        //Get the attempted value, skiping the validation
        var result = provider.GetValue(bindingContext.ModelName, skipValidation: true);
        Debug.Assert(result != null, "result is null");

        return result.AttemptedValue;
    }
}

So, what we do is quite simple: we catch the HttpRequestValidationException exception and add a validation error message to the ModelState specifying the field name that is violating the request. Next we read the attempted value, skipping the validation and return. In our action controller we can't forget to check if the ModelState is valid.

[HttpPost]
public ActionResult Create(MyModel model, string fieldArgument)
{

    if (ModelState.IsValid)
    {
        //The model is OK. We can do whatever we want to do with the model
        model.MyMessage = "Model Ok Updated @ " + DateTime.Now;
    }

    ViewBag.FieldArgument = fieldArgument;
    return View(model);
}

Next we have to register our binder as the default binder. We do this in the application start

//Register our binder as the default model binder
ModelBinders.Binders.DefaultBinder = new DefaultModelBinderWithHtmlValidation();

You can check the full source code here on my github.

Published Apr 15, 2013

Cloud Solutions and Software Engineer. Married and father of two sons. Obsessed to be a continuous learner.