Look into IStartupFilter in ASP.NET Core

Hello! We will Look into IStartupFilter in ASP.NET Core, usually, it is an unknown interface in ASP.NET Core, but hey… it is out there and it is worth knowing it a bit. Let’s take a peek!

Middlewares

At this point, I’m going to assume that we all know the middleware concept of asp.net core, right? If someone does not know it, this post by Jose Maria Aguilar is a must read.

Well, that … now we know that the middlewares in ASP.NET Core are in charge of processing the requests. The request travels through all the middlewares and each of them can perform different tasks (including modifying the request or the HTTP context) and then either pass the request to the next middleware or short-circuit and return a response … Response traveling again by the chain of middlewares (in reverse order) where again it can be processed by each of them until it is sent to the browser.

Let’s see an example of middleware, which allows us to short-circuit any request and send a 500 or do nothing. This would allow us to easily put a service “in failure mode” to test different casuistry. In GitHub is the entire middleware code, in case you want to keep an eye on it. There are three files. The first is FailingMiddleware.cs which contains the middleware itself. This middleware has a variable ( _mustFail ) and if that variable value true, the middleware short-circuits all the requests and sends a 500:

public async Task Invoke(HttpContext context)
{
    var path = context Request.Path;
    if (path.Equals(_options.Configuration, StringComparison.OrdinalIgnoreCase))
    {
        await ProcessConfigRequest(context);
        return;
    }
    if (_fail)
    {
        context Response StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
        context Response ContentType = "text/plain";
        await context.Response WriteAsync("Not able to proceed");
    }
    else
    {
        await _next Invoke(context);
    }
}

You can see that if _fail is true a 500 is sent and if not, then the request is passed to the next middleware. Of course, an endpoint that is set(in _options.ConfigPath ) allows you to enable and disable this middleware.

The file FailingMiddlewareAppBuilderExtensions.cs contains the extension methods that allow us to “plug in” this middleware in an easy way:

public static IApplicationBuilder UseFailingMiddleware(this IApplicationBuilder builder)
{
    return UseFailingMiddleware(builder, null);
}
public static IApplicationBuilder UseFailingMiddleware(this IApplicationBuilder builder, Action<FailingOptions> action)
{
    var options = new FailingOptions();
    action? Invoke(options);
    builder UseMiddleware<FailingMiddleware>(options);
    return builder;
}

The last file is FailingOptions.cs which contains the FailingOptions class that is used to configure the middleware (through the extension method) in a way like the following (and indicate the endpoint to configure the middleware):

app UseFailingMiddleware(options => 
{
    options ConfigPath = "/failure";
});

If we plug this middleware before any other (and enable it by calling its endpoint) any other request will always return a 500 (until we disable it again).

IStartupFilter

Ok, let’s see now what role this interface plays. We will start with the definition:

namespace Microsoft.AspNetCore.Hosting
{
    public interface IStartupFilter
    {
        Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next);
    }
}

It has a single method that takes an Action <IApplicationBuilder> and returns an Action <IApplicationBuilder>. Okay, that does not tell us much, but the idea is this: a startup filter is an element that allows us to do “something” (whatever we want) with the IApplicationBuilder even before the Startup code is executed.

We usually think that the entry point of an ASP.NET Core application is the Startup class. That perception surely comes from the times of OWIN, where that was so (the code that called Startup was part of the framework and we did not have it in our project) but in ASP.NET Core that is false. In fact, ASP.NET Core applications are basically console applications and if you search in your project you will find the Program.cs file:

public class Program
{
    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();

        host.Run();
    }
}

This is the true entry point of an ASP.NET Core application. Do you see the “UseStartup” method? Well that is the method that gives control to the Startup class that we have in our project. Notice that really, in addition to the pipeline of middlewares that we have in Startup, there is “another pipeline” built on WebHostBuilder . This other pipeline is what we establish using IStartupFilter. The difference is that it is not a pipeline of middlewares but a pipeline of … configurations

Let’s see an example of that. For this we are going to create a startup filter:

public class FailingStartupFilter : IStartupFilter
{
    private readonly string _path;
    public FailingStartupFilter(string path)
    {
        _path = path;
    }

    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        return app =>
        {
            app.UseFailingMiddleware(options =>
            {
                options.ConfigPath = _path;
            });
            next(app);
        };
    }
}

Okay, we can see how simple it is. In the end we simply have to place the code in the Configure method. The key is to understand that this Configure method does the same as the Configure method of the Startup class . In this case, what we do is add the FailingMiddleware that we have seen before, but we could add several. This is one of the usual uses of startup filters: add N middlewares that all go together, which can be useful if you develop libraries.

Now we need to see how we invoke it. Well, it’s very simple, for that we created an extension method on IWebHostBuilder:

public static class WebHostBuildertExtensions
{
    public static IWebHostBuilder UseFailing(this IWebHostBuilder builder, string path)
    {
        builder.ConfigureServices(services =>
        {
            services.AddSingleton<IStartupFilter>(new FailingStartupFilter(path));
        });
        return builder;
    }

}

Basically, we register the filter as a singleton, with the type IStartupFilter (note that here you could register more elements if you wanted). And now, of course, you can add the filter in Program.cs:

public static void Main(string[] args)
{
    var host = new WebHostBuilder()
        .UseKestrel()
        .UseFailing("/failing")
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseIISIntegration()
        .UseStartup<Startup>()
        .UseApplicationInsights()
        .Build();

    host.Run();
}

Notice how we have added it after the “UseKestrel”. Then add the FailingMiddleware in front of any other middleware that the developer puts in the Startup.

If you wonder who is in charge of “processing IStartupFilter”, well … it’s the Build() method. It is this method that collects all the IStartupFilter and then calls its methods to Configure, and “configure” that way the application.

When to use IStartupFilter?

Well, as a general rule I would say that I do not see many scenarios that you can use it unless you build a library that requires certain tasks before Startup(or even after).

Hope this helps someone! 🙂

You may also like...

Leave a Reply

Your email address will not be published.