top of page

What is middleware in .NET? How it Works?

Updated: Jul 29, 2022

In .Net 5, middleware plays a vital role to handle the request-response pipeline. In here we are going to discuss the concept of middleware pipeline and how we can make our own custom middleware and plug in to the pipeline.


In classic asp.net model, the request and response objects are bigger and tightly coupled with the IIS. Even some of the values of these objects are filled by IIS request-response pipeline and thus unit testing of such objects is a challenge.


To decouple the applications from the IIS or web servers, .Net followed the standards defined by OWIN, and there have been many implementations over the years from Katana to this day, and we are going to see how the implementation looks like in .Net 5.


How middleware works:

OWIN defines that, the requests coming from the web server to any web application, have to pass through multiple components. And these components actually build the request-response pipeline.

These components can inspect, modify, redirect or generate response for any incoming request and then the response get back to the web server passing through the same pipeline but in the reverse order.



There are three sorts of methods we can use to run the custom middleware.

  • Run()

  • Use()

  • Map()


Run, Use, Map and their responsibilities.


Run() intercepts requests and generate response without chaining it to any other pipeline. Run delegates do not receive a next parameter.

app.Run(async context =>
{
    await context.Response.WriteAsync
    ("I will generate the response");
});

Middleware component generates with Run generates a response.


Use() chains multiple request delegates where next parameter represents the next delegate of the pipeline. The pipeline can be short-circuited by not calling the next method and actions can be performed both before and after next delegate.

app.Use(async (context, next) =>
{
    //logic before next middleware here
    await next.Invoke();

    //logic after next middleware here
});


Middleware registered with Use can forward the request onto the next delegate.


Map() is used for branching the pipeline, it branches the request pipeline based on matches of the given request path. Map also supports nesting.

app.Map("/hello-world", SayHello);
//a method outside of configure method
private static void SayHello(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync
        ("Hi, the request was mapped here.");
    });
}


In the code, if the incoming request matches the “hello-world”, the SayHello method will be executed, and the SayHello method here will generate response using Run method.


Implementation:

There are 2 ways to define custom middleware:

  • Custom middleware class

  • Inline custom middleware

We will be trying both ways here.


Custom middleware class:

Let’s start with a custom middleware class containing our middleware logic.


public class justValidator
{
    public RequestDelegate _next { get; }
    public justValidator(RequestDelegate next)
    {
        _next = next;
    }
    public async Task InvokeAsync(HttpContext context)
    {
        StringValues authorizationToken;
        context.Request.Headers.TryGetValue("x-id", out 
          authorizationToken);
        if (authorizationToken.Count > 0 && authorizationToken[0] == 
        "confusing21")
                await _next(context);
        else
                context.Response.StatusCode = 
                (int)HttpStatusCode.InternalServerError;
        await context.Response.WriteAsync("Invalid header token");
        return;
    }
}


This class will get the call when the request will hit this middleware. The InvokeAsync method will be called and the current HttpContext will be passed to it. Using the context we can execute our custom logic and then the next method will call the next middleware.


Here this class will be looking for a header value like an authentication token, if it gets the desired value which is a static value for this demo, the next middleware will be called otherwise it will send a message.


For the middleware to get into the pipeline we need to put it into the Configure method of the Startup class.

app.UseMiddleware<justValidator>();

Let’s test it in Postman now.


Request-response checking using Postman without x-id


In the above picture, the Header does not contain x-id so the response is Invalid header token.


Request-response checking using Postman with x-id


Now, after giving the proper x-id value, we can see the data in Postman now.


Inline custom middleware:

The inline custom middleware can be directly defined in the Configure method like this:


public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseMiddleware<justValidator>();
    app.Use(async (context, next) =>
    {
        DateTime startTime = DateTime.Now;
        await next();
        DateTime endTime = DateTime.Now;
        TimeSpan processTime = endTime - startTime;
        await context.Response.WriteAsync($"Total Process Time 
        {processTime}");
        }
    );
    if (env.IsDevelopment())

    {
        app.UseDeveloperExceptionPage();
    }
        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>{endpoints.MapControllers();
    });
}

The inline middleware here to show/log any request execution time.


Let’s test again on Postman.


The request execution time


From the screenshot we can see the request execution time (marked with yellow ink).


Note : Even though middleware is discussed here in the context of .Net 5, the concept of middleware is same in all MVC implementations that maintains the OWIN standards.


For the source code, please check GitHub.

References:

  • owin: open web server interface for .net

  • asp.net core middleware

  • pipes and filters pattern



Resource: Medium - Mutasim Malik


The Tech Platform

0 comments
bottom of page