Custom Model Binding In ASP.NET Core MVC

Introduction
 
Model binding in MVC maps HTTP request data to the parameters of the Controller's action method. The parameter may either be of a simple type like integers, strings, double etc. or complex types. MVC binds the request data to the action parameter by parameter name.
 
Model binder provides mapping between request data and application model. The default model binder provided by ASP.NET Core MVC supports most of the common data types and would meet most of our needs. We can extend the built-in model binding functionality by implementing custom model binders and can transform the input prior to binding it.
 
To create custom model binder class, it needs to inherit from IModelBinder interface. This interface has async method named "BindModelAsync" and it has parameter of type ModelBindingContext. The ModelBindingContext class provides the context that model binder functions.
  1. using Microsoft.AspNetCore.Mvc.ModelBinding;  
  2. using System;  
  3. using System.Threading.Tasks;  
  4.   
  5. namespace ModelBinder.ModelBinder  
  6. {  
  7.     public class CustomModelBinder : IModelBinder  
  8.     {  
  9.         public Task BindModelAsync(ModelBindingContext bindingContext)  
  10.         {  
  11.             throw new NotImplementedException();  
  12.         }  
  13.     }  
  14. }  
Example
 
In this example, we will implement a custom model binder that convert incoming request data (that passed as query string) to user define class. The request data contain all model properties with pipe (|) separator and our custom model binder will separate the data and assign them to model property.
 
The first step is to create custom model binder.
  1. namespace ModelBinder  
  2. {  
  3.     using Microsoft.AspNetCore.Mvc.ModelBinding;  
  4.     using ModelBinder.Model;  
  5.     using System;  
  6.     using System.Threading.Tasks;  
  7.     public class CustomModelBinder : IModelBinder  
  8.     {  
  9.         public Task BindModelAsync(ModelBindingContext bindingContext)  
  10.         {  
  11.             if (bindingContext == null)  
  12.                 throw new ArgumentNullException(nameof(bindingContext));  
  13.   
  14.             var values = bindingContext.ValueProvider.GetValue("Value");  
  15.             if (values.Length == 0)  
  16.                 return Task.CompletedTask;  
  17.   
  18.             var splitData = values.FirstValue.Split(new char[] { '|' });  
  19.             if (splitData.Length >= 2)  
  20.             {  
  21.                 var result = new User  
  22.                 {  
  23.                     Id = Convert.ToInt32(splitData[0]),  
  24.                     Name = splitData[1]  
  25.                 };  
  26.                 bindingContext.Result = ModelBindingResult.Success(result);  
  27.             }  
  28.   
  29.             return Task.CompletedTask;  
  30.         }  
  31.     }  
  32. }  
Once the model is created from the request data, we need to assign this model to Result property of binding context using ModelBindingResult.Success method. This method representing a successful model binding operation. Same as Success Method, It has also method name “Failed” it represent a fail model binding operation.
 
Now, the next step is to register Model binder. We have two ways to register Model binder:
  1. Using ModelBinder attribute
  2. By defining model binder provider and register in startup class
Register custom model binder using ModelBinder Attribute
 
We can apply custom model binder using ModelBinder attribute by defining attributes on action method or model. If we are using this method (applying attribute on action method), we need to define this attribute on every action methods those want use this custom binding. We can also apply this attribute on model it-self.
 
Applying ModelBinder Attribute on Model
  1. namespace ModelBinder.Model  
  2. {  
  3.     using Microsoft.AspNetCore.Mvc;  
  4.           
  5.     [ModelBinder(BinderType = typeof(CustomModelBinder))]  
  6.     public class User  
  7.     {  
  8.         public int Id { getset; }  
  9.         public string Name { getset; }  
  10.         public string Address { getset; }  
  11.     }  
  12. }  
Applying ModelBinding Attribute on Action method
  1. [HttpGet]  
  2. [Route("test")]  
  3. public IActionResult Index([ModelBinder(BinderType = typeof(CustomModelBinder))]User u)  
  4. {  
  5.   
  6.     return View();  
  7. }  
Register custom Model binder in startup class
 
We can also register our custom model binder in startup class that's available for all s action methods. To register custom model binder, we need to create a binder provider. The model binder provider class implement IModelBinderProvider interface. The all built-in model binders have their own model binder providers. We can also specify the type of argument model binder produces, not the input of our model binder. In following example, provider is only work with "CustomModelBinder".
 
Custom Model binder provider
  1. namespace ModelBinder  
  2. {  
  3.     using Microsoft.AspNetCore.Mvc.ModelBinding;  
  4.     using ModelBinder.Model;  
  5.   
  6.     public class CustomModelBinderProvider : IModelBinderProvider  
  7.     {  
  8.         public IModelBinder GetBinder(ModelBinderProviderContext context)  
  9.         {  
  10.             if (context.Metadata.ModelType == typeof(User))  
  11.                 return new CustomModelBinder();  
  12.   
  13.             return null;  
  14.         }  
  15.     }  
  16. }  
Now, we need to add this provider to MVC model binder provider collection. We can add custom model binder provider to MVC model binder collection in ConfigureServices methods of Startup class.
  1. public void ConfigureServices(IServiceCollection services)  
  2. {  
  3.     // Add framework services.  
  4.     services.AddMvc(  
  5.         config => config.ModelBinderProviders.Insert(0, new CustomModelBinderProvider())  
  6.     );  
  7. }  
Output

ASP.NET Core
 
In the above example, we are reading the required data from request (query string). In the same way  we can also read the data from request body. With the post method, we need to post the data within request body. In the following example, I have read the request body data and converted it in to required form.
 
Model Binder
  1. namespace ModelBinder  
  2. {  
  3.     using Microsoft.AspNetCore.Mvc.ModelBinding;  
  4.     using ModelBinder.Model;  
  5.     using Newtonsoft.Json.Linq;  
  6.     using System;  
  7.     using System.IO;  
  8.     using System.Threading.Tasks;  
  9.   
  10.     public class CustomModelBinder1 : IModelBinder  
  11.     {  
  12.         public Task BindModelAsync(ModelBindingContext bindingContext)  
  13.         {  
  14.             if (bindingContext == null)  
  15.                 throw new ArgumentNullException(nameof(bindingContext));  
  16.   
  17.             string valueFromBody = string.Empty;  
  18.   
  19.             using (var sr = new StreamReader(bindingContext.HttpContext.Request.Body))  
  20.             {  
  21.                 valueFromBody = sr.ReadToEnd();  
  22.             }  
  23.   
  24.             if (string.IsNullOrEmpty(valueFromBody))  
  25.             {  
  26.                 return Task.CompletedTask;  
  27.             }  
  28.   
  29.             string values = Convert.ToString(((JValue)JObject.Parse(valueFromBody)["value"]).Value);  
  30.   
  31.             var splitData = values.Split(new char[] { '|' });  
  32.             if (splitData.Length >= 2)  
  33.             {  
  34.                 var result = new User1  
  35.                 {  
  36.                     Id = Convert.ToInt32(splitData[0]),  
  37.                     Name = splitData[1]  
  38.                 };  
  39.                 bindingContext.Result = ModelBindingResult.Success(result);  
  40.             }  
  41.   
  42.             return Task.CompletedTask;  
  43.         }  
  44.     }  
  45. }  
Output

ASP.NET Core
 
Summary

ASP.NET Core has many built-in model binders and their providers that meet our most all needs. But custom model binder provides a way to bind our data which is in specific format to our model classes or action parameter.
 
You can view and download source code from following link