Understanding ASP.Net -Part3- Building Reusable and Configurable Middlewares




Introduction

Hello and welcome to part3 of Understanding ASP.Net with Owin and Katana series, I this series we are learning about new owin and katana features in ASP.Net 4 and above. So, if you are new to OWIN then please go back and check other parts as well where we discussed all the details about what OWIN, what benefits it provides 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 debug middleware that we created in the last part and turn it into a reusable and configurable middleware by creating a middleware class. Now create a folder named “Middlewares” where we’ll put our middleware classes.


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


Before we turn this class into and 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 then you’ll get to see a lot of peoples will show you 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 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.

using AppFunc = Func<IDictionary<string, object>, Task>;

    public class DebugMiddleware
    {
    }

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

public class DebugMiddleware
    {
        private AppFunc _next;
        public DebugMiddleware(AppFunc next)
        {
            _next = next;
        }
    }

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 its returns a task so 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.

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

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.

public async Task Invoke(IDictionary<string,object> environment)
        {
            var owinContext = new OwinContext(environment);
            Debug.WriteLine("Request: " + owinContext.Request.Path);

            await _next(environment);//will forward request to next middleware

            /*at this point responce headers will be sent client
            /and response body is about to sent*/
            Debug.WriteLine("Responce Status Code:" + owinContext.Response.StatusCode);
        }

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

public class Startup
    {
        /*IAppBuilder object is used to plugin middlewares
        to build a pipeline*/
        public void Configuration(IAppBuilder app)
        {
            app.Use<DebugMiddleware>();

            app.Use(async (context, nextMiddleWare) =>
            {
                await context.Response.WriteAsync("Peace be on world.");
            });
        }
    }

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.


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

Configuring Middleware

In 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”.

public class DebugMiddlewareOptions
    {
    }

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.

public class DebugMiddlewareOptions
    {
        public Action<IOwinContext> OnIncomingRequest { get; set; }
        public Action<IOwinContext> OnOutgoingResponse { get; set; }
    }

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.

private AppFunc _next;
        private DebugMiddlewareOptions _options;
        public DebugMiddleware(AppFunc next, DebugMiddlewareOptions options)
        {
            _next = next;
            _options = options;
        }

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

public async Task Invoke(IDictionary<string,object> environment)
        {
            var owinContext = new OwinContext(environment);

            _options.OnIncomingRequest(owinContext);

            await _next(environment);//will forward request to next middleware

            /*at this point responce headers will be sent client
            /and response body is about to sent*/
            _options.OnOutgoingResponse(owinContext);
        }

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

public DebugMiddleware(AppFunc next, DebugMiddlewareOptions options)
        {
            _next = next;
            _options = options;

            if (_options.OnIncomingRequest == null)
                _options.OnIncomingRequest = (ctx) => {
                    Debug.WriteLine("Request: " + ctx.Request.Path);
                };

            if (_options.OnOutgoingResponse == null)
                _options.OnOutgoingResponse = (ctx) => {
                    Debug.WriteLine("Responce Status Code:" + ctx.Response.StatusCode);
                };
        }

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.
            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 its way to work. 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.

app.Use<DebugMiddleware>(new DebugMiddlewareOptions() {
                OnIncomingRequest = (ctx) => {
                    var stopWatch = new Stopwatch();
                    stopWatch.Start();

                    //put the stop into environment dictionary
                    ctx.Environment["StopWatch"] = stopWatch;
                },
                OnOutgoingResponse = (ctx)=> {
                    var stopWatch = (Stopwatch)ctx.Environment["StopWatch"];

                    Debug.Write($"Time Elapsed: {stopWatch.ElapsedMilliseconds} milliseconds");
                }
            });

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.


48 milliseconds are 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
public class Startup
    {
        /*IAppBuilder object is used to plugin middlewares
        to build a pipeline*/
        public void Configuration(IAppBuilder app)
        {
            app.Use<DebugMiddleware>(new DebugMiddlewareOptions() {
                OnIncomingRequest = (ctx) => {
                    var stopWatch = new Stopwatch();
                    stopWatch.Start();

                    //put the stop into environment dictionary
                    ctx.Environment["StopWatch"] = stopWatch;
                },
                OnOutgoingResponse = (ctx)=> {
                    var stopWatch = (Stopwatch)ctx.Environment["StopWatch"];

                    Debug.Write($"Time Elapsed: {stopWatch.ElapsedMilliseconds} milliseconds");
                }
            });

            app.Use(async (context, nextMiddleWare) =>
            {
                await context.Response.WriteAsync("Peace be on world.");
            });
        }
    }

DebugMiddleware.cs
using Microsoft.Owin;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;

namespace OwinPipeline.Middlewares
{
    using AppFunc = Func<IDictionary<string, object>, Task>;

    public class DebugMiddleware
    {
        private AppFunc _next;
        private DebugMiddlewareOptions _options;
        public DebugMiddleware(AppFunc next, DebugMiddlewareOptions options)
        {
            _next = next;
            _options = options;

            if (_options.OnIncomingRequest == null)
                _options.OnIncomingRequest = (ctx) => {
                    Debug.WriteLine("Request: " + ctx.Request.Path);
                };

            if (_options.OnOutgoingResponse == null)
                _options.OnOutgoingResponse = (ctx) => {
                    Debug.WriteLine("Responce Status Code:" + ctx.Response.StatusCode);
                };
        }

        public async Task Invoke(IDictionary<string,object> environment)
        {
            var owinContext = new OwinContext(environment);

            _options.OnIncomingRequest(owinContext);

            await _next(environment);//will forward request to next middleware

            /*at this point responce headers will be sent client
            /and response body is about to sent*/
            _options.OnOutgoingResponse(owinContext);
        }
    }
}

DebugMiddlewareOptions.cs
using Microsoft.Owin;
using System;

namespace OwinPipeline
{
    public class DebugMiddlewareOptions
    {
        public Action<IOwinContext> OnIncomingRequest { get; set; }
        public Action<IOwinContext> OnOutgoingResponse { get; set; }
    }
}



Comments

Popular posts from this blog

Understanding ASP.Net -Part 2- Building an Owin Pipeline

Understanding ASP.Net - Part1- Owin and Katana Introduction