What is middleware?
Simply, middleware is code that becomes part of a pipeline to handle requests and responses in a web app. In ASP.NET Core, middleware is a big deal! All requests are handled by a pipeline of middleware.
If you’ve created an ASP.NET Core app before, you are probably aware of the UseDeveloperExceptionPage
middleware. This middleware adds special exception handling. If an exception is thrown later in the pipeline, the exception bubbles up to this middleware, which returns a nice page with details as a response to the request. Because middleware is a pipeline, order matters. For the developer exception page, it’s important that it is one of the first middleware added to the pipeline.
Find the code for this post on GitHub!
Getting started
For this guide, I’m starting with a project created using dotnet new web
. This command creates an ASP.NET Core project with a minimum amount of boilerplate.
// Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
Even in this most basic project, we already have two middleware components: UseDeveloperExceptionPage
and Run
. We already know about UseDeveloperExceptionPage
. Run
is a terminal middleware. A terminal middleware stops processing additional middleware for the request.
Use
is a another middleware we can use. It is not necessarily a terminal middleware, like Run
. It can be terminal or non-terminal depending on whether the next middleware is called. Use
will be how we write our first custom middleware.
Custom Middleware
Let’s add some custom middleware to our Startup.Configure
method.
- public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILogger<Startup> logger)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
+ app.Use(async (context, next) =>
+ {
+ logger.LogInformation("Request started");
+ await next.Invoke();
+ logger.LogInformation("Request finished");
+ });
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
We added a logger and an app.Use
section. This is always a non-terminal middleware, because we call next.Invoke
for all code paths within the middleware. To try this out, run dotnet run
and navigate to http://localhost:5000
. You should see Hello World!
on the page, and something like this in the console:
info: CreatingMiddleware.Startup[0]
Request started
info: CreatingMiddleware.Startup[0]
Request finished
Enhance!
So we’ve created a super simple custom middleware that logs when a request is started and completed. Cool… but what else can we do? Let’s make some changes to our app.Use
block…
app.Use(async (context, next) =>
{
if (context.Request.Path.ToString().StartsWith("/test"))
{
await context.Response.WriteAsync("Welcome to Test");
}
else
{
await next.Invoke();
}
});
Now, our middleware is terminal when the request path starts with /test
, returning the response Welcome to Test
. To verify, navigate to http://localhost:5000/test
. For routes that don’t start with /test
, we just pass-through to the next middleware.
You’ve likely noticed that we have access to the entire HttpContext
in our middleware. We can check whether the user is authenticated, pull information out of the route, and read the body of the request. Of course, those are just some examples of the information we can get from HttpContext
.
That’s All for Now
Middleware is a pretty deep rabbit-hole, so I’m going to call this post here. I have more to write about, but that will have to come in later posts - I don’t want to overload post with everything. Enter your email below so you know when Part 2 comes out!