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,
- Encrypt data going out to views; using result filters.
- Decrypt as it comes back to controllers; using custom model binding.
Note
if you’re new to Model Binding, Data 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.
- public interface IProtectedIdAttribute { }
- public class ProtectedIdAttribute
- : Attribute, IProtectedIdAttribute { }
Create a custom model binder by implementing IModelBinder interface.
- public class ProtectedIdModelBinder : IModelBinder
- {
- private readonly IDataProtector protector;
- public ProtectedIdModelBinder(IDataProtectionProvider provider)
- {
- this.protector = provider.CreateProtector("protect_my_query_string");
- }
- public Task BindModelAsync(ModelBindingContext bindingContext)
- {
- var valueProviderResult =
- bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
- if (valueProviderResult == ValueProviderResult.None)
- return Task.CompletedTask;
- bindingContext.ModelState.SetModelValue(
- bindingContext.ModelName, valueProviderResult);
- var result = this.protector.Unprotect(valueProviderResult.FirstValue);
- bindingContext.Result = ModelBindingResult.Success(result);
- return Task.CompletedTask;
- }
- }
Create a custom model binder provider by implementing IModelBinderProvider interface.
- public class ProtectedIdModelBinderProvider : IModelBinderProvider
- {
- public IModelBinder GetBinder(ModelBinderProviderContext context)
- {
- if (context.Metadata.IsComplexType) return null;
- var propName = context.Metadata.PropertyName;
- if (propName == null) return null;
- var propInfo = context.Metadata.ContainerType.GetProperty(propName);
- if (propInfo == null) return null;
- var attribute = propInfo.GetCustomAttributes(
- typeof(IProtectedIdAttribute), false).FirstOrDefault();
- if (attribute == null) return null;
- return new BinderTypeModelBinder(typeof(ProtectedIdModelBinder));
- }
- }
Add your custom model binder provider to MVC services in Startup class.
- public void ConfigureServices(
- IServiceCollection services)
- {
- services.AddDataProtection();
- services.AddMvc(options =>
- {
- options.ModelBinderProviders.Insert(
- 0, new ProtectedIdModelBinderProvider());
- });
- }
Add your [ProtectedId] attribute to models.
- public class MovieInputModel
- {
- [ProtectedId]
- public string Id { get; set; }
- }
- public class MovieViewModel
- {
- [ProtectedId]
- public string Id { get; set; }
- public string Title { get; set; }
- public int ReleaseYear { get; set; }
- public string Summary { get; set; }
- }


Join the conversation! Your thoughts help the community grow.