top of page

Custom Model Binding in ASP.NET Core: A Comprehensive Guide

In the ever-evolving world of web development, ASP.NET Core stands as a robust framework for building dynamic web applications. One of its powerful features is Model Binding, a mechanism that automatically maps client request data to action method parameters. While the default model binding works seamlessly for most scenarios, there are instances where developers need more control over this process. This is where Custom Model Binding comes into play.


Custom Model Binding in ASP.NET Core

In this comprehensive guide, we will learn the concept of Custom Model Binding in ASP.NET Core. We’ll start by understanding the basics of model binding, explore when and why you might need to implement custom model binding, and then walk you through a step-by-step tutorial to create your custom model binder.


What is Model Binding?

ASP.NET Core's Model Binding is a powerful feature that simplifies web development by automatically mapping data from client requests to parameters in your action methods. This eliminates manual parsing and conversion of raw HTTP request data, allowing you to focus on core application logic.


Here's how Model Binding streamlines development:

  • Data Mapping: Model Binding bridges the gap between client requests (containing data in various formats like form values, query strings, route data, request body, or HTTP headers) and your action methods. It automatically maps this incoming data to the corresponding strongly typed .NET object parameters defined in your action methods.

  • Strongly-Typed .NET Objects: By leveraging Model Binding, you can work directly with strongly-typed .NET objects within your action methods. This enhances code readability, maintainability, and type safety compared to handling raw strings or dictionaries.

  • Abstraction from Raw Data: Model Binding abstracts away the complexities of dealing with raw HTTP request data formats. You no longer need to write manual parsing or conversion logic, freeing you to concentrate on the core functionality of your action methods.


Model Binding is a hidden orchestrator, seamlessly transforming client request data into usable .NET objects within your ASP.NET Core applications. This saves developers time and effort, making their code cleaner and easier to manage.


Limitations of Default Model Binding

ASP.NET Core's default model binding offers flexibility and support for most common .NET Core data types. However, there are situations where you might need to create a custom model binder in ASP.NET Core for more granular control.


Here are some of the limitations of default Model Binding:


Limitation 1: Custom Sources or Formats:

The default model binders typically handle text-based input directly from the request (e.g., form fields, query strings) and map them to model properties.


You need a custom model binder in ASP.NET Core if your data originates from custom sources (like encrypted data in the request body) or requires a custom format not natively supported (e.g., specific header structures).


Limitation 2: Large Files or Streams:

Default model binding is not suited for handling large files or data streams. For such scenarios, a custom model binder can provide more control over handling large data efficiently.


Limitation 3: Binding Data from Multiple Sources:

When building complex objects, you might need to collect data from multiple sources within the request (e.g., form fields, query string, and route data).


A custom model binder allows you to combine data from various sources into a single object.


You can tailor data binding behavior to your specific application requirements by implementing custom model binders, This empowers developers to handle complex data scenarios and achieve a more robust and flexible architecture.


Introduction to Custom Model Binding

Custom Model Binding in ASP.NET Core is a mechanism that allows developers to control how data from HTTP requests is bound to action method parameters. When a client requests a Web API, data can be sent in various parts of the request, such as in the URL, query string, headers, or body. The ASP.NET Core model binding system automatically maps this data to the parameters of the controller action methods.


When to Create a Custom Model Binder?

You would typically create a custom model binder in ASP.NET Core when the default model binding behavior does not meet the specific requirements of your application. Here are some scenarios where a custom model binder might be needed:

  • When binding from custom sources or in a custom format not supported out of the box (e.g., encrypted data in a request body or custom header formats).

  • When handling large files or streams in a specialized manner.

  • When binding data from multiple sources into a single complex object.


Steps to Create Custom Model Binding in ASP.NET Core

STEP 1: Custom Model Binder Creation

The first step is to create a custom model binder that converts incoming request data into strongly typed key arguments, fetches the associated entity using Entity Framework Core, and passes the entity as an argument to the action method.


STEP 2: ModelBinder Attribute

The ModelBinder attribute is used on the Author model to specify the type of IModelBinder that should bind Author action parameters.

using CustomModelBindingSample.Binders;
using Microsoft.AspNetCore.Mvc;

namespace CustomModelBindingSample.Data
{
	[ModelBinder(BinderType = typeof(AuthorEntityBinder))]
	public class Author
	{
    		public int Id { get; set; }
    		public string Name { get; set; }
    		public string GitHub { get; set; }
    		public string Twitter { get; set; }
    		public string BlogUrl { get; set; }
	}
}

STEP 3: AuthorEntityBinder Class

The AuthorEntityBinder class binds an Author parameter by fetching the entity from a data source using Entity Framework Core and an authorId.

public class AuthorEntityBinder : IModelBinder
{
    private readonly AuthorContext _context;

    public AuthorEntityBinder(AuthorContext context)
    {
        _context = context;
    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
	  # Binding Logic 
       if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }

        var modelName = bindingContext.ModelName;

        // Try to fetch the value of the argument by name
        var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);

        if (valueProviderResult == ValueProviderResult.None)
        {
            return Task.CompletedTask;
        }

        bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);

        var value = valueProviderResult.FirstValue;

        // Check if the argument value is null or empty
        if (string.IsNullOrEmpty(value))
        {
            return Task.CompletedTask;
        }

        if (!int.TryParse(value, out var id))
        {
            // Non-integer arguments result in model state errors
            bindingContext.ModelState.TryAddModelError(
                modelName, "Author Id must be an integer.");

            return Task.CompletedTask;
        }

        // Model will be null if not found, including for
        // out of range id values (0, -3, etc.)
        var model = _context.Authors.Find(id);
        bindingContext.Result = ModelBindingResult.Success(model);
        return Task.CompletedTask;
    }
}

In the above code,

  • The AuthorEntityBinder constructor takes an AuthorContext object, a database context for accessing Author data.

  • The BindModelAsync method is the core of the model binding process.

  • It first checks if the bindingContext is null, and if so, throws an ArgumentNullException.

  • It then tries to fetch the value of the ValueProvider argument.

  • If no value is found, it returns a completed task.

  • It sets the model state with the fetched value.

  • If the fetched value is null or empty, it returns a completed task.

  • It tries to parse the fetched value as an integer. If it fails, it adds a model state error indicating that the “Author ID must be an integer” returns a completed task.

  • It then uses the parsed integer to find the corresponding Author in the database.

  • If an Author is found, it sets the binding result to success with the found Author.

  • Finally, it returns a completed task.



STEP 4: Using the Custom Model Binder

The custom model binder can be used in an action method. If the Author is null, it returns a NotFound result. Otherwise, it returns an OK result with the Author.

[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
    if (author == null)
    {
        return NotFound();
    }

    return Ok(author);
}

STEP 5: Applying the ModelBinder Attribute

The ModelBinder attribute can be used to apply the AuthorEntityBinder to parameters that don’t use default conventions.

[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
    if (author == null)
    {
        return NotFound();
    }

    return Ok(author);
}

Another Method to Implement Custom Model Binding

Instead of applying an attribute, you can implement IModelBinderProvider. IModelBinderProvider is an interface in ASP.NET Core that is used to create and provide model binders based on certain conditions. It allows more granular control over the model binding process, determining when your custom binder should be used based on the context.


The IModelBinderProvider interface requires the GetBinder method, which returns an instance of IModelBinder. It is called by the framework while processing a request to select the appropriate model binder.


AuthorEntityBinderProvider Class:

This class implements the IModelBinderProvider interface and provides a custom model binder for the Author type.

using CustomModelBindingSample.Data;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System;

namespace CustomModelBindingSample.Binders
{
	public class AuthorEntityBinderProvider : IModelBinderProvider
	{
    		public IModelBinder GetBinder(ModelBinderProviderContext context)
    		{
        		if (context == null)
        		{
            		throw new ArgumentNullException(nameof(context));
        		}

        		if (context.Metadata.ModelType == typeof(Author))
        		{
           	return new BinderTypeModelBinder(typeof(AuthorEntityBinder));
        		}

        		return null;
    		}
	}
}

BinderTypeModelBinder:

The BinderTypeModelBinder is a factory for model binders and provides dependency injection (DI). The AuthorEntityBinder requires DI to access EF Core. Use BinderTypeModelBinder if your model binder requires services from DI.


Registering the Custom Model Binder Provider:

To use a custom model binder provider, add it in ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AuthorContext>(options => options.UseInMemoryDatabase("Authors"));

    services.AddControllers(options =>
    {
        options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
    });
}

Order of Evaluation:

When evaluating model binders, the collection of providers is examined in order. The first provider that returns a binder that matches the input model is used. Adding your provider to the end of the collection may result in a built-in model binder being called before your custom binder has a chance. In this example, the custom provider is added to the beginning of the collection to ensure it’s always used for Author action arguments


Difference between using Attributes and Providers for Custom Model Binding in ASP.NET Core

Factor

Attributes

Providers

Usage

Attributes like ModelBinder specify a custom model binder for a specific model type or action parameter.

Providers like IModelBinderProvider are used to create and provide model binders based on certain conditions.

Flexibility

They offer less flexibility as they are directly associated with a specific model or action parameter.

They offer more flexibility as they can decide which model binder to use based on the context.

Scope

They are suitable for scenarios where a specific model binder is used for a particular model or action parameter.

They are suitable for scenarios where a specific model binder is used across multiple models or action parameters.

Example

[ModelBinder(BinderType = typeof(AuthorEntityBinder))] applied on a model or action parameter.

Implementation of IModelBinderProvider interface and its GetBinder method.


Conclusion

Understanding and implementing custom model binding in ASP.NET Core is a powerful skill that enhances your web development capabilities. It provides you with the flexibility to handle complex scenarios that are not covered by the default model binding. Whether using attributes or implementing IModelBinderProvider, custom model binding allows you to control how data from HTTP requests is bound to action method parameters.


Remember, while the built-in model binders in ASP.NET Core are robust and cater to most needs, don’t hesitate to create custom binders when your application has specific requirements.


Happy coding! 😊

bottom of page