asp.net-core.net-coredependency-injectionstackexchange.rediscookie-authentication

Set a custom SessionStore for ConfigureApplicationCookie without BuildServiceProvider()


I have a .NET Core 3 project (recently upgraded from 2.2) that uses a Redis distributed cache and cookie authentication.

It currently looks something like this:

public void ConfigureServices(IServiceCollection services)
{
    // Set up Redis distributed cache
    services.AddStackExchangeRedisCache(...);

    ...

    services.ConfigureApplicationCookie(options =>
    {
        ...
        // Get a service provider to get the distributed cache set up above
        var cache = services.BuildServiceProvider().GetService<IDistributedCache>();

         options.SessionStore = new MyCustomStore(cache, ...);
    }):
}

The problem is that BuildServiceProvider() causes a build error:

Startup.cs(...): warning ASP0000: Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.

This doesn't appear to be an option - ConfigureApplicationCookie is in Startup.ConfigureServices and can only configure new services, Startup.Configure can use the new services, but can't override CookieAuthenticationOptions.SessionStore to be my custom store.

I've tried adding services.AddSingleton<ITicketStore>(p => new MyCustomRedisStore(cache, ...)) before ConfigureApplicationCookie, but this is ignored.

Explicitly setting CookieAuthenticationOptions.SessionStore appears to be the only way to get it to use anything other than the local memory store.

Every example I've found online uses BuildServiceProvider();

Ideally I want to do something like:

services.ConfigureApplicationCookieStore(provider => 
{
    var cache = provider.GetService<IDistributedCache>();
    return new MyCustomStore(cache, ...);
});

Or

public void Configure(IApplicationBuilder app, ... IDistributedCache cache)
{
    app.UseApplicationCookieStore(new MyCustomStore(cache, ...));
}

And then CookieAuthenticationOptions.SessionStore should just use whatever I've configured there.

How do I make the application cookie use an injected store?


Solution

  • Reference Use DI services to configure options

    If all the dependencies of your custom store are injectable, then just register your store and required dependencies with the service collection and use DI services to configure options

    public void ConfigureServices(IServiceCollection services) {
        // Set up Redis distributed cache
        services.AddStackExchangeRedisCache(...);
    
        //register my custom store
        services.AddSingleton<ITicketStore, MyCustomRedisStore>();
    
        //...
    
        //Use DI services to configure options
        services.AddOptions<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme)
            .Configure<ITicketStore>((options, store) => {
                options.SessionStore = store;
            });
    
        services.ConfigureApplicationCookie(options => {
            //do nothing
        }):
    }
    

    If not then work around what is actually registered

    For example

    //Use DI services to configure options
    services.AddOptions<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme)
        .Configure<IDistributedCache>((options, cache) => {
            options.SessionStore = new MyCustomRedisStore(cache, ...);
        });
    

    Note:

    ConfigureApplicationCookie uses a named options instance. - @KirkLarkin

    public static IServiceCollection ConfigureApplicationCookie(this IServiceCollection services, Action<CookieAuthenticationOptions> configure)
            => services.Configure(IdentityConstants.ApplicationScheme, configure);
    

    The option would need to include the name when adding it to services.