Introduction
HTTP is the backbone of a website's communication. This regulates how clients and servers exchange information. One of the must-have capabilities of a server is to get data from a client's request. In ASP NET Core, every incoming request from the client will be caught by the Kestrel server and transformed into object context, then passed into source code, and finally routed to the endpoint. First, let's look at how to get data from request object context without a model binding mechanism.
Get query string from context object
public class MoviesController : Controller | |
{ | |
[HttpGet] | |
[Route("Movies/Download")] | |
public IActionResult Download() | |
{ | |
int movieId; | |
bool isSubscribed; | |
if (HttpContext.Request.Query.TryGetValue("movieId", out var moviedIds)) | |
{ | |
if (moviedIds.Any()) | |
{ | |
string? movieIdStr = moviedIds.FirstOrDefault(); | |
movieId = Convert.ToInt32(movieIdStr); | |
} | |
} | |
if (HttpContext.Request.Query.TryGetValue("isSubscribed", out var isSubscribeds)) | |
{ | |
if (isSubscribeds.Any()) { | |
string? isSubscribedStr = isSubscribeds.FirstOrDefault(); | |
isSubscribed = Convert.ToBoolean(isSubscribedStr); | |
} | |
} | |
return Ok(); | |
} | |
} |
Without model binding, you must do extra work to do. First, you need to access the IQueryCollection
object from HttpContext
and check if there is any associate key-value pair. If any, it will give you a StringValues
type, which under the hood implements IList<string>
so you need to access a string element from that list, and then you need to perform a type conversation from the string into the appropriate value. This tedious work increases the error-proneness, luckily ASP.NET offers the easy way with Model Binding mechanism.
Model Binding
Model binding is one of the greatest mechanisms in ASP.NET. It is a process of collecting the data from an incoming request (it could be in the form of query string, route data, request body, or request header) and then supplying the values as an action method argument. Hence, you have access directly to data that was sent from the client to perform validation and process it in the business logic layer. Behind the scenes, ASP.NET will take care of the binding process, so it's not ours. The model binding mechanism will automatically perform when requests are routed to match endpoints and right before the execution of the action method.
let's look how to get query string data with model binding
[HttpGet] | |
[Route("Movies/Download")] | |
public IActionResult Download(int movieId, bool isSubscribed) | |
{ | |
return Ok(); | |
} |

It seems like magic ๐ฉโจ๐ isn't it?
It is significantly quicker and easier. to extract data from a query with model binding that you wouldn't need to perform a data conversation from a string into an appropriate type. It comes automatically behind the scenes by model binding.
In order to bind successfully, you must make sure the name of the key of the query string matches the identifier of the action method parameter. but you don't have to match the case because it is not case-sensitive. Bonded data is not limited to one source. It still works fine if you place the data in the query string and route data.
Get Query String and Route Data with model binding
public class MoviesController : Controller | |
{ | |
[HttpGet] | |
[Route("Movies/Download/{movieId}")] | |
public IActionResult Download(int movieId, bool isSubscribed) | |
{ | |
return Ok(); | |
} | |
} |

So it is great that ASP.NET has these features to make development more intuitive, easy, and fast. However, if you don't understand the behavior of model binding, you will end up confused. Let's say the client makes the following request:
From above, we can see that it has collision data between the query string and route data because they have the same identifier called "movieId". If we look at the above watch window, the movieId gets populated with a value of 89. You might wonder why ASP.NET extracts "movieId" parameter data from route data instead of a query string? It turns out ASP.NET has an order of priority for model binding. Let's take a look at the image below, It starts from the top, which has the highest priority, down to the bottom, which has less priority.
Great, now you have a better understanding of model binding behavior.
But what if you don't want to achieve this behavior?
You can use the [FromQuery]
attribute to tell ASP.NET, "Hey, forget about your order priority. I will state explicitly that I want you to bind the data from the query string instead."
public class MoviesController : Controller | |
{ | |
[HttpGet] | |
[Route("Movies/Download/{movieId}")] | |
public IActionResult Download([FromQuery] int movieId, bool isSubscribed) | |
{ | |
return Ok(); | |
} | |
} |

It will populated movieId with value of 22 instead of 89.
here another useful attribute list that you can use to explicitly specify the source of data for action method parameters
[FromRoute]
[FromQuery]
[FromBody]
[FromForm]
Model Binding with Model Class
What happens if the customer provides a lot of data? Of course, you don't want to scatter up the action method arguments; it will clutter your code. Instead, you can create a model class and declare it as an action method parameter. Model binding will automatically map incoming request data to the properties of your model class based on their names.
public class Movie | |
{ | |
public Guid MovieId { get; set; } | |
public string? Title { get; set; } | |
public string? Director { get; set; } | |
public DateOnly ReleaseDate { get; set; } | |
public string? Genre { get; set; } | |
public int Duration { get; set; } | |
} |
client send a request with a multipart/form-data
content type
[HttpPost] | |
[Route("Movies/Upload/")] | |
public IActionResult Upload(Movie movie) | |
{ | |
//insert movie into database | |
return Ok(); | |
} |

Great, the movie object has been automatically populated based on the data from the request form-data. Sometimes you want more granular control over which properties get populated from incoming request data, to achieve this, you can utilize [Bind]
and [BindNever]
attributes.
-
Bind
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters[HttpPost] [Route("Movies/Upload/")] public IActionResult Upload([Bind("Title")] Movie movie) { //insert movie into database return Ok(); } Title
value and leave the rest of properties to hold null or default value -
BindNever
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characterspublic class Movie { public Guid MovieId { get; set; } [BindNever] public string? Title { get; set; } public string? Director { get; set; } public DateOnly ReleaseDate { get; set; } public string? Genre { get; set; } public int Duration { get; set; } } Title
property excluded from the binding process hence, it will store a null value, and the rest of the properties still be populated from the binding.
Custom Model Binders
ASP.NET gives you more control over the binding process from requests into action method parameters by creating your own model binding class. so it will override the built-in Model binding mechanism and use your own model binding class. Let's say that the client will send query string data of poster URL and trailer URL and you want to store them into a single value separated by semicolon. First, you need to create a class that implements the IModelBinder
interface and overrides the BindModelAsync
method.
public class MovieBinder : IModelBinder | |
{ | |
public Task BindModelAsync(ModelBindingContext bindingContext) | |
{ | |
Movie movie = new Movie(); | |
var posterUrl = bindingContext.ValueProvider.GetValue("PosterUrl").FirstValue; | |
var trailerUrl = bindingContext.ValueProvider.GetValue("TrailerUrl").FirstValue; | |
var title = bindingContext.ValueProvider.GetValue("Title").FirstValue; | |
var director = bindingContext.ValueProvider.GetValue("Director").FirstValue; | |
var releaseDate = bindingContext.ValueProvider.GetValue("ReleaseDate").FirstValue; | |
var genre = bindingContext.ValueProvider.GetValue("Genre").FirstValue; | |
var duration = bindingContext.ValueProvider.GetValue("Duration").FirstValue; | |
movie.Title = title; | |
movie.Director = director; | |
if(releaseDate != null) | |
movie.ReleaseDate = DateOnly.Parse(releaseDate); | |
movie.Genre = genre; | |
movie.Duration = Convert.ToInt32(duration); | |
movie.Url = $"{posterUrl};{trailerUrl}"; | |
bindingContext.Result = ModelBindingResult.Success(movie); | |
return Task.CompletedTask; | |
} | |
} |
After you create your own model binder class, you need to decorate the parameter of your model parameter in the action method with the ModelBinder
attribute and pass your model as a type on the first argument.
[HttpPost] | |
[Route("Movies/Upload/")] | |
public IActionResult Upload([ModelBinder(typeof(MovieBinder))] Movie movie) | |
{ | |
//insert movie into database | |
return Ok(); | |
} |
You are successfully using your own custom model binder so you can have granular control over the rule of the binding process.
Conclusion
Model Binding Mechanism in ASP.NET helps us to populate data from requests with a breeze. It will perform automatically, and you don't have to write tedious code that could be error-prone. With the understanding behavior of model binding performed by ASP.NET you know how to handle it if something looks off or does not look as you expect it with the right technique. Even if the model binding by ASP.NET cannot meet your requirements, you can create your custom model binders by yourself and override the built-in model binding mechanism in ASP.NET.
Top comments (1)
Nice