I recently had requirement where I want to populate additional
values to the input passed on to the action method. I am using AngularJS as the
client which posts the data to the action method. In addition to what is passed
from the client, I want to have the developer certain fields present in the
model.
For AngularJS HttpPost, we use FromBody and BodyModelBinder
to bind the client data to the action method parameter. So I decided to enhance
the BodyModelBinder and populate these additional fields. Unfortunately
BodyModelBinder and its corresponding Provider does not allow extending the
existing Provider and Binder. We have to completely write our own Binder.
Instead of writing our own implementation of Provider and Binder, I decided to
create a wrapper that internally uses the default provider and binder.
Here is my code
Custom BodyModelBinderProvider
public class NtBodyModelBinderProvider : IModelBinderProvider
{
private readonly IList<IInputFormatter> formatters;
private readonly IHttpRequestStreamReaderFactory readerFactory;
private BodyModelBinderProvider defaultProvider;
public
NtBodyModelBinderProvider(IList<IInputFormatter> formatters, IHttpRequestStreamReaderFactory readerFactory)
{
this.formatters =
formatters;
this.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 NtBodyModelBinder(this.formatters, this.readerFactory);
}
return modelBinder;
}
}
Custom ModelBinder
public class NtBodyModelBinder : IModelBinder
{
private BodyModelBinder defaultBinder;
public NtBodyModelBinder(IList<IInputFormatter> formatters, IHttpRequestStreamReaderFactory readerFactory) // : base(formatters,
readerFactory)
{
defaultBinder = new BodyModelBinder(formatters,
readerFactory);
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
//
callinng the default body binder
await
defaultBinder.BindModelAsync(bindingContext);
if(bindingContext.Result.IsModelSet
&& bindingContext.Result.Model is IServiceDocument)
{
// populating the additional fields
// ....
// ....
// ....
}
}
}
Startup configuration
services.AddMvc()
.AddMvcOptions(options =>
{
IHttpRequestStreamReaderFactory readerFactory =
services.BuildServiceProvider().GetRequiredService<IHttpRequestStreamReaderFactory>();
options.ModelBinderProviders.Insert(0, new NtBodyModelBinderProvider(options.InputFormatters,
readerFactory));
});
Thank you for this...i have a very similar scenario and this fits perfect!
ReplyDeleteWe have to completely write our own Binder. Instead of writing our own implementation of Provider and Binder.
ReplyDeleteThank you mister - very useful.
ReplyDelete