Improvements to Model Binding in ASP.NET Core

Model Binding has been a popular feature of ASP.NET MVC, but has, before now, had some minor restrictions. ASP.NET Core now has enhanced model binding: If a value passed via routes, posted data, or query strings couldn't be bound appropriately, it forced a global exception. Now, there are a number of ways of providing a more graceful reaction to incorrect data. You can now force the binding to a given source or create new sources for binding to. Dino Esposito explains.

Model binding in ASP.NET MVC is an excellent example of how some apparently trivial improvements to a technology can sometimes make life easier for the programmer. To experience the difference, think of how cumbersome it could be to read an integer value passed on the query string of the URL.

Unfortunately, the content stored in the variable is a string and some explicit conversion is required to have a usable integer. With model-binding in action, it becomes much smoother:

The native model-binding layer kicks in and finds any value available around the HTTP request that can be mapped to the name of the “number” parameter. If the type of the declared parameter is compatible with the data in the request, the model binding layer does the conversion and serves the controller method, easily and effectively, an appropriate value. It is a good clean process that reduces the amount of written code that you or other members of the team have to read at some point. Any data you need to process from the incoming request is simply injected into the controller’s space through the signature of the method.

Of course, there is a possible drawback in terms of performance. Internally, model binding uses plenty of reflection to do the job and this reflection comes at a cost in terms of CPU cycles per request. However, a significant share of those CPU cycles is necessary anyway in order to get ready to process the data. In the end, it is a matter of who does what, and model binding can do its job very well. In ASP.NET Core it does it even better than in classic ASP.NET MVC.

New Features in ASP.NET Core

The first improvement I wish to mention is one I personally long waited for. It’s another of those small features that together make programming easier. Before ASP.NET Core, any failed attempt to bind to a model resulted in an exception being thrown. Moreover, the exception is thrown outside the control of the controller class so that a global error handler is the only way to catch it. A very common situation is when the controller method needs to receive an integer and either no integer is passed or no value is passed that can be turned into an integer. Let’s consider the following URL:

Let’s consider now a possible controller method that will handle it.

The query string of the request contains the value “hello” associated to the parameter number and it’s quite impossible to turn it into an integer. In ASP.NET MVC, you will get an exception unless you change the type of the parameter number to a nullable integer or give it a default value. The improvement to this In ASP.NET Core is that the model binding layer will automatically give the parameter number the default value of the type (0 for an integer) if the binding can’t happen for whatever reason.

ASP.NET Core also offers extreme flexibility when choosing the source of values for model binding. In the default configuration of classic ASP.NET MVC, the values to bind might come from routes, posted data, and query strings, in this exact order. In ASP.NET Core, the order is slightly different and posted data is processed before route data and query strings. In classic ASP.NET MVC furthermore you can’t control the choice of the binding source. When a value with the same parameter name is uploaded you can’t force the process to read the value from a particular source and are, instead, tied to the fixed order of processing. This is entirely different in ASP.NET Core.

Bind Behavior Attributes

Two new attributes can be used with model binding in ASP.NET Core. They are BindNever and BindRequired. Once applied, the former indicates that binding should never happen on that particular element. The latter, instead, indicates that binding is required. It isn’t entirely clear how these two relate. First of all, BindRequired has nothing to do with the Required data annotations attribute that helps in validating a model. The two attributes only determine whether or not binding will happen. Both attributes can only be applied at the class or, better yet, property level. Here’s an example:

Imagine now that the class Container is used to capture input data on a controller action method, as below.

The code parameter is not decorated in any way and its behavior is exactly the same as in previous versions of ASP.NET MVC. If there’s any request data that can be mapped, it will receive it. Suppose now that there’s some request data that can be mapped to a parameter named number. The default behavior—the same behavior as in old ASP.NET MVC—would provide for both the number method parameter and the Number property to receive the value. In ASP.NET Core, instead, a class property decorated with the BindNever attribute will not be subject to model binding. If you invoke a URL with a query string of ?number=67&code=90 then you would see what’s in the figure below.

In fact, the figure shows that the Number property of the Container object has not been bound to the incoming data. This is the effect of the BindNever attribute. At the same time, the plain method parameter named number has been correctly set via model binding.

The attribute BindNever may come handy when you are receiving posted data into an input model class and don’t want to have some posted data spoil the state of the object. The user of BindRequired is a bit trickier. The attribute doesn’t originate errors or exceptions but it simply invalidates the model state if binding failed on the decorated property.

Put another way, if you decorate Number with the BindRequired attribute and no value is posted that can be mapped there, then nothing bad happens to your application. However, the moment you check whether the model state is valid you get a negative answer.

Note that BindNever and BindRequired attributes can’t be applied to method parameters.

Forcing Binding to a Given Source

As mentioned, the model binding layer firstly processes route data, followed by posted data and finally query string data in a fixed order. Consider the following URL:

The ambiguity is resolved when assigning the parameter code the route value of 123 because route data is gets higher priority than query string values.

In ASP.NET Core, you can alter the fixed order of model binding data sources by forcing the source for a particular parameter. You can do this through any of the following new attributes: FromQuery, FromRoute, and FromForm. As the names indicate, those attributes force the model binding layer to map values from query strings, route data and posted data respectively. For the same URL above, let’s consider the following controller code.

The FromQuery attribute forces the binding of parameter code to whatever comes from the query string with a matching name. In this case, the parameter code will receive a value of 456 overriding the value being passed with the route data of the URL. The interesting thing is that if the query string doesn’t contain a matching value then the code parameter takes the default value for the declared type rather than any other matching value being posted. In other words, the net effect of any of the FromQuery, FromRoute and FromForm attributes is constraining the model binding to exactly the specified data source.

Additional Sources for Model binding

It’s not unusual that some data to be used in the processing of a HTTP request is passed in through a HTTP header. Model binding doesn’t work automatically with the content that comes in through HTTP headers, but in this case, you can retrieve it quite comfortably from within the controller code using the Request.Headers collection. Interestingly, this has been one of the favorite, and commonly used, extensions to the ASP.NET MVC framework.

To instruct ASP.NET MVC to support additional data sources for model binding, you create custom value providers. Sample additional value providers may read from HTTP headers, cookies, session state and whatever else you can think of that makes sense. A custom value provider is a class that implements the IValueProvider interface that you register at the application’s startup, as below:

To register a custom value provider in ASP.NET Core you need the following code instead.

In ASP.NET Core, a new attribute makes it easier to map header values more comfortably—the FromHeader attribute. The main problem I see with automatic binding of HTTP header values to action method parameters is the structure of the names. A header name such as Accept-Language, for example, would require a parameter named accordingly, except that dashes are not acceptable in a C# variable name. The FromHeader attribute provides an elegant workaround.

The attribute gets the header name as an argument and binds the associated value to the method parameter. As a result of the previous code, the language parameter will receive the value of the Accept-Language header. (See Figure.)

In other circumstances, it might worthwhile passing request data not via the URL or headers but as part of the request body. To enable the controller method to receive body content you must explicitly tell the model binding layer to parse the body content to a particular parameter. This is the job that the FromBody attribute does for you in ASP.NET Core. All that is required on your end is decorating a parameter method with the attribute, as below.

The entire content of the request (GET or POST) will be processed as a single unit and mapped wherever possible to the parameter standing possible type constraints.

Note that FromHeader and FromBody are new attributes if you see them through the lens of the MVC application model. However, for those accustomed with Web API development both attributes are old acquaintances.

The FromServices Attribute

Yet another new model binding tool you find in ASP.NET Core is the FromServices attribute. It still relates to binding values to controller method parameters but it involves a brand-new subsystem of ASP.NET Core—the Dependency Injection (DI) subsystem.

Speaking in general, DI provides three ways of injecting data into a class—through the constructor, a public property or a method parameter. Injecting through the constructor is enabled by default as long as a type registered with the DI system is discovered in the constructor’s signature.

To enable DI in the other two scenarios, you resort to the FromServices attribute. You can attach the FromServices attribute to a public property of a controller or a class, and have it automatically bound to an instance of the registered type, if any. Similarly, you can do the same on a method parameter.

The FromServices attribute relies on the ASP.NET Core embedded DI system and gives you all its benefits but only when you strictly need it. If you inject dependencies in the controller constructor and use them only in a few methods then you would pay the overhead of setting up DI for each and every request. With FromServices, instead, you would pay the overhead on a per-use basis.

Summary

Model binding has been a powerful feature of ASP.NET MVC since its early days; it has worked effectively from the start. The advent of Web API brought some extra features such as FromHeader and FromBody that were never brought back to the plain ASP.NET MVC application model. ASP.NET Core unifies the former MVC and Web API application models and provides a larger and more functional framework for model binding.

  • 4703 views

  • Rate
    [Total: 14    Average: 4.8/5]
  • Paul Speranza

    Thanks, great examples and explanations.

  • It’s nice to see that you can now control the binding source: FromQuery, FromRoute, FromForm, etc.

  • Jovan Paskota

    In “old” Asp.net I could use a Custom ValueProviderFactory and look for a value in either a Header or a Querystring whichever was found. This for providing flexibility in an API. How would I do that in Core?

  • Dhanik Sahni

    Thanks. Very good article with easy explanation.