How To Bind Model Value FromBody by Custom Binder in Asp.net Core
Tags: aspdotnetcore, csharp, modelbinding
Published: 2019-05-30 07:37:57

I encountered a scenario a few days back where I was required to use a custom model binder to transform my received values in a FromBody JSON request. When I implemented the Model Binder It was working fine with FromQuery Attribute but was not working with FromBody, same issue as: https://github.com/aspnet/Mvc/issues/8110.

For in-depth learning, you can also follow my channel on YouTube [@usmancode](https://www.youtube.com/channel/UCIzpLqvz1diUOAFzL08zjqg?sub_confirmation=1)

So after spending some hours on this problem, I came to know that the FromBody attribute will be handled using **BodyModelBinder **class with Formatters and ReaderFactory.

public class MyModelBinder : IModelBinder
  {
      private BodyModelBinder defaultBinder;
      public MyModelBinder(IList<IInputFormatter> formatters, IHttpRequestStreamReaderFactory readerFactory)
      {
          defaultBinder = new BodyModelBinder(formatters, readerFactory);
      }
 
      public async Task BindModelAsync(ModelBindingContext bindingContext)
      {
          // calling the default body binder
          await defaultBinder.BindModelAsync(bindingContext);
          if (bindingContext.Result.IsModelSet && bindingContext.Result.Model is ResetPasswordModel)
          {
              var inputModel = (ResetPasswordModel)bindingContext.Result.Model;
 
              // all of your property updates with be listed here
              //
              //
              //
 
              bindingContext.Result = ModelBindingResult.Success(inputModel);
          }
      }
  }
And for this model binder, we will required a model provider which will be like :


public class MyModelBinderProvider : IModelBinderProvider
   {
       private readonly IList<IInputFormatter> _formatters;
       private readonly IHttpRequestStreamReaderFactory _readerFactory;
       private BodyModelBinderProvider _defaultProvider;
 
       public MyModelBinderProvider(IList<IInputFormatter> formatters, IHttpRequestStreamReaderFactory readerFactory)
       {
           _formatters = formatters;
           _readerFactory = readerFactory;
           _defaultProvider = new BodyModelBinderProvider(formatters, readerFactory);
       }
 
       public IModelBinder GetBinder(ModelBinderProviderContext context)
       {
           IModelBinder modelBinder = _defaultProvider.GetBinder(context);
 
           // default provider returns null when there is error.So for not null setting our binder
           if (modelBinder != null)
           {
               modelBinder = new ProtectedModelBinder(_formatters, _readerFactory);
           }
           return modelBinder;
       }
   }
And we need to register this provide in our startup class inside ConfigureServices method like :


services.AddMvc(config =>
           {
               var readerFactory = services.BuildServiceProvider().GetRequiredService<IHttpRequestStreamReaderFactory>();
               config.ModelBinderProviders.Insert(0, new MyModelBinderProvider(config.InputFormatters, readerFactory));        
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
And at last annotate the model you want to customize the binding of which with a line as below:

[ModelBinder(BinderType = typeof(MyModelBinder))]
public class ResetPasswordModel : UserConfirmationModel
And now if you make a http post api call with content type application/json with your model properties in request body then your custom model binding will work.

You can fork our github repository for all useful and examples code : https://github.com/Usman-uzi/techintalk

For all my blog posts you can visit : https:www.techintalk.com