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 encrypt/decrypt 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 our 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 our custom model binder provider to MVC services in Startup class:

Add our [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 request 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 the framework that we need 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 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

GitHub: https://github.com/TahirNaushad/Fiver.Mvc.ModelBinding.Custom

Leave a Reply