15

When my application starts, I have a bunch of modules (module1, module2 …). For each of this module I have a bunch of controller actions :

/myModuleController/module1/action1
/myModuleController/module1/action2
/myModuleController/module2/action1
/myModuleController/module2/action2
…

As the user can log himself once per module, I deploy an authentication middleware per module, which is simply done this way :

app.UseWhen((context) => context.Request.Path.StartsWithSegments(urlPath), appbuilder =>
    {
        appbuilder.UseCookieAuthentication(new CookieAuthenticationOptions()
        {
            CookieName = cookieName,
            …
        });
    });

So basically, on the url path /myModuleController/module1 I have one middleware plus its cookie, another for /myModuleController/module2 … It’s a bit unusual I guess but it’s working fine, I’m happy with the behavior.

Here come the issue : I want to be able to add a new module at runtime, which would imply to be able to deploy a new middleware with a piece of code like app.UseWhen(url, app. UseCookieAuthentication(…)). I tried naively to inject IApplicationBuilder app in the controller responsible for the addition of the module, but I’m getting an exception:

System.InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Builder.IApplicationBuilder' while attempting to activate 'AdminController'

My question to you is : should it be working and I must have made a mistake somewhere? or, is it clear to you that what I’m trying here had not chance to work?

How would you have achieved the same requirement ? Thanks.

1
  • You are not going to be able to add new middleware after the startup code has been run. So you are better off determining what is different between the cookies for each module and then writing a more generic middleware that can handle all of your scenarios. Commented May 11, 2019 at 0:36

1 Answer 1

12

Firstly we need a service to keep the runtime middleware configurations

public class RuntimeMiddlewareService
{
    private Func<RequestDelegate, RequestDelegate> _middleware;

    private IApplicationBuilder _appBuilder;

    internal void Use(IApplicationBuilder app)
    => _appBuilder = app.Use(next => context => _middleware == null ? next(context) : _middleware(next)(context));

    public void Configure(Action<IApplicationBuilder> action)
    {
        var app = _appBuilder.New();
        action(app);
        _middleware = next => app.Use(_ => next).Build();
    }
}

Then add some extension methods to use it in Startup

public static class RuntimeMiddlewareExtensions
{
    public static IServiceCollection AddRuntimeMiddleware(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Singleton)
    {
        services.Add(new ServiceDescriptor(typeof(RuntimeMiddlewareService), typeof(RuntimeMiddlewareService), lifetime));
        return services;
    }

    public static IApplicationBuilder UseRuntimeMiddleware(this IApplicationBuilder app, Action<IApplicationBuilder> defaultAction = null)
    {
        var service = app.ApplicationServices.GetRequiredService<RuntimeMiddlewareService>();
        service.Use(app);
        if (defaultAction != null)
        {
            service.Configure(defaultAction);
        }
        return app;
    }
}

Then modify your Startup

Add to ConfigureServices:

services.AddRuntimeMiddleware(/* ServiceLifetime.Scoped could be specified here if needed */);

Add to where the runtime specified middlewares should be inside Configure.

app.UseRuntimeMiddleware(runtimeApp =>
{
    //runtimeApp.Use...
    //Configurations here could be replaced during the runtime.
});

Finally, you could reconfigure the runtime middlewares from other parts of the application

//var runtimeMiddlewareService = serviceProvider.GetRequiredService<RuntimeMiddlewareService>();
//Or resolved via constructor.
runtimeMiddlewareService.Configure(runtimeApp =>
{
    //runtimeApp.Use...
    //Configurations here would override the former ones.
});
Sign up to request clarification or add additional context in comments.

7 Comments

looks very promising. is that something you have running in prod? if so can you tell me which .net core runtime it is using? thank you.
I have tested it with .NET Core 3.0 preview6 and it worked perfectly. @Daboul
I'll give a try as soon as I can and comment back
Cool stuff! This helped me a lot when wanting to add UseStaticFiles middleware for dynamically loaded resources (plugins). @Alsein - though there is one null reference bug that can happen if no default action was supplied. So the Use method will have to check if _middleware is null, and if so return next(context)
Answered my own question, seems you need a lock statement around app.Use(_ => next).Build();, see my answer here for more details: stackoverflow.com/a/63944167/402706
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.