Understanding ASP.NET - Part Three - Building Reusable And Configurable Middlewares

Introduction

Hello and welcome to part 3 of "Understanding ASP.NET with Owin and Katana" series. In this series, we are learning about new Owin and Katana features in ASP.NET 4 and above. So, if you are new to OWIN, please go back and check other parts as well, where we discussed all the details about OWIN, its benefits, and why we should use it.

  1. Understanding ASP.Net - Part1- Owin and Katana Introduction
  2. Understanding ASP.Net -Part2- Building an Owin Pipeline

In this part, we will create a reusable OWIN middleware that we can configure and use in different projects and even in a different OWIN implementation other than Project Katana.

Creating Middleware

We will refactor our delegate based debugged middleware that we created in the last part and make it reusable and configurable by creating a middleware class. Now, create a folder named “Middlewares” where we’ll put our middleware classes.

ASP.NET

With this in place, create a class with name DebugMiddlware in Middlewares folder.

ASP.NET

Before we turn this class into an OWIN middleware, I just want to mention that if you look around on the internet about how to implement a Katana base OWIN middleware with a reusable pattern,  you’ll see a lot of people doing this by creating a class inheriting from base class OwinMiddleware. OwinMiddleware class comes with project Katana. Creating middleware using that pattern is fine but its only limitation is that you can’t use this middleware in different OWIN implementation other than project katana.

So, we are not going to use this pattern but instead, we’ll declare our very own AppFunc that takes IDictionary<string, object> and returns a Task.

  1. using AppFunc = Func<IDictionary<string, object>, Task>;  
  2.   
  3.     public class DebugMiddleware  
  4.     {  
  5.     }  

Now, we need to create constructor of this class that takes a single parameter of type AppFunc named as next.

  1. public class DebugMiddleware  
  2.     {  
  3.         private AppFunc _next;  
  4.         public DebugMiddleware(AppFunc next)  
  5.         {  
  6.             _next = next;  
  7.         }  
  8.     }  

This next variable is used to invoke the next middleware in the pipeline so that’s why we named it to next.

Now, we need to invoke this middleware. To invoke a middleware, we need to create a public method named “Invoke” that takes IDictionary<string,object> and returns a Task . As it returns a task we’ll mark it with async keyword to tell the C# compiler this method will perform an asynchronous operation. The benefit of marking the method with async is that we don’t have to manually return the task instead await keyword will do the job for us.

  1. public async Task Invoke(IDictionary<string,object> environment)  
  2.         {  
  3.             //all the functionality goes here   
  4.         }  

In the Invoke method, we’ll put all the logic of our middleware and will call the AppFunc to invoke the next middleware in pipeline. Since we are building a debug middleware, so let’s add some tracing logic that we have already implemented in our delegate based middleware in part2.

  1. public async Task Invoke(IDictionary<string,object> environment)  
  2.         {  
  3.             var owinContext = new OwinContext(environment);  
  4.             Debug.WriteLine("Request: " + owinContext.Request.Path);  
  5.   
  6.             await _next(environment);//will forward request to next middleware  
  7.   
  8.             /*at this point responce headers will be sent client  
  9.             /and response body is about to sent*/  
  10.             Debug.WriteLine("Responce Status Code:" + owinContext.Response.StatusCode);  
  11.         }  

Now, the last thing is to plug in middleware into the pipeline using IAppBuilder from Startup.cs class

  1. public class Startup  
  2.     {  
  3.         /*IAppBuilder object is used to plugin middlewares  
  4.         to build a pipeline*/  
  5.         public void Configuration(IAppBuilder app)  
  6.         {  
  7.             app.Use<DebugMiddleware>();  
  8.   
  9.             app.Use(async (context, nextMiddleWare) =>  
  10.             {  
  11.                 await context.Response.WriteAsync("Peace be on world.");  
  12.             });  
  13.         }  
  14.     }  

Finally, press F5 to run the app with debugger attached. To make sure our Debug middleware has run successfully, we need to see trace messages written to output window.

ASP.NET

Now, we have a reusable custom OWIN middleware running in pipeline, but still we have something to add into pattern.

Configuring Middleware

Most of the time, we would like our middleware to do different things in different situations. To introduce that functionality, we need an options class. An options class is plane C# class that have name of middleware plus “options” as suffix. In our case we’ll create class named “DebugMiddlewareOptions”.

  1. public class DebugMiddlewareOptions  
  2.     {  
  3.     }  

The options class will contain things that we need to configure in our middleware. Let’s say we need to configure two things in our middleware so we’ll have two properties in our options class.

  1. public class DebugMiddlewareOptions  
  2.     {  
  3.         public Action<IOwinContext> OnIncomingRequest { get; set; }  
  4.         public Action<IOwinContext> OnOutgoingResponse { get; set; }  
  5.     }   

OnIncomingRequest will be called when middleware will receive request and OnOutgoingResponse will be called when response will be on its way to client. Now we need our middleware class to take this options class instance through constructor for configuration to work. We’ll have a second parameter of Options class in DebugMiddleware’s constructor that’s already taking AppFunc parameter.

  1. private AppFunc _next;  
  2.         private DebugMiddlewareOptions _options;  
  3.         public DebugMiddleware(AppFunc next, DebugMiddlewareOptions options)  
  4.         {  
  5.             _next = next;  
  6.             _options = options;  
  7.         }  

Now instead of directly outputting response to output window from Invoke method we’ll refactor it to use DebugMiddlewareOptions delegates.

  1. public async Task Invoke(IDictionary<string,object> environment)  
  2.         {  
  3.             var owinContext = new OwinContext(environment);  
  4.   
  5.             _options.OnIncomingRequest(owinContext);  
  6.   
  7.             await _next(environment);//will forward request to next middleware  
  8.   
  9.             /*at this point responce headers will be sent client  
  10.             /and response body is about to sent*/  
  11.             _options.OnOutgoingResponse(owinContext);  
  12.         }   

We should provide default functionality to both delegates of DebugMiddlewareOptions class if in some case options class didn’t provide a call back.

  1. public DebugMiddleware(AppFunc next, DebugMiddlewareOptions options)  
  2.         {  
  3.             _next = next;  
  4.             _options = options;  
  5.   
  6.             if (_options.OnIncomingRequest == null)  
  7.                 _options.OnIncomingRequest = (ctx) => {  
  8.                     Debug.WriteLine("Request: " + ctx.Request.Path);  
  9.                 };  
  10.   
  11.             if (_options.OnOutgoingResponse == null)  
  12.                 _options.OnOutgoingResponse = (ctx) => {  
  13.                     Debug.WriteLine("Responce Status Code:" + ctx.Response.StatusCode);  
  14.                 };  
  15.         }  

We’ll provide instance of options class to middleware with app.Use<DebugMiddleware> method by the when we’ll plug in middleware to pipeline in Startup.cs class.

  1. app.Use<DebugMiddleware>(new DebugMiddlewareOptions());  

Run the app with debugger attached and see the output window to make sure debug middleware runs.

At this point we need to add some configuration to our middleware to change the way it works. Let’s turn this middleware into a performance mintoring module that will output total time elapsed by a request to complete the response. We’ll add the functionality to middleware by providing options call back methods for both OnOutgoingResponse and OnIncomingRequest delegates from Startup.cs class.

  1. app.Use<DebugMiddleware>(new DebugMiddlewareOptions() {  
  2.                 OnIncomingRequest = (ctx) => {  
  3.                     var stopWatch = new Stopwatch();  
  4.                     stopWatch.Start();  
  5.   
  6.                     //put the stop into environment dictionary   
  7.                     ctx.Environment["StopWatch"] = stopWatch;  
  8.                 },  
  9.                 OnOutgoingResponse = (ctx)=> {  
  10.                     var stopWatch = (Stopwatch)ctx.Environment["StopWatch"];  
  11.   
  12.                     Debug.Write($"Time Elapsed: {stopWatch.ElapsedMilliseconds} milliseconds");  
  13.                 }  
  14.             });  

The middleware adds a running stopwatch into environment dictionary from OnIncomingRequest and get it back from OnOutgoingResponse to output total millicseconds elapsed during the completion of request.

Press F5 to run the app with debugger attached and look at the output ot make sure configuration worked properly.

ASP.NET

48 milliseconds are there because of extra overhead when you first time run the app but if you refresh from browser window after the app is running, you will see a much faster response.

Congratulations! Finally, we have a fully configurable and reusable middleware running into our pipeline.

Source Code

Startup.cs

  1. public class Startup  
  2.     {  
  3.         /*IAppBuilder object is used to plugin middlewares  
  4.         to build a pipeline*/  
  5.         public void Configuration(IAppBuilder app)  
  6.         {  
  7.             app.Use<DebugMiddleware>(new DebugMiddlewareOptions() {  
  8.                 OnIncomingRequest = (ctx) => {  
  9.                     var stopWatch = new Stopwatch();  
  10.                     stopWatch.Start();  
  11.   
  12.                     //put the stop into environment dictionary   
  13.                     ctx.Environment["StopWatch"] = stopWatch;  
  14.                 },  
  15.                 OnOutgoingResponse = (ctx)=> {  
  16.                     var stopWatch = (Stopwatch)ctx.Environment["StopWatch"];  
  17.   
  18.                     Debug.Write($"Time Elapsed: {stopWatch.ElapsedMilliseconds} milliseconds");  
  19.                 }  
  20.             });  
  21.   
  22.             app.Use(async (context, nextMiddleWare) =>  
  23.             {  
  24.                 await context.Response.WriteAsync("Peace be on world.");  
  25.             });  
  26.         }  
  27.     }  

DebugMiddleware.cs

  1. using Microsoft.Owin;  
  2. using System;  
  3. using System.Collections.Generic;  
  4. using System.Diagnostics;  
  5. using System.Threading.Tasks;  
  6.    
  7. namespace OwinPipeline.Middlewares  
  8. {  
  9.     using AppFunc = Func<IDictionary<string, object>, Task>;  
  10.   
  11.     public class DebugMiddleware  
  12.     {  
  13.         private AppFunc _next;  
  14.         private DebugMiddlewareOptions _options;  
  15.         public DebugMiddleware(AppFunc next, DebugMiddlewareOptions options)  
  16.         {  
  17.             _next = next;  
  18.             _options = options;  
  19.   
  20.             if (_options.OnIncomingRequest == null)  
  21.                 _options.OnIncomingRequest = (ctx) => {  
  22.                     Debug.WriteLine("Request: " + ctx.Request.Path);  
  23.                 };  
  24.   
  25.             if (_options.OnOutgoingResponse == null)  
  26.                 _options.OnOutgoingResponse = (ctx) => {  
  27.                     Debug.WriteLine("Responce Status Code:" + ctx.Response.StatusCode);  
  28.                 };  
  29.         }  
  30.   
  31.         public async Task Invoke(IDictionary<string,object> environment)  
  32.         {  
  33.             var owinContext = new OwinContext(environment);  
  34.   
  35.             _options.OnIncomingRequest(owinContext);  
  36.   
  37.             await _next(environment);//will forward request to next middleware  
  38.   
  39.             /*at this point responce headers will be sent client  
  40.             /and response body is about to sent*/  
  41.             _options.OnOutgoingResponse(owinContext);  
  42.         }  
  43.     }  
  44. }  

DebugMiddlewareOptions.cs

  1. using Microsoft.Owin;  
  2. using System;  
  3.   
  4. namespace OwinPipeline  
  5. {  
  6.     public class DebugMiddlewareOptions  
  7.     {  
  8.         public Action<IOwinContext> OnIncomingRequest { get; set; }  
  9.         public Action<IOwinContext> OnOutgoingResponse { get; set; }  
  10.     }  
  11. }  


Similar Articles