Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get a service in a IServiceCollection extension

I have this extension

public static class ServiceCollectionExtensions
{
    public static IServiceCollection MyExtension(this IServiceCollection serviceCollection)
    {
      ...
    }
}

and I need to get information from a service like this:

services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
    {
        var myService = <<HERE>>();
        options.TokenValidationParameters = this.GetTokenValidationParameters(myService);
    });

how can I do that?

I tried to get the ServiceProvider after var serviceProvider = services.BuildServiceProvider(); and then I send the serviceProvider, but this doesn't work..

like image 534
andres descalzo Avatar asked Oct 26 '17 19:10

andres descalzo


People also ask

Is there an extension method for iservicecollection?

The 5th part of this series was about Polly and this article is about writing extension method for IServiceCollection. Honestly not every Web API project needs a service collection extension and if you are ok with the long and messy Startup class, you can finish up reading this article.

Is it possible to create a service provider for the iservicecollection?

But if you really need to do so for the IServiceCollection, you can create a "Provider", such as: Show activity on this post. Most of the time, IServiceProvider should not be used directly, because it will result in the Service Locator antipattern instead of the Dependency Injection pattern.

What is the difference between add (iservicecollection) and removeall (servicedescriptor)?

Add(IServiceCollection, ServiceDescriptor) Adds the specified descriptorto the collection. Add(IServiceCollection, IEnumerable<ServiceDescriptor>) Adds a sequence of ServiceDescriptorto the collection. RemoveAll(IServiceCollection, Type) Removes all services of type serviceTypein IServiceCollection. RemoveAll<T>(IServiceCollection)

How do I add a transient service to an iservicecollection?

Adds a transient service of the type specified in TServicewith an implementation type specified in TImplementationto the specified IServiceCollection. AddTransient<TService,TImplementation>(IServiceCollection, Func<IServiceProvider,TImplementation>)


1 Answers

At the time you are calling services.AddSomething(), the service provider has not been built from the service collection yet. So you cannot instantiate a service at that time. Fortunately, there is a way to configure services while using dependency injection.

When you do services.AddSomething(options => …) what usually happens is that a certain amount of services will be registered with the service collection. And then the passed configuration action will also be registered in a special way, so that when the service is later instantiated, it will be able to execute that configuration action in order to apply the configuration.

For that, you need to implement IConfigureOptions<TOptions> (or actually IConfigureNamedOptions<TOptions> for authentication options) and register it as a singleton. For your purpose, this could look like this:

public class ConfigureJwtBearerOptions : IConfigureNamedOptions<JwtBearerOptions>
{
    private readonly IMyService _myService;

    public ConfigureJwtBearerOptions(IMyService myService)
    {
        // ConfigureJwtBearerOptionsis constructed from DI, so we can inject anything here
        _myService = myService;
    }

    public void Configure(string name, JwtBearerOptions options)
    {
        // check that we are currently configuring the options for the correct scheme
        if (name == JwtBearerDefaults.AuthenticationScheme)
        {
            options.TokenValidationParameters = myService.GetTokenValidationParameters();
        }
    }

    public void Configure(JwtBearerOptions options)
    {
        // default case: no scheme name was specified
        Configure(string.Empty, options);
    }
}

You then register that type in your Startup:

services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    // add JwtBearer but no need to pass options here
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, configureOptions: null);

// instead we are registering our configuration type to configure it later
services.AddSingleton<IConfigureOptions<JwtBearerOptions>, ConfigureJwtBearerOptions>();

This is actually the exact same thing that happens when you just do services.AddJwtBearer(scheme, options => { … }), just abstracted away, so you don’t need to care about it. But by doing it manually, you now have more power and access to the full dependency injection service provider.

like image 111
poke Avatar answered Oct 28 '22 09:10

poke