Validating ASP.NET MVC 2 Route Values with DefaultValueAttribute

In ASP.NET MVC, the default signature for a Details action includes an Int32 method argument. The system works fine when the expected data is entered, and the happy path is followed, but put in an invalid value or no value at all, and an exception explodes all over the user.

The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult Details(Int32)' in 'MvcSampleApplication.Controllers.WidgetController'.

The following solution applies to the ASP.NET MVC 2 framework. If you are looking for a solution in the first version of the ASP.NET MVC framework, try last November's post on Validating ASP.NET MVC Route Values with ActionFilterAttribute.

By default, the third section of an ASP.NET MVC Route is the Id, passed as a method argument to a controller action. This value is an optional URL Parameter (UrlParameter.Optional), as defined in the default routing tables, but the value can be parsed to other types such as an integer for a default Details action. The problem stems from when invalid values or missing values are passed to these integer-expecting actions; MVC handles the inability to parse the value into an integer, but then throws an exception trying to pass a null value to a Controller Action expecting a value type for a method argument.

A common solution is to convert the method argument to a nullable integer, which will automatically cause the argument to be null when the route value specified in the URL is an empty or non-integer value. The solution works fine, but seems a little lame to me; I want to avoid having to check HasValue within every action. I have to check for invalid identity values anyway (a user with an id of –1 isn’t going to exist in my system), so I would much rather default these invalid integers to one of these known, invalid values.

Under ASP.NET MVC 2, the solution lies with a DefaultValueAttribute. Prior to executing a Controller Action, the DefaultValueAttribute can validate that the specified route value meets an expected type, and correct the value to a default value if this validation fails; this allows me to keep that method argument as a value-type integer and avoid Nullable<int>.HasValue.

The attribute is a member of the System.ComponentModel namespace, and accepts any one of many different base values as the default to assign to the route value should the parsing fail. Unlike the ActionFilterAttribute used to solve this problem in the first version of the ASP.NET MVC framework, there are no route redirects, which also means we do not modify the browser URL. (Using the ActionFilterAttribute, the browser location is redirected from ~/Widgets/Details/ or ~/Widgets/Details/Foo to ~/Widgets/Details/0, but with the DefaultValueAttribute, no such redirect occurs.)

Usage

1
2
3
4
5
6
7
public class WidgetsController : Controller
{
  public ActionResult Details([DefaultValue(0) int id)
  {
    return View();
  }
}

There is very little code to make this all happen. With one attribute added to the method argument, MVC validates that my identity is an integer or otherwise provides a replacement default value. I can depend on my user input without having to laden my code with unnecessary value checks and without risk of unhandled exceptions. However, the DefaultValueAttribute is just for providing a default value for your value-type method arguments. Unlike the ActionFilterAttribute, the DefaultValueAttribute will not perform any filtering, such as making sure a string argument begins with "Foo" or that a decimal input only contains two decimal places; for this type of logic, continue to use the ActionFilterAttribute. DefaultValueAttribute is a perfect fit for eliminating Nullable<int> in Action arguments, making the code clean, simple, and elegant. Eliminate the extra code, and let the framework do the work for you.