Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Installing a new middleware at runtime in ASP.Net Core

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.

like image 636
Daboul Avatar asked Feb 21 '17 11:02

Daboul


1 Answers

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.
});
like image 60
Alsein Avatar answered Nov 12 '22 08:11

Alsein