Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use custom model binder that supports dependency injection in ASP.NET Core?

I am trying to use a custom model binder in MVC that I want resolved from my IoC container. The issue I am having is that I can't access my container while I am adding the MVC service, because my container isn't built yet (and I need to add MVC before building my container). Feels like a chicken/egg issue, and I am sure I am missing a simple solution.

Example:

services.AddMvc().AddMvcOptions(options =>
{
     options.ModelBinders.Add(serviceProvider.Resolve<CustomModelBinder>());
});

My custom model binder looks like this:

public class CustomModelBinder : IModelBinder
{
    private IServiceProvider serviceProvider;

    public CustomModelBinder(IServiceProvider serviceProvider)
    {
        this.serviceProvider = serviceProvider;
    }

    public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
    {
        var model = serviceProvider.GetService(bindingContext.ModelType);
        bindingContext.Model = model;

        var binder = new GenericModelBinder();
        return binder.BindModelAsync(bindingContext);
    }
}
like image 356
Jeremy Armstrong Avatar asked Feb 26 '16 00:02

Jeremy Armstrong


People also ask

What interface and method needs to be implemented for a custom model binder?

For creating a custom model binder class, we have to inherit from IModelBinder interface. This interface has an async method named "BindModelAsync" and it has a parameter of type ModelBindingContext. The ModelBindingContext class provides the context that model on which the binder acts.

What is a correct use case for creating a custom model binder in ASP.NET Core?

They expect to bind text-based input from the request directly to model types. You might need to transform the input prior to binding it. For example, when you have a key that can be used to look up model data. You can use a custom model binder to fetch data based on the key.


3 Answers

Per the post here: https://github.com/aspnet/Mvc/issues/4167

To answer your question directly, use:

bindingContext.OperationBindingContext.ActionContext.HttpContext.RequestServices

On a side note, you also have the option of using [FromServices] to resolve it for you.

like image 59
Serj Sagan Avatar answered Nov 08 '22 07:11

Serj Sagan


Instead of using a service locator pattern where the required service is retrieved in the model binder you can use IConfigureOptions to configure options with dependency injection. This allows you to delay the configuration of options until the dependency injection container has been built.

Here is a model binder having a dependency on ICustomService:

class CustomModelBinder : IModelBinder
{
    private readonly ICustomService customService;

    public CustomModelBinder(ICustomService customService) => this.customService = customService;

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        // Use customService during model binding.
    }
}

You need a provider for this model binder:

class CustomModelBinderProvider : IModelBinderProvider
{
    private readonly ICustomService customService;

    public CustomModelBinderProvider(ICustomService customService) => this.customService = customService;

    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        // Return CustomModelBinder or null depending on context.
        return new CustomModelBinder(customService);
    }
}

Normally, you would add the model binder provider in Startup using code like this:

services.AddMvc().AddMvcOptions(options =>
{
    options.ModelBinderProviders.Add(new CustomModelBinderProvider(customService));
});

However, as you have noted this is not possible. You cannot resolve customService while the system is being configured. The general solution to this problem is to use IConfigureOptions with the above code to configure the options but with the added benefit of being able to use dependency injection:

class CustomModelBinderConfigureMvcOptions : IConfigureOptions<MvcOptions>
{
    private readonly ICustomService customService;

    public CustomModelBinderConfigureMvcOptions(ICustomService customService) => this.customService = customService;

    public void Configure(MvcOptions options)
        => options.ModelBinderProviders.Add(new CustomModelBinderProvider(customService));
}

This class has to be added to the container in Startup

services.AddSingleton<IConfigureOptions<MvcOptions>, CustomModelBinderConfigureMvcOptions>();

You are now able to use a model binder with a dependency.

like image 25
Martin Liversage Avatar answered Nov 08 '22 06:11

Martin Liversage


For ASP.NET Core 2.2, this should work for DI:

public class CustomModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        // dependency injection
        var model = bindingContext.HttpContext.RequestServices.GetService(bindingContext.ModelType);

        // other changes...

        bindingContext.Result = ModelBindingResult.Success(model);

        return Task.CompletedTask;
    }
}
like image 32
Ray Avatar answered Nov 08 '22 06:11

Ray