In this post we are going to explore the way AspNet Core aids us as developers with binding processes that create .Net objects from incoming http requests so that we do not need to parse this values manually.

This process is called ModelBinding. And it is very simple to use with the default implementation and is easily and readily extendable. In the default case it is a matter of supplying parameters to an controller action. Where you then get the value is determined by a given sequence which we will explore shortly.

What this post will cover

  • Model binding from query string
  • Model binding from attributes
  • Model binding from form values

What this post will not cover

  • Model validation
  • Writing custom model binding

We will look at model binding with a standalone MVC project. For this you can either use the Empty template or use the Visual Studio MVC application template with all conventions set up.

For further demonstration we then create a Models namespace.
With the following ModelData that has lots of different C# types that can be used to demonstrate the ModelBinding principles.

public class Person 
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public Address HomeAddress { get; set; }
public bool IsApproved { get; set; }
public Role Role { get; set; }
}
public class Address 
{
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
}
public enum Role
{
Admin,
User,
Guest 
}

We also going to need an IRepository to manage the Person Model, I will only show the interface, as the implementation will be some kind of in memory data structure (like a dictionary or list or whatever, does not matter for our intents and purposes).

public interface IRepository
{
IEnumerable<Person> GetAll();
Person FindById(int id);
}

For our simple example this will be sufficient, because the Persons will be hardcoded, so we can concentrate on the model binding features.

To access our data we also need a Controller in the Controller namespace:

public class HomeController : Controller 
{
private IRepository _repository;
public HomeController(IRepository repository) 
{
_repository = repository;
}
public ViewResult Index(int id)
=> View(_repository.FindById(id));
}

As I generally do, when it is not the main focus, I omit the html file for the Index view.
The configuration will also be omitted, as I think it will use all the default values you might have seen quite often. (we also add a Singleton MemoryRepository/IRepository)

Understand Model Binding

With all the requirements set up, we will now look at the main focus of this post, to understand how the ModelBinding process works.
As mentioned above ModelBinding works as an elegant bridge between http requests and C# action methods.

The easiest way we can see model binding in action is with the request of the following URL and the default routes action method that is invoked:

/Home/Index/1

and

public ViewResult Index(int id)…

The id parameter for the action method as a primitive C# type is taken from the URL and parsed to the expected value. This is necessary so that the method can be invoked. The model binding system relies on so called Model binders.

The ModelBinders look in the following places for values that correspond to the method parameter(s):

  1. Form data values (html form)
  2. Routing variables (like the id above)
  3. Query strings of the incoming url

So in this request we assumed that there was no form data. According to the above list, the next in sequence is routing then. (from default route with segment {id} see my routing introduction on how that works)

Note that the order in which data is tried to parse is important.
Consider the following URL

/Home/Index/1?id=3

The parameter for the action method will be the integer value 1. This is because the above order is processed! In other words, the first one that can parse the expected value will be used, and then stops the sequence (think chain of responsibility pattern)

Default Bindings

As one probably expects, the MVC ModelBinding will use the defaultValues, for integer it is 0 for example, if no value can be parsed. So as long as we have not evaluated the model Validation principles, an action method needs to check for the default values of the parameters.

In our example this would look like this:

 

public ViewResult Index(int id)
=> View(_repository.FindById(id) ?? _repository.GetAll().First());

You could also do one of the following:

  • Apply default values to routing
  • Apply default values to action method parameters

Binding Simple Types

As we saw with id, simple types are those that can be parsed from a string to the required value.
This includes numeric values, boolean values, datetimes and string values.

If the model binding can again not convert the given value, it retreats to the default value.
If we would use the URL /Home/Index/not_parseable_to_int, it would result in the default value, yet /Home/Index/0 would result also in the default value so to speak. There is a semantic difference here, which can be overcome by making the id property to a Nullable<int> type and then checking if it has a Value or not.

With this nullable type we can now change the method to:

public ViewResult Index(int? id)
{
if (id.HasValue && (person = _repository.FindById(id)) != null)
{
return View(person);
}
else 
{
return NotFound();
}

With this Nullable value we can distinguish the values and return a 404 NotFound if no value or a false value is supplied to the method.

Binding Complex or user defined types

For a value that cannot be parsed from simple string value, like complex types, the ModelBinding process uses reflection to get a set of the target types public properties. The values are then matched.

To demonstrate we add two methods to the HomeController

public ViewResult Create() 
=> View(new Person());
[HttpPost]
public ViewResult Create(Person model) 
=> View("Index", model);

This time we will need the form and the html therefore I will supply a simple html form, in a cshtml file that is strongly typed for the Person class:

@model Person
@{
ViewBag.Title = "Create Person";
Layout = "_Layout";
}
<form asp-action="Create" method="post">
<div class="form-group">
<label asp-for="PersonId"></label>
<input asp-for="PersonId" class="form-control" />
</div>
<div class="form-group">
<label asp-for="FirstName"></label>
<input asp-for="FirstName" class="form-control" />
</div>
<div class="form-group">
<label asp-for="LastName"></label>
<input asp-for="LastName" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Role"></label>
<select asp-for="Role" class="form-control"
asp-items="@new SelectList(Enum.GetNames(typeof(Role)))"></select>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>

The form allows for values that can be supplied to the post method and as we saw above the Person class is then created with the values of the form by using reflection.

With the above form, the model binding process recognizes that the action method it is posted to need a complex type. The Model binding inspects the Persons public properties, and checks on the form values if a asp-for tag helper value matches this Properties name. You can see the tag helpers (asp-for) that are used by model binding to complete this process.

This works recursively for nested complex types (as is the case with the address property).

To provide data for the Address property, which is called HomeAddress in the Person class we need to add the following after the Role
div section:

...
<div class="form-group">
<label asp-for="HomeAddress.City"></label>
<input asp-for="HomeAddress.City" class="form-control" />
</div>
<div class="form-group">
<label asp-for="HomeAddress.Country"></label>
<input asp-for="HomeAddress.Country" class="form-control" />
</div>
...

The tag helpers will assist the model binding process to find the needed Values for the address type.

Change the default binding behaviors
This default behaviors can be changed with the use of Attributes. One Example might be, that you’d only want to bind some of the properties of a Type. For this consider the matching of the PersonSummary Type we created for the Models namespace:

public class PersonSummary
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}

You can than match one type to another with the bind attribute:

public ViewResult DisplaySummary([Bind(Prefix = nameof(Person)] personSummary)
=> View(personSummary);

For this we’d need to change the tag helper asp-action=”Create” to asp-action=”DisplaySummary” so the DisplaySummary action method is called. The rest is done by the model binding middleware.

Selectively Binding Properties

Another Notion is to tell the model binder not to bind a specific property. In other words you can have fine grained control over the process of Model binding. The Bind attributes first parameter is a comma separated list of the property names that should be included. This can, and probably should, be applied to the model class itself, else it would only work for the action method with the given attribute.

[Bind(nameof(PersonId))]
public class PersonSummary
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}

If I use the PersonSummary in the above example with this setup, only the PersonId would be populated.

Another way to accomplish this would be to use the BindNever attribute

public class PersonSummary
{
public int PersonId { get; set; }
[BindNever]
public string FirstName { get; set; }
[BindNever]
public string LastName { get; set; }
}

This could be useful for sensitive data, that should not be tampered with from the form or request body by a savvy user.

Binding Arrays and Collections

The ModelBinding process supports arrays by default.
e.g.

...
public ViewResult Names(string[] names) => View(names ?? new string[0]);
..

When we add the following View called Names that is strongly typed to the

@model string[] like the following:
<form method="post" action="/Home/Names">
<div class="form-group">
<label>Name 1:</label>
<input id="names" name="names" class="form-control" />
</div>
<div class="form-group">
<label>Name 2:</label>
<input id="names" name="names" class="form-control" />
</div>
<div class="form-group">
<label>Name 3:</label>
<input id="names" name="names" class="form-control" /> 
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>

The nice thing about this is, that every form value with the name “names” will be used to gather items for the array. So all the above created input elements will generate a string that is supplied to the action method on posting the form.

Binding to collections

The ModelBinding also works with strongly typed collections like List<T> etc.
This also supports complex types as the Type parameter. You can just create method parameters with IList<ComplexType> and bind from form values etc.

Select Binding Source

As I mentioned with the introduction of this post you can relatively simple determine where the data for your action methods should be parsed from by simply applying attributes to the action parameters.

With the following Attributes you can override the standard ModelBinding sequence and select exactly which source should be selected.

  • FromForm obviously looks in a html form and parameter name or use Name property to specify
  • FromRoute uses routing system, parameter name, or use Name property to specify
  • FromQuery uses the query string, parameter name or Name property
  • FromHeader http request header, parameter name, or name property
  • FromBody http request body, parameter name, or Name property (necessary for api controllers)

This would look like the following on an action method:

...
public ViewResult Queried([FromQuery]int? id)
=> View(id);
...
URL : /Home/Queried/?id=42

FromHeader, FromBody are probably the most important for modern applications

Summary

In this post we looked at how MVC supports a feature to bridge the gap between http request values and C# types. We also looked at the way the ModelBinding process works by default and how it can be customized for your own applications needs.


0 Comments

Leave a Reply

Avatar placeholder