If you’ve newed up an ASP.NET project template you’re most probably using the OWIN pipeline and are familiar with middleware such as Use
, Map
, Run
or UseCookieAuthentication
.
These are all extension methods for IAppBuilder
, found in your OWIN Startup class, calling logic that will manipulate the current OWIN context or environment.
Using the OwinMiddleware
abstract class found in Microsoft.Owin
we can start to create our own OWIN Middleware components that we can then integrate with our existing pipeline or distribute as a package.
OwinMiddleware
The abstract OwinMiddleware
class looks like this:
/// <summary>
/// An abstract base class for a standard middleware pattern.
/// </summary>
public abstract class OwinMiddleware {
/// <summary>
/// Instantiates the middleware with an optional pointer to the next component.
/// </summary>
/// <param name="next"></param>
protected OwinMiddleware(OwinMiddleware next) {
Next = next;
}
/// <summary>
/// The optional next component.
/// </summary>
protected OwinMiddleware Next { get; set; }
/// <summary>
/// Process an individual request.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public abstract Task Invoke(IOwinContext context);
}
Instead of using the usual IDictionary<string, object>
environment variable, OwinMiddleware
uses the strongly typed abstractions found in Microsoft.Owin.IOwinContext
.
This includes abstractions such as request and response objects, saving us from searching the OWIN environment by key and a whole lot of casting.
A good takeaway from the above is that the Next property is optional, meaning we can stop the pipeline ourselves by simply not invoking the next OwinMiddleware
.
To implement OwinMiddleware
we will need a constructor that calls the base constructor and an implementation of the Invoke
method. Let's spin up an example.
Example OwinMiddleware Implementation
To start off, we’ll create a new empty web application. Do not include any authentication or core packages/folders (i.e. no MVC or Web API, empty will do).
Required Packages
Install-Package Microsoft.Owin.Host.SystemWeb
This will also install the necessary dependencies of Owin
and Microsoft.Owin
.
OwinMiddleware
Next we’ll implement OwinMiddleware
to return the HTTP status code 418: I’m a Teapot.
public class TeapotMiddleware : OwinMiddleware {
public TeapotMiddleware(OwinMiddleware next) : base(next) {
}
public async override Task Invoke(IOwinContext context) {
context.Response.StatusCode = 418;
// await this.Next.Invoke(context);
}
}
As we know from looking at the base class, the Next
property can be null and in our case it will be.
If you wanted to call the next middleware in the pipeline however, you would use the above commented out code of this.Next.Invoke(context)
in your own Invoke
method.
Startup
Now all we need is our OWIN startup class. We have two options for registering our middleware with the pipeline:
public class Startup {
public void Configuration(IAppBuilder app) {
// app.Use(typeof(TeapotMiddleware));
app.Use<TeapotMiddleware>();
}
}
Now when you start you project you’ll get something like the following:
HTTP/1.1 418
Server: Microsoft-IIS/8.0
X-Powered-By: ASP.NET
Date: Sat, 29 Aug 2015
Content-Length: 0
Congratulations. It’s a teapot.
UseOwinMiddleware
We now have a working OWIN Middleware component, but that app.Use
doesn’t look very professional, especially if we’re going to share it with others.
We can change this by creating an extension method for our middleware.
internal static class TeapotMiddlewareHandler {
public static IAppBuilder UseTeapotMiddleware(this IAppBuilder app) {
app.Use<TeapotMiddleware>();
return app;
}
}
Now we can register our middleware in the pipeline like so:
app.UseTeapotMiddleware();
OwinMiddlewareOptions
If you want to pass in a class to your Owin Middleware you’ll need to expand on the constructor. For this example I’ll use the common use case of passing in some configuration options.
Options Class
public sealed class TeapotOptions {
public string Biscuit { get; set; }
}
Updated Middleware
private readonly TeapotOptions options;
public TeapotMiddleware(OwinMiddleware next, TeapotOptions options) : base(next) {
this.options = options;
}
public async override Task Invoke(IOwinContext context) {
context.Response.Cookies.Append("Biscuit", this.options.Biscuit);
context.Response.StatusCode = 418;
}
Updated Handler
To set our options in the middleware constructor we pass them in as params object[] args
:
public static IAppBuilder UseTeapotMiddleware(
this IAppBuilder app, TeapotOptions options) {
app.Use<TeapotMiddleware>(options);
return app;
}
Updated Startup
Now we can set our options in our startup class:
app.UseTeapotMiddleware(new TeapotOptions { Biscuit = "Hobnob" });
And get a new response of:
HTTP/1.1 418
Server: Microsoft-IIS/8.0
Set-Cookie: Biscuit=Hobnob;
X-Powered-By: ASP.NET
Date: Sat, 29 Aug 2015
Content-Length: 0
Dependency Injection
I’ve used a relatively simple use case here but things are not as clean when you want to pass in a class with its own dependencies and we want to start using dependency injection.
Since the constructor for OwinMiddleware
requires OwinMiddleware
next, we cannot simply register the middleware itself and allow our DI container to resolve its dependencies.
From what I can see, if you do need to add another dependency to your middleware you have two options: new it up with a concrete implementation (bad) or pass it in using your project’s Dependency Resolver (anti-pattern, also different between MVC and Web API). If you find any nicer methods please tell me, as I’d love to know and update this section. This has apparently been resolved for ASP.NET 5, but that doesn’t help us now…
Writing to the Response Body
Whilst I won’t go into the full detail of how to manage the reading and writing of a response, I will give you a basic example and share a huge gotcha that caught me out. After banging my head against this for days, I ended up finding this line in a self-published book on Amazon by the fantastic Badrinarayanan Lakshmiraghavan:
In [the] case of streaming hosts, as soon as a middleware writes to the response stream, the client gets the response along with the response headers - Badrinarayanan Lakshmiraghavan, OWIN and Microsoft Katana 101
This means that once you start writing to the response body, you can no longer write to the response headers.
If you try modifying the response headers after writing to the response body in a separate middleware component, you will be met with a lovely Yellow Screen of Death with the message: 'Cannot register for 'OnSendingHeaders' event after response headers have been sent.'
.
However, if you try doing this within the same middleware component, no exceptions or warnings are thrown or given; any new response headers will just not be assigned.
Let's expand on my previous example by adding a couple more things to the pipeline.
Logging In and Sending a File
First we will login a user using the IAuthenticationManager found in the OwinContext.
This also requires the CookieAuthentication
middleware found in Microsoft.Owin.Security.Cookies
.
To send back a file we’ll need to use the SendFileAsync
method found in Microsoft.Owin.StaticFiles
.
This will also pull in the dependency Microsoft.Owin.FileSystems
which can be used to display folder directories using OWIN.
Middleware Invoke Method
public async override Task Invoke(IOwinContext context) {
// user login
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, "Scott"));
claims.Add(new Claim(ClaimTypes.Email, "[email protected]"));
var id = new ClaimsIdentity(claims, "cookie");
context.Authentication.SignIn(id);
// send file
context.Response.ContentType = "image/jpeg";
await context.Response.SendFileAsync("/img/teapot.jpg");
}
Startup
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = "cookie"
});
app.UseTeapotMiddleware(new TeapotOptions { Biscuit = "Hobnob" });
app.UseOrderMiddleware();
If we run our middleware in this order we receive expected results:
with the headers:
HTTP/1.1 418
Cache-Control: no-cache
Pragma: no-cache
Content-Type: image/jpeg
Expires: -1
Server: Microsoft-IIS/8.0
Set-Cookie: Biscuit=Hobnob; path=/
Set-Cookie: .AspNet.cookie=9ZjAu2tTrcSWPIpVcdkl0bOw68geFrO6GFJJMwjHHiOZi-4Uhdwt5wGHBesYUFE4mYmJ6rA2sgRCvmwSij5Y3gRlv6_TEVLiGzrUCDsR9AAi-zDwIFlIYtwLkYzi30rODqz3ISS-x6NsudDrSOot-gy12hdmJibbFvGsPcRpg4vI4EZjX7VobUCUaSEQMPZc-9S4jQIRnRgKuctRrKTEhl2IH9V56EA329Ga2_S7gtPWrJtLSVvbqBx557cx3r7BPMg978EpGA5sTctzgqfUYuUGFcPqxoea8_7JSzNyzteSrgBagKSt9c8TL4au4Xbr; path=/; HttpOnly
X-Powered-By: ASP.NET
Date: Sun, 30 Aug 2015
Content-Length: 32067
Now lets break the rules and switch our login and file send logic around. Now we still get our teapot but the headers have changed significantly:
HTTP/1.1 418
Content-Type: image/jpeg
Server: Microsoft-IIS/8.0
Set-Cookie: Biscuit=Hobnob; path=/
X-Powered-By: ASP.NET
Date: Sun, 30 Aug 2015
Content-Length: 32067
No authenticated users and no exceptions.
OWIN and Microsoft Katana 101
If you want to take this further, I have to recommend OWIN and Microsoft Katana 101 by Badrinarayanan Lakshmiraghavan. Whilst it’s a little out of date now, you’ll find a lot of sanity saving snippets like the quote earlier that are only ever stated in this book.
Microsoft.Owin Dependency
It is worth pointing out that I have taken a dependency on Microsoft.Owin
in these examples by using the OwinMiddleware
abstract class within my pipeline. To remove this dependency you’ll need to instead use Func<IDictionary<string, object>>
instead of OwinMiddleware
in your constructor and IDictionary<string, object> env
in your Invoke
method.
You can still use the IOwinContext
abstractions within your Invoke method however, as this would not make the pipeline itself dependent on Micorsoft.Owin
, only the logic in your middleware. You can convert your env parameter like so:
IOwinContext context = new OwinContext(env);
GitHub
I’ve uploaded a working solution using the above code to GitHub. You can download this and easily run it to jump in and start messing with Owin middleware.