DEV Community

Cover image for Middleware in ASP.NET Core
Mohamad Lawand
Mohamad Lawand

Posted on

Middleware in ASP.NET Core

In this article we will be exploring Asp.Net Middlewares. On this episode we will explore and dig deep into middleware, what are they and how do they work. What are the different types of middleware and when we should use each.

You can watch the full video on Youtube

You can find the source code on GitHub:
https://github.com/mohamadlawand087/v43-delegates

So what we will cover today:

  • What is a Middleware
  • Advantages and Disadvantages
  • Ingredients
  • Diving deep
  • 3 types of Middlewares execution
  • Built in Middlewares in Asp.Net Core

As always please comment your questions, clarifications and suggestions in the comments down below. Please like, share and subscribe if you like the video. It will really help the channel

What is a Middleware

Middleware is a component assembled in the app pipeline to handle requests and responses. they are chained one after the other so each one will choose to pass the request to the next component or not.

Each component:

  • Chooses whether to pass the request to the next component in the pipeline.
  • Can perform work before and after the next component in the pipeline.

Perform work before and after the next component.

Advantages

  • Configuration of every HTTP processing:Essentially, the middleware performs some specific function on the HTTP request or response at a specific stage in the HTTP pipeline before or after the user defined controller.
  • Lean application due to the high customisability
  • Better Performance / Less memory usage
  • Middleware are highly asynchronous

Disadvantages

  • Porting complex authorisation / authentication module could be time consuming
  • Philosophy of middleware is complicated
  • Finding the right middleware to use

Will start by creating a new asp.net core application

dotnet new web -n testApp
Enter fullscreen mode Exit fullscreen mode

Now that we have created our application let us open it in VS Code and examen our application.

We can see we have our startup class and program.cs

In the startup class is where we are doing all of the wiring and configuration for our Asp.Net Core application.

In the Startup Class we have 2 methods

  • Configure Services: This is not a mandatory method
  • Configure: This is mandatory

Now let us build and run the application and see what do we get, to build the application we will run the following

dotnet build
Enter fullscreen mode Exit fullscreen mode

To run the application

dotnet run
Enter fullscreen mode Exit fullscreen mode

Now let us visit the generated Url and we can see that the response we are getting is hello world no matter what controller/action we add to the url if they don't exist.

Why is that? its because of the way our startup class has been configured out of the box which is to route all requests to a "Hello World."

Now let us examin how the Startup class work and let us understand how Middlewear works.

Will start by having an empty class and will build it up as we go so we can understand how all fits together.

We will remove all of the source code from inside the startup class and will leave the 2 mthods there. So we will have something like this

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {    
    }
}
Enter fullscreen mode Exit fullscreen mode

Now let us run the application and see what is going to happen

dotnet run
Enter fullscreen mode Exit fullscreen mode

Once the application run, and we visit the Url we can see that we are getting a 404 because our Configure method is empty and we have not added any configuration to our Asp.Net application so it know how to handle the requests, so a 404 response is given.

Now let us fix this by adding some functionality to our startup class, the configure method is we setup middlewares.

Now let us update our configure method with a simple middleweare so we can process any incoming requests

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // app is an instance of application builder
    // run method => which takes a delegate function to process the request
    // we utilise the response and write a message 
    // This is a request delegate which is a dotnet middlewear - inline middlewear
    app.Run(async c => await c.Response.WriteAsync("Welcome to the App"));
}
Enter fullscreen mode Exit fullscreen mode

Now let us run the application and see what do we get

dotnet run
Enter fullscreen mode Exit fullscreen mode

As we can see now we are getting the "Welcome to the App " message when we visite the generated url http://localhost:5000 or any url under the same localhost. No matter requests comes in the inline middlewear that we have created will be able to pick up the request and show the same message on the screen.

The request delegates or the middlewear can be configured in 3 extension methods

  • app.Run
  • app.Use
  • app.Map

Let us delve deep into each one and see how do they differe and when should we use each one

app.Run

  • it only recieves a context parameter, it doesn't know about the next middlewear these delegates are called terminal delegates because it ends and terminates the middlewear pipelines
  • It is more of a convention to use the Run method to indicate that we are at the end of a pipelines, let us add a new delegate after this app.Run and we will see that it will not work as the first app.Run middlewear has terminated the pipelines if we run it now we can see only the first message is showing
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // app is an instance of application builder
    // run method => which takes a delegate function to process the request
    // we utilise the response and write a message 
    // This is a request delegate which is a dotnet middlewear - inline middlewear
    app.Run(async c => await c.Response.WriteAsync("Welcome to the App"));
    app.Run(async c => await c.Response.WriteAsync("Welcome to the App part 2"));
}
Enter fullscreen mode Exit fullscreen mode

The main idea of a middlewear is the ability to chain middlewear behind each other to process and create the functionality that we want.

app.Use

with this extension method will allow us to chain middlewares so lets see this in action

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // middlewear 1
    app.Use(async (c, next) => {
        Console.WriteLine("Order 1");
        await c.Response.WriteAsync("I am the first middlewear");
                // this is responsible for calling middlewear 2 
        await next();

                // once middlewear 2 has completed we return to continue the execution
                // of the first middlewear
        Console.WriteLine("Order 2");
    });

        // middlewear 2
    app.Run(async c => await c.Response.WriteAsync("\nI am the second middlewear"));
}
Enter fullscreen mode Exit fullscreen mode

As we can see here we have utilised the Use extension method to create our middlewear and chain the second middlewear to it.

Now let us add a new middlewear and let us monitor the flow of execution

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // middlewear 1
    app.Use(async (c, next) => {
        Console.WriteLine("Order 1");
        await c.Response.WriteAsync("I am the first middlewear");

        // this is responsible for calling middlewear 2 
        await next();

        // once middlewear 2 has completed we return to continue the execution
        // of the first middlewear
        Console.WriteLine("Order 2");
    });

    // middlewear 2
    app.Use(async (c, next) => {
        Console.WriteLine("Order 1a");
        await c.Response.WriteAsync("\nI am the second middlewear");

        // this is responsible for calling middlewear 2 
        await next();

        // once middlewear 2 has completed we return to continue the execution
        // of the first middlewear
        Console.WriteLine("Order 2a");
    });

    // middlewear 3
    app.Run(async c => await c.Response.WriteAsync("\nI am the third middlewear"));
}
Enter fullscreen mode Exit fullscreen mode

Once we run our application we can see the order of execution is as follow

Console:
Order 1
Order 1a
Order 2a
Order 2

Browser:
I am the first middlewear
I am the second middlewear
I am the third middlewear
Enter fullscreen mode Exit fullscreen mode

Which means we are going throw the middlewear processing in order of execution. the only way we can specify in which order the middlewear are running is by writing them in the right order in the code.

Let us see if we update the middlewares to the following

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // middlewear 1
    app.Use(async (c, next) => {
        Console.WriteLine("Order 1");
        await c.Response.WriteAsync("I am the first middlewear");

        // this is responsible for calling middlewear 2 
        await next();

        // once middlewear 2 has completed we return to continue the execution
        // of the first middlewear
        Console.WriteLine("Order 2");
    });

    // middlewear 3
    app.Run(async c => await c.Response.WriteAsync("\nI am the third middlewear"));

    // middlewear 2
    app.Use(async (c, next) => {
        Console.WriteLine("Order 1a");
        await c.Response.WriteAsync("\nI am the second middlewear");

        // this is responsible for calling middlewear 2 
        await next();

        // once middlewear 2 has completed we return to continue the execution
        // of the first middlewear
        Console.WriteLine("Order 2a");
    }); 
}
Enter fullscreen mode Exit fullscreen mode

We can see here that the second middlewear doesn't get executed as its after the app.Run middlewear which basically terminates the middlewear pipelines.

For this reason ordering the middlewares is really important.

So now let us examine how the pipeline is getting chained

Alt Text

The chainning of the middleware will be following the numbers

  • 1 - the request will come
  • Logic will excute inside the middleware 1
  • middleware 1 will call the next method which will call the next middleware
  • 2- middleware 2 is being called
  • Logic inside middleware 2 will execute
  • middleare 2 will call the next method which will call the next middleware
  • 3- middleare 3 is being caled
  • Logic inside middleware 3 is being executed
  • since there is not next method this will be a terminator middleware and the request will start going back up the stack
  • middleware 3 will call middleware 2
  • Logic will execute in middleware 2
  • middleware 2 will call middleware 1
  • middleware 1 will execute the logic
  • middleware 1 will return the request to the user

In summary middleare are chained with each other no matter how many middlewares we have they are going to be chainned together and executed in the order they are added.

From the illustration below we can see how middleware nesting works when we chain middleware together. The main idea of having seperate methods (seperate middlewares) is to keep our code clean and to follow the single responsibility principle where every method (middleware) is responsible for only 1 responsibility.

Alt Text

Built-in middleware

ASP.NET Core ships with a lot middleware components built into it which help us utilise ASP.NET to its full potential and removing the need for us to write a lot of the functionalities that we need. Some of the main middlewares that comes with Asp.Net

  • Authentication
  • Authorisation
  • CORS
  • MVC
  • Rooting
  • HTTPS

And so much more.

Building our own middleware

The next step is to move the code from the startup to its own class so we can keep the startup class as clean and simple as possible. The first thing we need to do is create a folder in our root directory called Extensions and inside the extensions folder will create a new class called AppCultureMiddleware.cs

Now let us build our first middleware with extension methods

public class AppCultureMiddleware //: IMiddleware
    {
        private readonly RequestDelegate _next;

        public AppCultureMiddleware(RequestDelegate next)
        {
            _next = next;
        }
        public async Task InvokeAsync(HttpContext context)
        {
           var cultureQuery = context.Request.Query["culture"];
            if (!string.IsNullOrWhiteSpace(cultureQuery))
            {
                var culture = new CultureInfo(cultureQuery);

                CultureInfo.CurrentCulture = culture;
                CultureInfo.CurrentUICulture = culture;
            }

            // Call the next delegate/middleware in the pipeline
             await _next(context);
        }
    }
Enter fullscreen mode Exit fullscreen mode

Will start by utilising the extention method, in the same folder Extentions let us create a new class called AppCultureMiddlewareExtensions and update it with the following

// Static class
public static class AppCultureMiddlewareExtensions
{
        // ASP.NET core builder
    public static IApplicationBuilder UseAppCulture(
        this IApplicationBuilder builder)
    {
                // initialise the middleware
        return builder.UseMiddleware<AppCultureMiddleware>();
    }
}
Enter fullscreen mode Exit fullscreen mode

Now let us update the startup class, inside the ConfigureServices metho with the following

app.UseAppCulture();
Enter fullscreen mode Exit fullscreen mode

Now to test it we can build and run our application

dotnet run
Enter fullscreen mode Exit fullscreen mode

and then visiting the application with the cutlture query string similar to the following

https://localhost:5001/?culture=no
Enter fullscreen mode Exit fullscreen mode

Now let us check the other way to create a middleware, will create a new middleare just to console log the flow of the code. Inside the extensions folder let us create a new class called AppSampleLogsMiddleware.cs this class will implement the IMiddleware interface

public class AppSampleLogsMiddleware : IMiddleware // implementing the interface
{
    // Required methods to implement the interface correctly
    public Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        throw new System.NotImplementedException();
    }
}
Enter fullscreen mode Exit fullscreen mode

We are going to add some logic to our middleware which will log the flow of the middleware.

public class AppSampleLogsMiddleware : IMiddleware // implementing the interface
{
    // Required methods to implement the interface correctly
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        Console.WriteLine("Order 1a");
        await context.Response.WriteAsync("\nI am the second middlewear");

        // this is responsible for calling middlewear 2 
        await next(context);

        // once middlewear 2 has completed we return to continue the execution
        // of the first middlewear
        Console.WriteLine("Order 2a");
    }
}
Enter fullscreen mode Exit fullscreen mode

Now that our middleware is ready we need to update the startup class so our application will recognise it, inside out configureServices we can register our new middleware by adding the following code

public void ConfigureServices(IServiceCollection services)
{
    // we are going to register the middleware into our services collection
    services.AddTransient<AppSampleLogsMiddleware>();
}
Enter fullscreen mode Exit fullscreen mode

Now let us update the Configure method in the startup class to use the middleware by adding the following code

app.UseMiddleware<AppSampleLogsMiddleware>();
Enter fullscreen mode Exit fullscreen mode

Now to utilise this middleware we have 2 ways to register it the first one is to

  • Use the Middleware extension method
  • Register the Middleware method in the startup class
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseAppCulture();

    // middlewear 1
    app.Use(async (c, next) => {
        Console.WriteLine("Order 1");
        await c.Response.WriteAsync("I am the first middlewear");

        // this is responsible for calling middlewear 2 
        await next();

        // once middlewear 2 has completed we return to continue the execution
        // of the first middlewear
        Console.WriteLine("Order 2");
    });

    app.UseMiddleware<AppSampleLogsMiddleware>();

    // middlewear 3
    app.Run(async c => await c.Response.WriteAsync("\nI am the third middlewear"));
}
Enter fullscreen mode Exit fullscreen mode

app.Map

This extension method is usually used when we want to branch the middleware pipeline, so anytime we want to branch the request into 2 different pipelines we use map.

Each time we use map it will completely break off the original pipeline and everything after that it will not be invoked. Let us see this in action

So Basically when ever we redirect our application to /life the middleweare will be triggered and will execute


public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
        // middlewear 1
    app.Use(async (c, next) => {
        Console.WriteLine("Order 1");
        await c.Response.WriteAsync("I am the first middlewear");

        // this is responsible for calling middlewear 2 
        await next();

        // once middlewear 2 has completed we return to continue the execution
        // of the first middlewear
        Console.WriteLine("Order 2");
    });

    app.Map("/life", lifeHandler);
    app.UseMiddleware<AppSampleLogsMiddleware>();
}

private void lifeHandler(IApplicationBuilder app)
{
    app.Run(async context => {
        Console.WriteLine("I am your life handler");
        await context.Response.WriteAsync("I am your life handler");
    });
}
Enter fullscreen mode Exit fullscreen mode

Now let us run the code

dotnet run
Enter fullscreen mode Exit fullscreen mode

Ad we can see when executing that noting after the map middleware is executing because the map method will be the last one to execute

Another instance pf the map method is to use the mapWhen, which is a conditional based mapping. mapWhen will take a condition and only execute if these conditions are met. Let us update our code to the following

app.MapWhen(c => c.Request.Query.ContainsKey("s"), HandleConditionalRequest);
Enter fullscreen mode Exit fullscreen mode
private void HandleConditionalRequest(IApplicationBuilder app)
{
    app.Run(async context => {
        Console.WriteLine("I am your conditional handler");
        await context.Response.WriteAsync("I am your conditional handler");
    });
}
Enter fullscreen mode Exit fullscreen mode

This will terminate the middleware pipeline since we are using the app.Run inside the method.

And finally we can use the app.UseWhen which basically allow us to continue utilising our pipeline with something similar to this

app.UseWhen(c => c.Request.Query.ContainsKey("u"), HandleSecondConditionalRequest);
Enter fullscreen mode Exit fullscreen mode
private void HandleSecondConditionalRequest(IApplicationBuilder app)
{
    app.Use(async (context, next) => {
        Console.WriteLine("I am your second conditional handler");
        await context.Response.WriteAsync("I am your second conditional handler");
        await next();
    });
}
Enter fullscreen mode Exit fullscreen mode

Discussion (0)