Chapter 3. The model-binding architecture

It does not matter how slowly you go, so long as you do not stop.

Confucius

By default, the Microsoft Visual Studio standard project template for ASP.NET MVC applications includes a Models folder. If you look around for some guidance on how to use it and information about its intended role, you’ll quickly reach the conclusion that the Models folder exists to store model classes. Fine, but which model is it for? Or, more precisely, what’s the definition of a “model”?

I like to say that “model” is the most misunderstood idea in the history of software. As a concept, it needs to be expanded a bit to make sense in modern software. When the Model-View-Controller (MVC) pattern was introduced, software engineering was in its infancy, and applications were much simpler than today. Nobody really felt the need to break up the concept of model into smaller pieces. Such smaller pieces, however, existed.

In general, I find at least two distinct models: the domain model and the view model. The former describes the data you work with in the middle tier and is expected to provide a faithful representation of the entities and relationships that populate the business domain. These entities are typically persisted by the data-access layer and consumed by services that implement business processes. This domain model pushes a vision of data that is, in general, distinct and likely different from the vision of data you find in the presentation layer. The view model just describes the data that is being worked on in the presentation layer. A good example might be the canonical Order entity. There might be use-cases in which the application needs to present a collection of orders to users but not all properties are required. For example, you might need ID, date, and total, and likely a distinct container class—a data-transfer object (DTO).

Having said that, I agree with anyone who points out that not every application needs a neat separation between the object models used in the presentation and business layers. You might decide that for your own purposes the two models nearly coincide, but you should always recognize the existence of two distinct models that operate in two distinct layers.

This chapter introduces a third type of model that, although hidden for years in the folds of the ASP.NET Web Forms runtime, stands on its own in ASP.NET MVC: the input model. The input model refers to the model through which posted data is exposed to controllers and subsequently received by the application. The input model defines the DTOs the application uses to receive data from input forms.

Note

Yet another flavor of model not mentioned here is the data model or the (mostly relational) model used to persist data.

The input model

Chapter 1, discusses request routing and the overall structure of controller methods. Chapter 2, explores views as the primary result of action processing. However, neither chapter thoroughly discusses how in ASP.NET MVC a controller method gets input data.

In ASP.NET Web Forms, we had server controls, view state, and the overall page life cycle working in the background to serve input data that was ready to use. With ASP.NET Web Forms, developers had no need to worry about an input model. Server controls in ASP.NET Web Forms provided a faithful server-side representation of the client user interface. Developers just needed to write C# code to read from input controls.

ASP.NET MVC makes a point of having controllers receive input data, not retrieve it. To pass input data to a controller, you need to package data in some way. This is precisely where the input model comes into play.

To better understand the importance and power of the new ASP.NET MVC input model, let’s start from where ASP.NET Web Forms left us.

Evolving from the Web Forms input processing

An ASP.NET Web Forms application is based on pages, and each server page is based on server controls. The page has its own life cycle that spans from processing the raw request data to arranging the final response for the browser. The page life cycle is fed by raw request data such as HTTP headers, cookies, the URL, and the body, and it produces a raw HTTP response containing headers, cookies, the content type, and the body.

Inside the page life cycle, there are a few steps in which HTTP raw data is massaged into more easily programmable containers—server controls. In ASP.NET Web Forms, these “programmable containers” are never perceived as being part of an input object model. In ASP.NET Web Forms, the input model is just based on server controls and the view state.

Role of server controls

Suppose that you have a webpage with a couple of TextBox controls to capture a user name and password. When the user posts the content of the form, there is likely a piece of code to process the request similar to what is shown in the following code:

public void Button1_Click(Object sender, EventArgs e)
{
   // You're about to perform requested action using input data.
   CheckUserCredentials(TextBox1.Text, TextBox2.Text);
   ...
}

The overall idea behind the architecture of ASP.NET Web Forms is to keep the developer away from raw data. Any incoming request data is mapped to properties on server controls. When this is not possible, data is left parked in general-purpose containers such as QueryString or Form.

What would you expect from a method such as the Button1_Click just shown? That method is the Web Forms counterpart of a controller action. Here’s how to refactor the previous code to use an explicit input model:

public void Button1_Click(Object sender, EventArgs e)
{
   // You're actually filling in the input model of the page.
   var model = new UserCredentialsInputModel();
   model.UserName = TextBox1.Text;
   model.Password = TextBox2.Text;

   // You're about to perform the requested action using input data.
   CheckUserCredentials(model);
   ...
}

The ASP.NET runtime environment breaks up raw HTTP request data into control properties, thus offering a control-centric approach to request processing.

Role of the view state

Speaking in terms of a programming paradigm, a key distinguishing characteristic between ASP.NET Web Forms and ASP.NET MVC is the view state. In Web Forms, the view state helps server controls to always be up to date. Because of the view state, as a developer you don’t need to care about segments of the user interface that you don’t touch in a postback. Suppose that you display a list of choices into which the user can drill down. When the request for details is made, in Web Forms all you need to do is display the details. The raw HTTP request, however, posted the list of choices as well as key information to find. The view state makes it unnecessary for you to deal with the list of choices.

The view state and server control build a thick abstraction layer on top of classic HTTP mechanics, and they make you think in terms of page sequences rather than successive requests. This is neither wrong nor right; it is just the paradigm behind Web Forms. In Web Forms, there’s no need for clearly defining an input model. If you do that, it’s only because you want to keep your code cleaner and more readable.

Input processing in ASP.NET MVC

Chapter 1, explains that a controller method can access input data through Request collections—such as QueryString, Headers, or Form—or value providers. Although it’s functional, this approach is not ideal from a readability and maintenance perspective. You need an ad hoc model that exposes data to controllers.

The role of model binders

ASP.NET MVC provides an automatic binding layer that uses a built-in set of rules for mapping raw request data from any value providers to properties of input model classes. As a developer, you are largely responsible for the design of input model classes. The logic of the binding layer can be customized to a large extent, thus adding unprecedented heights of flexibility, as far as the processing of input data is concerned.

Flavors of a model

The ASP.NET MVC default project template offers just one Models folder, thus implicitly pushing the idea that “model” is just one thing: the model of the data the application is supposed to use. Generally speaking, this is a rather simplistic view, though it’s effective in very simple sites.

If you look deeper into things, you can recognize three different types of “models” in ASP.NET MVC, as illustrated in Figure 3-1.

Types of models potentially involved in an ASP.NET MVC application.
Figure 3-1. Types of models potentially involved in an ASP.NET MVC application.

The input model provides the representation of the data being posted to the controller. The view model provides the representation of the data being worked on in the view. Finally, the domain model is the representation of the domain-specific entities operating in the middle tier.

Note that the three models are not neatly separated, which Figure 3-1 shows to some extent. You might find overlap between the models. This means that classes in the domain model might be used in the view, and classes posted from the client might be used in the view. The final structure and diagram of classes is up to you.

Model binding

Model binding is the process of binding values posted over an HTTP request to the parameters used by the controller’s methods. Let’s find out more about the underlying infrastructure, mechanics, and components involved.

Model-binding infrastructure

The model-binding logic is encapsulated in a specific model-binder class. The binder works under the control of the action invoker and helps to figure out the parameters to pass to the selected controller method.

Analyzing the method’s signature

Chapter 1 points out that each and every request passed to ASP.NET MVC is resolved in terms of a controller name and an action name. Armed with these two pieces of data, the action invoker—a native component of the ASP.NET MVC runtime shell—kicks in to actually serve the request. First, the invoker expands the controller name to a class name and resolves the action name to a method name on the controller class. If something goes wrong, an exception is thrown.

Next, the invoker attempts to collect all values required to make the method call. In doing so, it looks at the method’s signature and attempts to find an input value for each parameter in the signature.

Getting the binder for the type

The action invoker knows the formal name and declared type of each parameter. (This information is obtained via reflection.) The action invoker also has access to the request context and to any data uploaded with the HTTP request—the query string, the form data, route parameters, cookies, headers, files, and so forth.

For each parameter, the invoker obtains a model-binder object. The model binder is a component that knows how to find values of a given type from the request context. The model binder applies its own algorithm, which includes the parameter name, parameter type, and request context available, and returns a value of the specified type. The details of the algorithm belong to the implementation of the model binder being used for the type.

ASP.NET MVC uses a built-in binder object that corresponds to the DefaultModelBinder class. The model binder is a class that implements the IModelBinder interface.

public interface IModelBinder
{
    Object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext);
}

Let’s first explore the capabilities of the default binder and then see what it takes to write custom binders for specific types later.

The default model binder

The default model binder uses convention-based logic to match the names of posted values to parameter names in the controller’s method. The DefaultModelBinder class knows how to deal with primitive and complex types as well as collections and dictionaries. In light of this, the default binder works just fine most of the time.

Note

If the default binder supports primitive and complex types and the collections thereof, will you ever feel the need to use something other than the default binder? You will hardly ever feel the need to replace the default binder with another general-purpose binder. However, the default binder can’t apply your custom logic to massage request data into the properties of a given type. As you’ll see later, a custom binder is helpful when the values being posted with the request don’t exactly match the properties of the type you want the controller to use. In this case, a custom binder makes sense and helps keep the controller’s code lean and mean.

Binding primitive types

Admittedly, it sounds a bit magical at first, but there’s no actual wizardry behind model binding. The key fact about model binding is that it lets you focus exclusively on the data you want the controller method to receive. You completely ignore the details of how you retrieve that data, whether it comes from the query string or the route.

Suppose that you need a controller method to repeat a particular string a given number of times. Here’s what you do:

public class BindingController : Controller
{
   public ActionResult Repeat(String text, Int32 number)
   {
      var model = new RepeatViewModel {Number = number, Text = text};
      return View(model);
   }
}

Designed in this way, the controller is highly testable and completely decoupled from the ASP.NET runtime environment. There’s no need for you to access the Request object or the Cookies collection directly.

Where do the values for text and number come from? And, which component is actually reading them into text and number parameters?

The actual values are read from the request context, and the default model-binder object does the trick. In particular, the default binder attempts to match formal parameter names (text and number in the example) to named values posted with the request. In other words, if the request carries a form field, a query-string field, or a route parameter named text, the carried value is automatically bound to the text parameter. The mapping occurs successfully as long as the parameter type and actual value are compatible. If a conversion cannot be performed, an argument exception is thrown. The next URL works just fine:

http://server/binding/repeat?text=Dino&number=2

Conversely, the following URL causes an exception:

http://server/binding/repeat?text=Dino&number=true

The query-string field text contains Dino, and the mapping to the String parameter text on the method Repeat takes place successfully. The query-string field number, on the other hand, contains true, which can’t be successfully mapped to an Int32 parameter. The model binder returns a parameters dictionary in which the entry for number contains null. Because the parameter type is Int32—that is, a non-nullable type—the invoker throws an argument exception.

Dealing with optional values

An argument exception that occurs because invalid values are being passed is not detected at the controller level. The exception is fired before the execution flow reaches the controller. This means that you won’t be able to catch it with try/catch blocks.

If the default model binder can’t find a posted value that matches a required method parameter, it places a null value in the parameter dictionary returned to the action invoker. Again, if a value of null is not acceptable for the parameter type, an argument exception is thrown before the controller method is even called.

What if a method parameter must be considered optional?

A possible approach entails changing the parameter type to a nullable type, as shown here:

public ActionResult Repeat(String text, Nullable<Int32> number)
{
    var model = new RepeatViewModel {Number = number.GetValueOrDefault(), Text = text};
    return View(model);
}

Another approach consists of using a default value for the parameter:

public ActionResult Repeat(String text, Int32 number=4)
{
    var model = new RepeatViewModel {Number = number, Text = text};
    return View(model);
}

Any decisions about the controller method’s signature are up to you. In general, you might want to use types that are very close to the real data being uploaded with the request. Using parameters of type Object, for example, will save you from argument exceptions, but it will make it hard to write clean code to process the input data.

The default binder can map all primitive types, such as String, integers, Double, Decimal, Boolean, DateTime, and related collections. To express a Boolean type in a URL, you resort to the true or false strings. These strings are parsed using .NET native Boolean parsing functions, which recognize true and false strings in a case-insensitive manner. If you use strings such as yes/no to mean a Boolean, the default binder won’t understand your intentions and places a null value in the parameter dictionary, which might cause an argument exception.

Value providers and precedence

The default model binder uses all the registered value providers to find a match between posted values and method parameters. By default, value providers cover the collections listed in Table 3-1.

Table 3-1. Request collections for which a default value provider exists

Collection

Description

Form

Contains values posted from an HTML form, if any

RouteData

Contains values excerpted from the URL route

QueryString

Contains values specified as the URL’s query string

Files

A value is the entire content of an uploaded file, if any

Table 3-1 lists request collections being considered in the exact order in which they are processed by the default binder. Suppose that you have the following route:

routes.MapRoute(
    "Test",
    "{controller}/{action}/test/{number}",
    new { controller = "Binding", action = "RepeatWithPrecedence", number = 5 }
);

As you can see, the route has a parameter named number. Now, consider this URL:

/Binding/RepeatWithPrecedence/test/10?text=Dino&number=2

The request uploads two values that are good candidates to set the value of the number parameter in the RepeatWithPrecedence method. The first value is 10 and is the value of a route parameter named number. The second value is 2 and is the value of the QueryString element named number. The method itself provides a default value for the number parameter:

public ActionResult RepeatWithPrecedence(String text, Int32 number=20)
{
    ...
}

Which value is actually picked up? As Table 3-1 suggests, the value that is actually passed to the method is 10, which is the value read from the route data collection.

Binding complex types

There’s no limitation regarding the number of parameters you can list on a method’s signature. However, a container class is often better than a long list of individual parameters. For the default model binder, the result is nearly the same whether you list a sequence of parameters or just one parameter of a complex type. Both scenarios are fully supported. Here’s an example:

public class ComplexController : Controller
{
   public ActionResult Repeat(RepeatText inputModel)
   {
      var model = new RepeatViewModel
                      {
                        Title = "Repeating text",
                        Text = inputModel.Text,
                        Number = inputModel.Number
                      };
     return View(model);
   }
}

The controller method receives an object of type RepeatText. The class is a plain data-transfer object, defined as follows:

public class RepeatText
{
    public String Text { get; set; }
    public Int32 Number { get; set; }
}

As you can see, the class just contains members for the same values you passed as individual parameters in the previous example. The model binder works with this complex type as well as it did with single values.

For each public property in the declared type—RepeatText in this case—the model binder looks for posted values whose key names match the property name. The match is case insensitive. Here’s a sample URL that works with the RepeatText parameter type:

http://server/Complex/Repeat?text=ASP.NET%20MVC&number=5

Figure 3-2 shows the output that the URL might generate.

Repeating text with values extracted from a complex type.
Figure 3-2. Repeating text with values extracted from a complex type.

Binding collections

What if the argument that a controller method expects is a collection? For example, can you bind the content of a posted form to an IList<T> parameter? The DefaultModelBinder class makes it possible, but doing so requires a bit of contrivance of your own. Have a look at Figure 3-3.

The page will post an array of strings.
Figure 3-3. The page will post an array of strings.

When the user clicks the Send button, the form submits its content. Specifically, it sends out the content of the various text boxes. If the text boxes have different IDs, the posted content takes the following form:

[email protected]&TextBox2=&TextBox3=&TextBox4=&TextBox5=

In classic ASP.NET, this is the only possible way of working because you can’t just assign the same ID to multiple controls. However, if you manage the HTML yourself, nothing prevents you from assigning the five text boxes in the figure the same ID. The HTML DOM, in fact, fully supports this scenario (though it is not recommended). Therefore, the following markup is entirely legal in ASP.NET MVC and produces HTML that works on all browsers:

@using (Html.BeginForm())
{
    <h2>List your email address(es)</h2>
    foreach(var email in Model.Emails)
    {
        <input type="text" name="email" value="@email" />
        <br />
    }
    <input type="submit" value="Send" />
}

What’s the expected signature of a controller method that has to process the email addresses typed in the form? Here it is:

public ActionResult Emails(IList<String> email)
{
    ...
}

Figure 3-4 shows that an array of strings is correctly passed to the method, thanks to the default binder class.

An array of strings has been posted.
Figure 3-4. An array of strings has been posted.

As is dicussed in greater detail in Chapter 4, when you work with HTML forms, you likely need to have a pair of methods: one to handle the display of the view (the verb GET), and one to handle the scenario in which data is posted to the view. The HttpPost and HttpGet attributes make it possible for you to mark which scenario a given method is handling for the same action name. Here’s the full implementation of the example, which uses two distinct methods to handle GET and POST scenarios:

[ActionName("Emails")]
[HttpGet]
public ActionResult EmailForGet(IList<String> emails)
{
    // Input parameters
    var defaultEmails = new[] { "[email protected]", "", "", "", "" };
    if (emails == null)
        emails = defaultEmails;
    if (emails.Count == 0)
        emails = defaultEmails;
    var model = new EmailsViewModel {Emails = emails};
    return View(model);
}

[ActionName("Emails")]
[HttpPost]
public ActionResult EmailForPost(IList<String> email)
{
    var defaultEmails = new[] { "[email protected]", "", "", "", "" };
    var model = new EmailsViewModel { Emails = defaultEmails, RegisteredEmails = email };
    return View(model);
}

Here’s the full Razor markup for the view you see rendered in Figure 3-5:

@model BindingFun.ViewModels.Complex.EmailsViewModel

<h2>List your email address(es)</h2>
@using (Html.BeginForm())
{
    foreach(var email in Model.Emails)
    {
        <input type="text" name="email" value="@email" />
        <br />
    }
    <input type="submit" value="Send" />
}

<hr />
<h2>Emails submitted</h2>
<ul>
@foreach (var email in Model.RegisteredEmails)
{
    if (String.IsNullOrWhiteSpace(email))
    {
        continue;
    }
    <li>@email</li>
}
</ul>
The page rendered after a POST.
Figure 3-5. The page rendered after a POST.

In the end, to ensure that a collection of values is passed to a controller method, you need to ensure that elements with the same ID are emitted to the response stream. The ID, then, must match to the controller method’s signature according to the normal rules of the binder.

In case of collections, the required match between names forces you to violate basic naming conventions. In the view, you have input fields and would like to call them, for instance, email using the singular. When you name the parameter in the controller, because you’re getting a collection, you would like to name it, for instance, emails. Instead, you’re forced to use either email or emails all the way through. The workaround comes in a moment when we move on to consider customizable aspects of model binders.

Binding collections of complex types

The default binder can also handle situations in which the collection contains complex types, even nested, as demonstrated here:

[ActionName("Countries")]
[HttpPost]
public ActionResult CountriesForPost(IList<Country> country)
{
   ...
}

As an example, consider the following definition for type Country:

public class Country
{
    public Country()
    {
        Details = new CountryInfo();
    }
    public String Name { get; set; }
    public CountryInfo Details { get; set; }
}
public class CountryInfo
{
    public String Capital { get; set; }
    public String Continent { get; set; }
}

For model binding to occur successfully, all you really need to do is use a progressive index on the IDs in the markup. The resulting pattern is prefix[index].Property, where prefix matches the name of the formal parameter in the controller method’s signature:

@using (Html.BeginForm())
{
    <h2>Select your favorite countries</h2>
    var index = 0;
    foreach (var country in Model.CountryList)
    {
        <fieldset>
        <div>
            <b>Name</b><br />
            <input type="text"
                   name="countries[@index].Name"
                   value="@country.Name" /><br />
            <b>Capital</b><br />
            <input type="text"
                   name="country[@index].Details.Capital"
                   value="@country.Details.Capital" /><br />
            <b>Continent</b><br />
            @{
                var id = String.Format("country[{0}].Details.Continent", index++);
             }
             @Html.TextBox(id, country.Details.Continent)
             <br />
        </div>
    </fieldset>
    }
    <input type="submit" value="Send" />
}

The index is numeric, 0-based, and progressive. In this example, I’m building user interface blocks for each specified default country. If you have a fixed number of user interface blocks to render, you can use static indexes.

<input type="text"
       name="country[0].Name"
       value="@country.Name" />
<input type="text"
       name="country[1].Name"
       value="@country.Name" />

Be aware that holes in the series (for example, 0 and then 2) stop the parsing, and all you get back is the sequence of data types from 0 to the hole.

The posting of data works fine, as well. The POST method on the controller class will just receive the same hierarchy of data, as Figure 3-6 shows.

Complex and nested types posted to the method.
Figure 3-6. Complex and nested types posted to the method.

Rest assured that if you’re having trouble mapping posted values to your expected hierarchy of types, it might be wise to consider a custom model binder.

Binding content from uploaded files

Table 3-1 indicates that uploaded files can also be subject to model binding. The default binder does the binding by matching the name of the input file element used to upload with the name of a parameter. The parameter (or the property on a parameter type), however, must be declared of type HttpPostedFileBase:

public class UserData
{
    public String Name { get; set; }
    public String Email { get; set; }
    public HttpPostedFileBase Picture { get; set; }
}

The following code shows a possible implementation of a controller action that saves the uploaded file somewhere on the server computer:

public ActionResult Add(UserData inputModel)
{
    var destinationFolder = Server.MapPath("/Users");
    var postedFile = inputModel.Picture;
    if (postedFile.ContentLength > 0)
    {
        var fileName = Path.GetFileName(postedFile.FileName);
        var path = Path.Combine(destinationFolder, fileName);
        postedFile.SaveAs(path);
    }

    return View();
}

By default, any ASP.NET request can’t be longer than 4 MB. This amount should include any uploads, headers, body, and whatever is being transmitted. You can configure the value at various levels. You do that through the maxRequestLength entry in the httpRuntime section of the web.config file:

<system.web>
   <httpRuntime maxRequestLength="6000" />
</system.web>

Obviously, the larger a request is, the more room you potentially leave for hackers to prepare attacks on your site. Also keep in mind that in a hosting scenario your application-level settings might be ignored if the host has set a different limit at the domain level and locked down the maxRequestLength property at lower levels.

What about multiple file uploads? As long as the overall size of all uploads is compatible with the current maximum length of a request, you can upload multiple files within a single request. However, consider that web browsers just don’t know how to upload multiple files. All a web browser can do is upload a single file, and only if you reference it through an input element of type file. To upload multiple files, you can resort to some client-side ad hoc component or place multiple <input> elements in the form. If you use multiple <input> elements that are properly named, a class like the one shown here will bind them all:

public class UserData
{
    public String Name { get; set; }
    public String Email { get; set; }
    public HttpPostedFileBase Picture { get; set; }
    public IList<HttpPostedFileBase> AlternatePictures { get; set; }
}

The class represents the data posted for a new user with a default picture and a list of alternate pictures. Here is the markup for the alternate pictures:

<input type="file" id="AlternatePictures[0]" name="AlternatePictures[0]" />
<input type="file" id="AlternatePictures[1]" name="AlternatePictures[1]" />

Customizable aspects of the default binder

Automatic binding stems from a convention-over-configuration approach. Conventions, though, sometimes harbor bad surprises. If for some reason you lose control over the posted data (for example, in the case of data that has been tampered with), it can result in undesired binding; any posted key/value pair will, in fact, be bound. For this reason, you might want to consider using the Bind attribute to customize some aspects of the binding process.

The Bind attribute

The Bind attribute comes with three properties, which are described in Table 3-2.

Table 3-2. Properties for the BindAttribute class

Property

Description

Prefix

String property. It indicates the prefix that must be found in the name of the posted value for the binder to resolve it. The default value is the empty string.

Exclude

Gets or sets a comma-delimited list of property names for which binding is not allowed.

Include

Gets or sets a comma-delimited list of property names for which binding is permitted.

You apply the Bind attribute to parameters on a method signature.

Creating whitelists of properties

As mentioned, automatic model binding is potentially dangerous when you have complex types. In such cases, in fact, the default binder attempts to populate all public properties on the complex types for which it finds a match in the posted values. This might end up filling the server type with unexpected data, especially in the case of request tampering. To avoid that, you can use the Include property on the Bind attribute to create a whitelist of acceptable properties, such as shown here:

public ActionResult RepeatOnlyText([Bind(Include = "text")]RepeatText inputModel)
{
   ...
}

The binding on the RepeatText type will be limited to the listed properties (in the example, only Text). Any other property is not bound and takes whatever default value the implementation of RepeatText assigned to it. Multiple properties are separated by a comma.

Creating blacklists of properties

The Exclude attribute employs the opposite logic: It lists properties that must be excluded from binding. All properties except those explicitly listed will be bound:

public ActionResult RepeatOnlyText([Bind(Exclude = "number")]RepeatText inputModel)
{
   ...
}

You can use Include and Exclude in the same attribute if doing so makes it possible for you to better define the set of properties to bind. For instance, if both attributes refer to the same property, Exclude will win.

Aliasing parameters by using a prefix

The default model binder forces you to give your request parameters (for example, form and query string fields) given names that match formal parameters on target action methods. Using the Prefix attribute, you can change this convention. By setting the Prefix attribute, you instruct the model binder to match request parameters against the prefix rather than against the formal parameter name. All in all, alias would have been a much better name for this attribute. Consider the following example:

[HttpPost]
[ActionName("Emails")]
public ActionResult EmailForPost([Bind(Prefix = "email")]IList<String> emails)
{
   ...
}

For the emails parameter to be successfully filled, you need to have posted fields whose name is email, not emails. The Prefix attribute makes particular sense on POST methods and fixes the aforementioned issue with naming conventions and collections of parameters.

Finally, note that if a prefix is specified, it becomes mandatory; subsequently, fields whose names are not prefixed are not bound.

Note

Yes, the name chosen for the attribute—Prefix—is not really explanatory of the scenarios it addresses. Everybody agrees that Alias would have been a much better name. But, now it’s too late to change it!

Advanced model binding

So far, we’ve examined the behavior of the default model binder. The default binder does excellent work, but it is a general-purpose tool designed to work with most possible types in a way that is not specific to any of them. The Bind attribute gives you some more control over the binding process, but there are some reasonable limitations to its abilities. If you want to achieve total control over the binding process, all you do is create a custom binder for a specific type.

Custom type binders

There’s just one primary reason you should be willing to create a custom binder: The default binder is limited to taking into account only a one-to-one correspondence between posted values and properties on the model.

Sometimes, though, the target model has a different granularity than the one expressed by form fields. The canonical example is when you employ multiple input fields to let users enter content for a single property; for example, distinct input fields for day, month, and year that then map to a single DateTime value.

Customizing the default binder

To create a custom binder from scratch, you implement the IModelBinder interface. Implementing the interface is recommended if you need total control over the binding process. For example, if all you need to do is to keep the default behavior and simply force the binder to use a non-default constructor for a given type, inheriting from DefaultModelBinder is the best approach. Here’s the schema to follow:

public RepeatTextModelBinder : DefaultModelBinder
{
    protected override object CreateModel(
         ControllerContext controllerContext,
         ModelBindingContext bindingContext,
         Type modelType)
    {
         ...
         return new RepeatText( ... );
    }
}

Another common scenario for simply overriding the default binder is when all you want is the ability to validate against a specific type. In this case, you override OnModelUpdated and insert your own validation logic, as shown here:

protected override void OnModelUpdated(ControllerContext controllerContext,
           ModelBindingContext bindingContext)
{
   var obj = bindingContext.Model as RepeatText;
   if (obj == null)
      return;

   // Apply validation logic here for the whole model
   if (String.IsNullOrEmpty(obj.Text))
   {
      bindingContext.ModelState.AddModelError("Text", ...);
   }
   ...
}

You override OnModelUpdated if you prefer to keep in a single place all validations for any properties. You resort to OnPropertyValidating if you prefer to validate properties individually.

Important

When binding occurs on a complex type, the default binder simply copies matching values into properties. You can’t do much to refuse some values if they put the instance of the complex type in an invalid state.

A custom binder could integrate some logic to check the values being assigned to properties and signal an error to the controller method or degrade gracefully by replacing the invalid value with a default one.

Although it’s possible to use this approach, it’s not commonly employed because there are more powerful options in ASP.NET MVC that you can use to deal with data validation across an input form. And that is exactly the topic I address in Chapter 4.

Implementing a model binder from scratch

The IModelBinder interface is defined as follows:

public interface IModelBinder
{
    Object BindModel(ControllerContext controllerContext,
                     ModelBindingContext bindingContext);
}

Following is the skeleton of a custom binder that directly implements the IModelBinder interface. The model binder is written for a specific type—in this case, MyComplexType:

public class MyComplexTypeModelBinder : IModelBinder
{
  public Object BindModel(ControllerContext controllerContext,
                          ModelBindingContext bindingContext)
  {
     if (bindingContext == null)
         throw new ArgumentNullException("bindingContext");

     // Create the model instance (using the ctor you like best)
     var obj = new MyComplexType();

     // Set properties reading values from registered value providers
     obj.SomeProperty = FromPostedData<string>(bindingContext, "SomeProperty");
     ...
     return obj;
}

// Helper routine
private T FromPostedData<T>(ModelBindingContext context, String key)
{
   // Get the value from any of the input collections
   ValueProviderResult result;
   context.ValueProvider.TryGetValue(key, out result);

   // Set the state of the model property resulting from value
   context.ModelState.SetModelValue(key, result);

   // Return the value converted (if possible) to the target type
   return (T) result.ConvertTo(typeof(T));
}

The structure of BindModel is straightforward. You first create a new instance of the type of interest. In doing so, you can use the constructor (or factory) you like best and perform any sort of custom initialization that is required by the context. Next, you simply populate properties of the freshly created instance with values read or inferred from posted data. In the preceding code snippet, I assume that you simply replicate the behavior of the default provider and read values from registered value providers based on a property name match. Obviously, this is just the place where you might want to add your own logic to interpret and massage what’s being posted by the request.

Keep in mind that when writing a model binder, you are in no way restricted to getting information for the model only from the posted data, which represents only the most common scenario. You can grab information from anywhere (for example, from the ASP.NET cache and session state), parse it, and store it in the model.

Note

ASP.NET MVC comes with two built-in binders beyond the default one. These additional binders are automatically selected for use when posted data is a Base64 stream (ByteArrayModelBinder type) and when the content of a file is being uploaded (HttpPostedFileBaseModelBinder type).

Registering a custom binder

You can associate a model binder with its target type globally or locally. In the former case, any occurrence of model binding for the type will be resolved through the registered custom binder. In the latter case, you apply the binding to just one occurrence of one parameter in a controller method.

Global association takes place in the global.asax file as follows:

void Application_Start()
{
    ...
    ModelBinders.Binders[typeof(MyComplexTypeModelBinder)] =
                         new MyCustomTypeModelBinder();
}

Local association requires the following syntax:

public ActionResult RepeatText(
           [ModelBinder(typeof(MyComplexTypeModelBinder))] MyComplexType info)
{
   ...
}

Local binders always take precedence over globally defined binders.

As you can glean clearly from the preceding code within Application_Start, you can have multiple binders registered. You can also override the default binder, if required:

ModelBinders.Binders.DefaultBinder = new MyNewDefaultBinder();

However, modifying the default binder can have a considerable impact on the behavior of the application and should therefore be a very thoughtful choice.

A sample DateTime model binder

With input forms, it is quite common to have users enter a date. You can sometimes use a jQuery user interface to let users pick dates from a graphical calendar. If you use HTML5 markup on recent browsers, the calendar is automatically provided. The selection is translated to a string and saved to a text box. When the form posts back, the date string is uploaded and the default binder attempts to parse it to a DateTime object.

In other situations, you might decide to split the date into three distinct text boxes, one each for day, month, and year. These pieces are uploaded as distinct values in the request. The result is that the default binder can manage them only separately; the burden of creating a valid DateTime object out of day, month, and year values is up to the controller. With a custom default binder, you can take this code out of the controller and still enjoy the pleasure of having the following signature for a controller method:

public ActionResult MakeReservation(DateTime theDate)

Let’s see how to arrange a more realistic example of a model binder.

The displayed data

The sample view we consider next shows three text boxes for the items that make up a date as well as a submit button. You enter a date, and the system calculates how many days have elapsed since or how many days you have to wait for the specified day to arrive. Here’s the Razor markup:

@model DateEditorResponseViewModel
@section title{
    @Model.Title
}

@using (Html.BeginForm())
{
<fieldset>
    <legend>Date Editor</legend>
    <div>
        <table><tr>
        <td>@DateHelpers.InputDate("theDate", Model.DefaultDate)</td>
        <td><input type="submit" value="Find out more" /></td>
        </tr></table>
    </div>
</fieldset>
}
<hr />
@DateHelpers.Distance(Model.TimeToToday)

As you can see, I’m using a couple of custom helpers to better encapsulate the rendering of some view code. Here’s how you render the date elements:

@helper InputDate(String name, DateTime? theDate)
{
    String day="", month="", year="";
    if(theDate.HasValue)
    {
        day = theDate.Value.Day.ToString();
        month = theDate.Value.Month.ToString();
        year = theDate.Value.Year.ToString();
    }
    <table cellpadding="0">
        <thead>
            <th>DD</th>
            <th>MM</th>
            <th>YYYY</th>
        </thead>
        <tr>
            <td><input type="number" name="@(name + ".day")"
                       value="@day" style="width:30px" /></td>
            <td><input type="number" name="@(name + ".month")"
                       value="@month" style="width:30px"></td>
            <td><input type="number" name="@(name + ".year")"
                       value="@year" style="width:40px" /></td>
        </tr>
    </table>
}

Figure 3-7 shows the output.

A sample view that splits date input text into day-month-year elements.
Figure 3-7. A sample view that splits date input text into day-month-year elements.

The controller methods

The view in Figure 3-7 is served and processed by the following controller methods:

public class DateController : Controller
{
    [HttpGet]
    [ActionName("Editor")]
    public ActionResult EditorForGet()
    {
        var model = new EditorViewModel();
        return View(model);
    }

    [HttpPost]
    [ActionName("Editor")]
    public ActionResult EditorForPost(DateTime theDate)
    {
        var model = new EditorViewModel();
        if (theDate != default(DateTime))
        {
            model.DefaultDate = theDate;
            model.TimeToToday = DateTime.Today.Subtract(theDate);
        }
        return View(model);
    }
}

After the date is posted back, the controller action calculates the difference with the current day and stores the results back in the view model by using a TimeSpan object. Here’s the view model object:

public class EditorViewModel : ViewModelBase
{
    public EditorViewModel()
    {
        DefaultDate = null;
        TimeToToday = null;
    }
    public DateTime? DefaultDate { get; set; }
    public TimeSpan? TimeToToday { get; set; }
}

What remains to be examined is the code that performs the trick of transforming three distinct values uploaded independently into one DateTime object.

Creating the DateTime binder

The structure of the DateTimeModelBinder object is not much different from the skeleton I described earlier. It is just tailor-made for the DateTime type.

public class DateModelBinder : IModelBinder
{
    public Object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException("bindingContext");
        }

        // This will return a DateTime object
        var theDate = default(DateTime);

        // Try to read from posted data. xxx.Day|xxx.Month|xxx.Year is assumed.
        var day = FromPostedData<int>(bindingContext, "Day");
        var month = FromPostedData<int>(bindingContext, "Month");
        var year = FromPostedData<int>(bindingContext, "Year");

        return CreateDateOrDefault(year, month, day, theDate);
    }

    // Helper routines
    private static T FromPostedData<T>(ModelBindingContext context, String id)
    {
        if (String.IsNullOrEmpty(id))
            return default(T);

        // Get the value from any of the input collections
        var key = String.Format("{0}.{1}", context.ModelName, id);
        var result = context.ValueProvider.GetValue(key);
        if (result == null && context.FallbackToEmptyPrefix)
        {
            // Try without prefix
            result = context.ValueProvider.GetValue(id);
            if (result == null)
                return default(T);
        }

        // Set the state of the model property resulting from value
        context.ModelState.SetModelValue(id, result);

        // Return the value converted (if possible) to the target type
        T valueToReturn = default(T);
        try
        {
            valueToReturn = (T)result.ConvertTo(typeof(T));
        }
        catch
        {
        }

       return valueToReturn;
    }

    private DateTime CreateDateOrDefault(Int32 year, Int32 month, Int32 day,
                                         DateTime? defaultDate)
    {
        var theDate = defaultDate ?? default(DateTime);
        try
        {
            theDate = new DateTime(year, month, day);
        }
        catch (ArgumentOutOfRangeException e)
        {
        }

        return theDate;
    }
}

The binder makes some assumptions about the naming convention of the three input elements. In particular, it requires that those elements be named day, month, and year, possibly prefixed by the model name. It is the support for the prefix that makes it possible to have multiple date input boxes in the same view without conflicts.

With this custom binder available, all you need to do is register it either globally or locally. Here’s how to make it work with just a specific controller method:

[HttpPost]
[ActionName("Editor")]
public ActionResult EditorForPost([ModelBinder(typeof(DateModelBinder))] DateTime theDate)
{

}

Figure 3-8 shows the final page in action.

Working with dates using a custom type binder.
Figure 3-8. Working with dates using a custom type binder.

Summary

In ASP.NET MVC as well as in ASP.NET Web Forms, posted data arrives within an HTTP packet and is mapped to various collections on the Request object. To offer a nice service to developers, ASP.NET then attempts to expose that content in a more usable way.

In ASP.NET Web Forms, the content is parsed and passed on to server controls; in ASP.NET MVC, on the other hand, it is bound to parameters of the selected controller’s method. The process of binding posted values to parameters is known as model binding and occurs through a registered model-binder class. Model binders provide you with complete control over the deserialization of form-posted values into simple and complex types.

In functional terms, the use of the default binder is transparent to developers—no action is required on your end—and it keeps the controller code clean. By using model binders, including custom binders, you also keep your controller’s code free of dependencies on ASP.NET intrinsic objects and thus make it cleaner and more testable.

The use of model binders is strictly related to posting and input forms. In Chapter 4, I discuss aspects of input forms, input modeling, and data validation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.94.249