Code Focused

Inject Custom Middleware into the ASP.NET Pipeline with OWIN

You can easily slide middleware and other custom code into the ASP.NET pipeline by taking advantage of the Microsoft implementation of the OWIN specification in the coming Visual Studio 2015.

Among the many recent changes to ASP.NET, one of the most exciting is the Microsoft implementation of the Open Web Interface for .NET (OWIN) specification. The purpose of OWIN is to define a standardized interface between Web servers such as IIS and the .NET Web applications they host.

If you've been following these changes, you've probably also heard of something called Project Katana. While OWIN is a specification, Katana is the implementation of that specification. However, now when most people refer to either the spec or the implementation, it' just referred to as "OWIN." In fact, Katana uses the namespace Microsoft.Owin.

The real beauty of OWIN is that it gives you direct access to the ASP.NET pipeline in a relatively friction-free way. Things that previously would have required creating HTTP modules and handlers can now be simplified to actions passed to OWIN. And if you want to self-host without IIS, even that is a lot easier than it was without OWIN.

As you read through, please note that all samples here were created using Visual Studio 2015 CTP 6 in April 2015. If you notice something no longer working in later versions please leave me a comment so that I can update the article to reflect those changes.

Hello Middleware
After creating a new ASP.NET 5 application, you'll see a Startup.cs file at the root of the project. This file now contains much of the application's configuration (and as you'll notice, web.config no longer exists). This file is also where middleware is wired up or even directly inserted into the pipeline. In my first simple example, I will put the middleware right into the top of the Configure method, like in Listing 1.

} Listing 1: The "Hello Middleware!" Middleware Code Directly Injected into the Pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
  ILoggerFactory loggerfactory)
{
  app.Use(async (context, next) =>
  {
    if (context.Request.Path.Value == "/hello")
      using (var writer = new StreamWriter(context.Response.Body))
      {
        await writer.WriteAsync("Hello Middleware!");
      }
    else
    {
      await next();
    }
  });

  // More code

Walking through the code, the first line is the app.Use statement, which takes an async function containing the code to be executed as part of the pipeline. This isn't called immediately, but rather is added to a queue of middleware that will be executed whenever a request comes in.

The middleware receives two parameters: the current context and a task representing the next middleware in the queue. When executed, the if statement evaluates whether the path of the request is "/hello," and if it is, it writes, "Hello Middleware!" to the response stream, like in Figure 1. Because next isn't called in this branch, no further middleware will be executed, and the response will be sent to the browser. If the path is anything other than "/hello," no action will take place and the next and subsequent middleware will be executed.

[Click on image for larger view.] Figure 1. A Request for "/hello" Will Result in Seeing "Hello Middleware!"

The Power of Middleware
Because the middleware has access to every part of the request and response, it can be used to do almost anything imaginable. And not only can you view all of that contextual information, you can also change the response as it passes through your middleware. If you have some special URLs that shouldn't be accessed, for example, you could set the response status code to 401 (unauthorized) when a request is made to that URL.

The Startup.cs file is a place for configuration, so it doesn't usually make sense for middleware to exist there, but rather to be called there. In Listing 2 I've created a middleware behind an extension method on IApplicationBuilder. This middleware checks to see if the request is going to the admin section of the site, and only allows the request to go through if it's coming from a local IP address.

Listing 2: Middleware Code Refactored To Be More Generic and Hide Behind an Extension Method
public static class MiddlewareExtensions
{
  public static void AdminFromLocalAddressesOnly(this IApplicationBuilder app)
  {
    app.Use(async (context, next) =>
    {
      if (context.Request.Path.Value.StartsWith("/admin")
        && !context.GetFeature<IHttpConnectionFeature>().IsLocal)
      {
      // Request to admin and IP is not local
      context.Response.StatusCode = 401;
      }
      else
      {
        await next();
      }
    });

  }
} 

And in Startup.cs I call it inside the Configure method, like this:

app.AdminFromLocalAddressesOnly();

Using Dependency Injection
Often when writing middleware it can be useful to bring in other services in order to offload some of the functionality. With the dependency injection features built into ASP.NET 5, it's easy to wire up services and use them in your middleware. In this example I've created an interface for an IP logger:

public interface IIpLogger
{
  void LogRequest(string ip, string requestPath);
}
I've also created a class, SimpleIpLogger, implementing that interface. Then in the ConfigureServices method in Startup.cs I've added a line to wire up the SimpleIpLogger as a singleton:
public void ConfigureServices(IServiceCollection services)
{
  services.AddSingleton<IIpLogger, SimpleIpLogger>();

  // Other services
}
I then created the middleware inside of my MiddlewareExtensions class, like in the previous example:
public static void LogIpAddresses(this IApplicationBuilder app)
{
  app.Use<IIpLogger>(async (context, next, logger) =>
  {
    var remoteIp = 
      context.GetFeature<IHttpConnectionFeature>().RemoteIpAddress.ToString();
    var requestPath = context.Request.Path.Value;
    logger.LogRequest(remoteIp, requestPath);
    await next();
  });
}

The app.Use call in this middleware is a bit different than before. I'm using an overload that allows me to specify a dependency that will be resolved by the dependency resolver and passed into the middleware. There are overloads for up to four services to be passed in like this. Because I wired up IIpLogger to SimpleIpLogger, the logger parameter of the middleware function will be given an instance of a SimpleIpLogger. Then I just grab the remote IP address and path from the request, pass them into the logger, and move on to the next middleware.

The last step is to enable the middleware in the Startup.cs file, like this:

app.LogIpAddresses();

What It All Means
Writing middleware in OWIN is an easy way to insert functionality into your application at a low level, and is a great place to deal with cross-cutting concerns such as logging and authentication. Dependency injection is now also an integral part of OWIN and can be used to inject services into your middleware.

If you're interested in learning more about the concept of dependency injection, check out my seven-part series on dependency injection starting here.

About the Author

Ondrej Balas owns UseTech Design, a Michigan development company focused on .NET and Microsoft technologies. Ondrej is a Microsoft MVP in Visual Studio and Development Technologies and an active contributor to the Michigan software development community. He works across many industries -- finance, healthcare, manufacturing, and logistics -- and has expertise with large data sets, algorithm design, distributed architecture, and software development practices.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube