For programmers, testers and tech geeks

Custom Model Binding : How To Bind Model Value FromBody by Custom Binder in Asp.net Core

I encountered a scenario few days back where I was required to use custom model binder to transform my received values in 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 . So after spending some hours on this problem I came to know that the FromBody attribute will be handled using BodyModelBinder class with uses 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