ASP.NET Core Middleware In A NutShell

In this article, we are going to understand the new pipeline concept in ASP.NET Core, Middleware. If you have been doing ASP.NET stuff for a while, you must have known that HTTP-Handlers and HTTP-Modules have always been part of the ASP.NET processing structure. But, with the new ASP.NET Core concept, Handlers and Modules are now history. They are no longer in use. Instead, the new Pipeline is implemented and that is called Middleware. Developers can utilize normal existing Middlewares or they can write their own custom Middleware depending on the need. Below is the high level diagram of MiddleWare.


Here, once an HTTP request comes at the ASP.NET server, the server will delegate the request to the first middleware in our application. Each middleware has the option of creating a response, or calling the next middleware. Basically, it's a bi-directional pipeline. Therefore, if the first component passes the request to the next middleware, the first component will see a response coming out of the other components. Each Middleware has got specific sets of functionalities. Now, let’s see how to configure Middleware. Let me create one empty project as shown below. Here, I am using Visual Studio 2017 RC Version. You can use VS-2015 as well.



 
Once the project is created; we will see something like the below project structure.

 
Now, let’s go ahead and see the Startup file created. Below is the sample startup file given to us.  
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Threading.Tasks;  
  5. using Microsoft.AspNetCore.Builder;  
  6. using Microsoft.AspNetCore.Hosting;  
  7. using Microsoft.AspNetCore.Http;  
  8. using Microsoft.Extensions.DependencyInjection;  
  9. using Microsoft.Extensions.Logging;  
  10.   
  11. namespace Custom_Midlleware  
  12. {  
  13.     public class Startup  
  14.     {  
  15.         // This method gets called by the runtime. Use this method to add services to the container.  
  16.         // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940  
  17.         public void ConfigureServices(IServiceCollection services)  
  18.         {  
  19.         }  
  20.   
  21.         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.  
  22.         public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)  
  23.         {  
  24.             loggerFactory.AddConsole();  
  25.   
  26.             if (env.IsDevelopment())  
  27.             {  
  28.                 app.UseDeveloperExceptionPage();  
  29.             }  
  30.   
  31.             app.Run(async (context) =>  
  32.             {  
  33.                 await context.Response.WriteAsync("Hello World!");  
  34.             });  
  35.         }  
  36.     }  

As you can see, we have two methods here. ConfigureServices and Configure. ConfigureServices is the place where Dependency injection happens and Configure is the place where we use middleware. We will do a deep dive around the same in a moment. Let’s first run the same and see how processing goes.



 

Obviously, first it will hit ConfigureServices to check if any component has been requested, later on it will go to the pipeline section and execute the same in linear ordering.

Note

Ordering of Midlleware is very important. We will explore this in the coming section.

After above execution, it simply produced Hello World text in browser.


Now, if you look closely at this Run method, it accepts RequestDelegate as a parameter whose signature accepts one parameter as a HTTP Context and return type is Task as shown below.



Now, what this middleware component is doing is it's handling all the requests made to the app and sending back the response. Run method adds as a RequestDelegate which is terminal to the request pipeline. This means any middleware comes after this; it won’t be reached. At the moment, we don’t have much in the middleware section. Let’s go ahead and add one.  Below is the structure for that.

 

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Threading.Tasks;  
  5. using Microsoft.AspNetCore.Builder;  
  6. using Microsoft.AspNetCore.Hosting;  
  7. using Microsoft.AspNetCore.Http;  
  8. using Microsoft.Extensions.DependencyInjection;  
  9. using Microsoft.Extensions.Logging;  
  10.   
  11. namespace Custom_Midlleware  
  12. {  
  13.     public class Startup  
  14.     {  
  15.         // This method gets called by the runtime. Use this method to add services to the container.  
  16.         // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940  
  17.         public void ConfigureServices(IServiceCollection services)  
  18.         {  
  19.         }  
  20.   
  21.         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.  
  22.         public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)  
  23.         {  
  24.             loggerFactory.AddConsole();  
  25.   
  26.             if (env.IsDevelopment())  
  27.             {  
  28.                 app.UseDeveloperExceptionPage();  
  29.             }  
  30.   
  31.             app.Run(async (context) =>  
  32.             {  
  33.                 await context.Response.WriteAsync("Hello World!");  
  34.             });  
  35.   
  36.             app.Use(async (context, next) =>  
  37.             {  
  38.                await context.Response.WriteAsync("First Middleware!");  
  39.                 await next.Invoke();  
  40.             });  
  41.         }  
  42.     }  

In the above snippet, I have added one middleware and in the response of that middleware, I have added some message to identify the same. Let’s go ahead and run the same. Here, it produced for me the same Hello World output as shown below. Nothing changed.


This is why the ordering of Middleware is very important. Run is a terminal middleware, which terminates the middleware chain, hence our new middleware couldn’t reach it. Middlewares always executes from top to bottom. Hence, let’s place the same before Run method.

 
With the above change in place, when I go ahead and run the same,


Now, we are going to delve further. Here, we will focus on other methods available to us like
  • Map
  • MapWhen

Let’s start with Map. Map has two parameters. Below is the concrete structure for the same.

Map has two parameters. First is pathMatch and second is delegate named configuration. Map allows you to add additional middleware components in the pipeline and this will be executed if it matches the string provided in the pathMatch, then it will start the requested path. Below is the complete structure of Map.

 
Below is the glimpse of Map extension. Here, I have provided “/myview” as path and just printed one text there using Run Middleware. Therefore, here you can see how branching is happening. In the Use extension, I have also printed one line after invoke method. This is there, just to prove the point of how context travels to and fro in the middleware pipeline.
 
 
 
Let’s run and see the same in action. If there comes a base request, then it will produce the below output.


Notic, the last line; this came from the first middleware after context returns from terminal middleware. Now, let’s go ahead and provide the map. In this case, it will produce the following output. 


One thing to notice here, it didn’t go outside Map. Here is the thing, if you are using Map extension, it won’t land in terminal middleware outside Map branch. It will automatically terminate that. It won’t even need Run inside Map to terminate the pipeline. I have used it here, just to print the message. Let me comment the same. Now my code looks like,

 
With the above changes in place, when I go ahead and Run the same, it will produce the following output.


It means, it printed the  first line from 1st middleware, which is Use extension, then it invoked the next middleware which is Map extension, where it matched the path. Upon successful match, it just terminated the pipeline and returned the result in browser. Now, let’s go ahead and see the structure of MapWhen.  

 
Here first parameter is the predicate and second parameter is the same configuration. Let’s go ahead and see the same in action.

 
With the above change in place, when I go ahead and run the app, and apply querystring like shown below, then it will produce the following thing.


Similarly, If I don’t keep terminal middleware inside our MapWhen extension, then also it will get terminated and below is the result for the same.

 

I can also go ahead and use middleware inside middleware like shown below.

 
In the above use case, I have used Use extension inside MapWhen. Upon running the same, it will produce the following output.

Output is pretty predictable and understood. It first executed first middleware, then came in next middleware, where it matched the request, then invoked next middleware, which printed 3rd line and finally while returning it printed the last line. However, if I don’t invoke the next middleware from MapWhen extension like shown below, then output would be something like this. 




Below is the finished code for the same. 
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Threading.Tasks;  
  5. using Microsoft.AspNetCore.Builder;  
  6. using Microsoft.AspNetCore.Hosting;  
  7. using Microsoft.AspNetCore.Http;  
  8. using Microsoft.Extensions.DependencyInjection;  
  9. using Microsoft.Extensions.Logging;  
  10.   
  11. namespace Custom_Midlleware  
  12. {  
  13.     public class Startup  
  14.     {  
  15.         // This method gets called by the runtime. Use this method to add services to the container.  
  16.         // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940  
  17.         public void ConfigureServices(IServiceCollection services)  
  18.         {  
  19.         }  
  20.   
  21.         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.  
  22.         public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)  
  23.         {  
  24.             loggerFactory.AddConsole();  
  25.   
  26.             if (env.IsDevelopment())  
  27.             {  
  28.                 app.UseDeveloperExceptionPage();  
  29.             }  
  30.   
  31.             app.Use(async (context, next) =>  
  32.             {  
  33.                 await context.Response.WriteAsync("First Middleware! <br/>");  
  34.                 await next.Invoke();  
  35.                 await context.Response.WriteAsync("While returning from Run!! <br/>");  
  36.             });  
  37.   
  38.             app.Map("/myview", (builder) =>  
  39.             {  
  40.                 builder.Run(async (context) =>  
  41.                 {  
  42.                     await context.Response.WriteAsync("Hello From Map Component!<br/>");  
  43.                 });  
  44.             });  
  45.   
  46.             app.MapWhen(context => context.Request.Query.ContainsKey("something"), (builder) =>  
  47.             {  
  48.                 builder.Use(async (context, next) =>  
  49.                 {  
  50.                     await context.Response.WriteAsync("Using Use Extension from MapWhen extension!!<br/>");  
  51.                     await next.Invoke();  
  52.                 });  
  53.                   
  54.                 builder.Run(async (context) =>  
  55.                 {  
  56.                     await context.Response.WriteAsync("Hi, From MapWhen Extension!<br/>");  
  57.                 });  
  58.             });  
  59.             app.Run(async (context) =>  
  60.             {  
  61.                 await context.Response.WriteAsync("Hello World!<br/>");  
  62.             });  
  63.   
  64.              
  65.         }  
  66.     }  

Now, let's go ahead and build our own custom middleware extensions and try to utilize the same in our code. I have created one folder for the same. Below is the glimpse for the same.

 
I have created one sample middleware and below is the snippet for the same.

 
Here, first comes constructor which has the reference of RequestDelegate next. This means this component will be able to invoke next middleware in the pipeline. After constructor, we have definition which allows us to invoke our middleware. Here, we have very simple custom middleware class. Now, in order to use the same in our application with appBuilder extension methods, we need to expose the same as extension. Therefore, let’s go ahead and write extension for the same. As for extensions, I’ll be keeping in a separate folder just for the sake of tidiness. Now, my folder structure goes like this.

 
And my extension method which I have written on top of IApplicationBuilder is just to make sure that it should be available like other extension methods.
 
 
 
Off-course, I am using one generic method to return my custom middleware here “UseMiddleware<>“. With that being said, let’s consume the same in our app.
 
 
 
With the above changes done, when I run my app, it will come out something like this.

However, when I run some other middlewares like MapWhen, then it will produce following output. 

I hope middleware chaining is clear now. In coming sections, we will delve further. Thanks for joining me.

Next Recommended Readings