Injecting Service in Middleware in ASP.NET Core
Asked Answered
J

3

10

I want to inject a service based on the HTTP header value. So I have 2 classes - DbDataProvider and InMemDataProvider, both are implemented from IDataProvider. Whenever an API call is made, a header is passed by the client which determines whether DbDataProvider is required or InMemDataProvider is required. How do I achieve that? So in short I need to inject service in the ServiceCollection in one of the middlewares. Is that possible?

The problem is that in the ConfigureService method in Startup class I cannot get the HttpContext. I have written a middleware using which I am able to get the HTTP Context but how do I inject a Service there?

Joliejoliet answered 3/7, 2016 at 21:26 Comment(1)
Prevent injecting services based on runtime conditions, just as you should not inject runtime data itself into your application components. Instead have a composite component that gets injected with both implementations and forward the call to the implementation at runtime. Factories are hardly ever the right solution.Dahomey
L
13

There is no easy or clean way to do this. You can't modify the IServiceCollection outside of the ConfigureServices method. But even if you could, it's of no use, because the container has already been built before Configure is being called.

What you could do is create a factory class and register it as scoped.

public interface IDataProviderFactory
{
    bool UseInMemoryProvider { get; set; }
    IDataProvider Create();
}

public class DataProviderFactory : IDataProviderFactory
{
    private readonly IServiceProvider provider;

    public bool UseInMemoryProvider { get; set; }

    public DataProviderFactory(IServiceProvider provider) 
    {
        this.provider = provider;
    }

    public IDataProvider Create()
    {
        if(UseInMemoryProvider) 
        {
            return provider.RequestService<InMemoryDataProvider>();
        }

        return provider.RequestService<DbDataProvider>();
    }
}

Then in your middleware:

public class MyMiddleware
{
    public void Invoke(HttpContext context) 
    {
        var dataProviderFactory = context.RequestServices.RequestService<IDataProviderFactory>();

        // Your logic here
        if(...)
        {
            dataProviderFactory.UseInMemoryStore = true;
        } 
    }
}

and in your controller/services:

public class MyController : Controller 
{
    private readonly IDataProvider dataProvider;

    public MyController(IDataProviderFactory dataProviderFactory)
    {
        dataProvider = dataProviderFactory.Create();
    }
}
Locative answered 4/7, 2016 at 0:33 Comment(1)
IServiceProvider.RequestService<> doest not exist (anymore ?) whereas GetService<> does.Shirashirah
R
6

You can achieve this in your DI config in Startup.cs.

They key is services.AddHttpContextAccessor() which allows you to get access to the HttpContext.

services.AddHttpContextAccessor();

services.AddScoped<DbDataProvider>();
services.AddScoped<InMemDataProvider>();
services.AddScoped<IDataProvider>(ctx =>
{
    var contextAccessor = ctx.GetService<IHttpContextAccessor>();
    var httpContext = contextAccessor.HttpContext;

    // Whatever the header is that you are looking for
    if (httpContext.Request.Headers.TryGetValue("Synthetic", out var syth))
    {
        return ctx.GetService<InMemDataProvider>();
    }
    else
    {
        return ctx.GetService<DbDataProvider>();
    }
});
Reshape answered 17/4, 2019 at 9:54 Comment(0)
K
2

The answer above from Tsen is correct. You should implement a factory.

But in addition you can also register factory methods to the services collection. Like so:

Services.AddTransient(serviceProvider => serviceProvider.GetService<IDataProviderFactory>().Create())

This registers your IDataProvider. In the Create you should evaluate that HTTP header value so it returns the correct IDataProvider instance. Then in any class you need it you can simply request IDataProvider via the constructor and the correct implementation will be provided by the container.

Kiblah answered 4/7, 2016 at 6:30 Comment(2)
This will result in always returning the same instance, since it's singleton ;) Another issue with it in this place is that you don't have direct access to HttpContext and IHttpContextAccessor isn't registered by default (for performance reasons), since creating it on each request has a little overhead and if his other services doesn't require HttpContext outside of a controller he shouldn't just register it for that one single reason. github.com/aspnet/Announcements/issues/190Locative
I mean transient (updated answer). And we've registered IHttpContextAccessor manually indeed. To each their own I'd say.Kiblah

© 2022 - 2024 — McMap. All rights reserved.