Custom Model Binding In ASP.NET Core 2.0

Problem

How to implement custom model binders in ASP.NET Core.

Description

In an earlier post, I discussed how to prevent insecure object references by encrypting the internal references (e.g. table primary keys) using Data Protection API. To avoid duplication of code that encrypts/decrypts on every controller, I used filters in that example. In this post, I’ll use another complimentary technique: custom model binding.

What we want to achieve is,

  1. Encrypt data going out to views; using result filters.
  2. Decrypt as it comes back to controllers; using custom model binding.

Note

if you’re new to Model BindingData Protection API or Filters, please read earlier posts first.

Solution

Create a marker interface and attribute to flag properties on your model as protected, i.e., require encryption/decryption.

Create a custom model binder by implementing IModelBinder interface.

Create a custom model binder provider by implementing IModelBinderProvider interface.

Add your custom model binder provider to MVC services in Startup class.

Add your [ProtectedId] attribute to models.

Add a controller to use the models.

Views will show encrypted identifiers.

But our model binder will decrypt it.


Discussion

As we discussed in the previous post Model Binding is the mechanism through which ASP.NET Core maps HTTP requests to our models. In order to achieve this mapping, the framework will go through a list of providers that will indicate whether they can handle the mapping or not. If they can, they will return a binder that is responsible for actually doing the mapping.

Model Binding

In order to tell if the framework that we need needs some bespoke mapping; i.e., decryption of incoming data, we create our own provider and binder.

Model Binder Provider will decide when our custom binder is needed. In our solution, the provider is simply checking the existence of our attribute/interface on the model property and if it exists, it will return our custom binder.

Note

BinderTypeModelBinder is used here since our custom binder has dependencies it needs at runtime. Otherwise, you could just return an instance of your custom binder.

Model Binder will actually do the mapping (i.e. decryption in our case) of incoming data. In our solution we get the value being passed, decrypt it using Data Protection API and set the decrypted value as the binding result.

Result Filter

In order to automatically encrypt the properties of our model, I wrote a simple result filter.

This filter is added globally in Startup class.

Source Code