Nothing Special   »   [go: up one dir, main page]

Middleware, Also Called Middleware Components. Each Middleware Component in The Request

Download as doc, pdf, or txt
Download as doc, pdf, or txt
You are on page 1of 9

ASP.

NET Core Middleware


 07/09/2019
 8 minutes to read

o +3

By Rick Anderson and Steve Smith

Middleware is software that's assembled into an app pipeline to handle requests and responses.
Each component:

 Chooses whether to pass the request to the next component in the pipeline.
 Can perform work before and after the next component in the pipeline.

Request delegates are used to build the request pipeline. The request delegates handle each
HTTP request.

Request delegates are configured using Run, Map, and Use extension methods. An individual
request delegate can be specified in-line as an anonymous method (called in-line middleware), or
it can be defined in a reusable class. These reusable classes and in-line anonymous methods are
middleware, also called middleware components. Each middleware component in the request
pipeline is responsible for invoking the next component in the pipeline or short-circuiting the
pipeline. When a middleware short-circuits, it's called a terminal middleware because it prevents
further middleware from processing the request.

Migrate HTTP handlers and modules to ASP.NET Core middleware explains the difference
between request pipelines in ASP.NET Core and ASP.NET 4.x and provides additional
middleware samples.
Create a middleware pipeline with IApplicationBuilder
The ASP.NET Core request pipeline consists of a sequence of request delegates, called one after
the other. The following diagram demonstrates the concept. The thread of execution follows the
black arrows.

Each delegate can perform operations before and after the next delegate. Exception-handling
delegates should be called early in the pipeline, so they can catch exceptions that occur in later
stages of the pipeline.

The simplest possible ASP.NET Core app sets up a single request delegate that handles all
requests. This case doesn't include an actual request pipeline. Instead, a single anonymous
function is called in response to every HTTP request.

C#
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
}
}
The first Run delegate terminates the pipeline.

Chain multiple request delegates together with Use. The next parameter represents the next
delegate in the pipeline. You can short-circuit the pipeline by not calling the next parameter. You
can typically perform actions both before and after the next delegate, as the following example
demonstrates:

C#
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
// Do work that doesn't write to the Response.
await next.Invoke();
// Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>


{
await context.Response.WriteAsync("Hello from 2nd delegate.");
});
}
}

When a delegate doesn't pass a request to the next delegate, it's called short-circuiting the
request pipeline. Short-circuiting is often desirable because it avoids unnecessary work. For
example, Static File Middleware can act as a terminal middleware by processing a request for a
static file and short-circuiting the rest of the pipeline. Middleware added to the pipeline before
the middleware that terminates further processing still processes code after their next.Invoke
statements. However, see the following warning about attempting to write to a response that has
already been sent.

Warning

Don't call next.Invoke after the response has been sent to the client. Changes to HttpResponse
after the response has started throw an exception. For example, changes such as setting headers
and a status code throw an exception. Writing to the response body after calling next:

 May cause a protocol violation. For example, writing more than the stated Content-
Length.
 May corrupt the body format. For example, writing an HTML footer to a CSS file.

HasStarted is a useful hint to indicate if headers have been sent or the body has been written to.

Order
The order that middleware components are added in the Startup.Configure method defines the
order in which the middleware components are invoked on requests and the reverse order for the
response. The order is critical for security, performance, and functionality.

The following Startup.Configure method adds middleware components for common app
scenarios:

1. Exception/error handling
o When the app runs in the Development environment:

 Developer Exception Page Middleware (UseDeveloperExceptionPage)


reports app runtime errors.
 Database Error Page Middleware (UseDatabaseErrorPage) reports
database runtime errors.
o When the app runs in the Production environment:

 Exception Handler Middleware (UseExceptionHandler) catches


exceptions thrown in the following middlewares.
 HTTP Strict Transport Security Protocol (HSTS) Middleware (UseHsts)
adds the Strict-Transport-Security header.

2. HTTPS Redirection Middleware (UseHttpsRedirection) redirects HTTP requests to


HTTPS.
3. Static File Middleware (UseStaticFiles) returns static files and short-circuits further
request processing.
4. Cookie Policy Middleware (UseCookiePolicy) conforms the app to the EU General Data
Protection Regulation (GDPR) regulations.
5. Authentication Middleware (UseAuthentication) attempts to authenticate the user before
they're allowed access to secure resources.
6. Session Middleware (UseSession) establishes and maintains session state. If the app uses
session state, call Session Middleware after Cookie Policy Middleware and before MVC
Middleware.
7. MVC (UseMvc) to add MVC to the request pipeline.

C#
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseSession();
app.UseMvc();
}

In the preceding example code, each middleware extension method is exposed on


IApplicationBuilder through the Microsoft.AspNetCore.Builder namespace.

UseExceptionHandler is the first middleware component added to the pipeline. Therefore, the
Exception Handler Middleware catches any exceptions that occur in later calls.

Static File Middleware is called early in the pipeline so that it can handle requests and short-
circuit without going through the remaining components. The Static File Middleware provides
no authorization checks. Any files served by it, including those under wwwroot, are publicly
available. For an approach to secure static files, see Static files in ASP.NET Core.

If the request isn't handled by the Static File Middleware, it's passed on to the Authentication
Middleware (UseAuthentication), which performs authentication. Authentication doesn't short-
circuit unauthenticated requests. Although Authentication Middleware authenticates requests,
authorization (and rejection) occurs only after MVC selects a specific Razor Page or MVC
controller and action.

The following example demonstrates a middleware order where requests for static files are
handled by Static File Middleware before Response Compression Middleware. Static files aren't
compressed with this middleware order. The MVC responses from UseMvcWithDefaultRoute
can be compressed.

C#
public void Configure(IApplicationBuilder app)
{
// Static files not compressed by Static File Middleware.
app.UseStaticFiles();
app.UseResponseCompression();
app.UseMvcWithDefaultRoute();
}

Use, Run, and Map


Configure the HTTP pipeline using Use, Run, and Map. The Use method can short-circuit the
pipeline (that is, if it doesn't call a next request delegate). Run is a convention, and some
middleware components may expose Run[Middleware] methods that run at the end of the
pipeline.

Map extensions are used as a convention for branching the pipeline. Map branches the request
pipeline based on matches of the given request path. If the request path starts with the given path,
the branch is executed.

C#
public class Startup
{
private static void HandleMapTest1(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
}

private static void HandleMapTest2(IApplicationBuilder app)


{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 2");
});
}

public void Configure(IApplicationBuilder app)


{
app.Map("/map1", HandleMapTest1);

app.Map("/map2", HandleMapTest2);

app.Run(async context =>


{
await context.Response.WriteAsync("Hello from non-Map delegate.
<p>");
});
}
}

The following table shows the requests and responses from http://localhost:1234 using the
previous code.

Request Response
localhost:1234 Hello from non-Map delegate.
localhost:1234/map1 Map Test 1
localhost:1234/map2 Map Test 2
localhost:1234/map3 Hello from non-Map delegate.

When Map is used, the matched path segments are removed from HttpRequest.Path and
appended to HttpRequest.PathBase for each request.
MapWhen branches the request pipeline based on the result of the given predicate. Any predicate
of type Func<HttpContext, bool> can be used to map requests to a new branch of the pipeline.
In the following example, a predicate is used to detect the presence of a query string variable
branch:

C#
public class Startup
{
private static void HandleBranch(IApplicationBuilder app)
{
app.Run(async context =>
{
var branchVer = context.Request.Query["branch"];
await context.Response.WriteAsync($"Branch used = {branchVer}");
});
}

public void Configure(IApplicationBuilder app)


{
app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
HandleBranch);

app.Run(async context =>


{
await context.Response.WriteAsync("Hello from non-Map delegate.
<p>");
});
}
}

The following table shows the requests and responses from http://localhost:1234 using the
previous code.

Request Response
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=master Branch used = master

Map supports nesting, for example:

C#
app.Map("/level1", level1App => {
level1App.Map("/level2a", level2AApp => {
// "/level1/level2a" processing
});
level1App.Map("/level2b", level2BApp => {
// "/level1/level2b" processing
});
});

Map can also match multiple segments at once:


C#
public class Startup
{
private static void HandleMultiSeg(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map multiple segments.");
});
}

public void Configure(IApplicationBuilder app)


{
app.Map("/map1/seg1", HandleMultiSeg);

app.Run(async context =>


{
await context.Response.WriteAsync("Hello from non-Map delegate.");
});
}
}

Built-in middleware
ASP.NET Core ships with the following middleware components. The Order column provides
notes on middleware placement in the request processing pipeline and under what conditions the
middleware may terminate request processing. When a middleware short-circuits the request
processing pipeline and prevents further downstream middleware from processing a request, it's
called a terminal middleware. For more information on short-circuiting, see the Create a
middleware pipeline with IApplicationBuilder section.

Middleware Description Order


Before HttpContext.User is needed.
Authentication Provides authentication support.
Terminal for OAuth callbacks.
Tracks consent from users for storing
Before middleware that issues
personal information and enforces
Cookie Policy cookies. Examples: Authentication,
minimum standards for cookie fields,
Session, MVC (TempData).
such as secure and SameSite.
Configures Cross-Origin Resource
CORS Before components that use CORS.
Sharing.
Exception Before components that generate
Handles exceptions.
Handling errors.
Before components that consume the
Forwards proxied headers onto the
Forwarded Headers updated fields. Examples: scheme,
current request.
host, client IP, method.
Checks the health of an ASP.NET
Terminal if a request matches a health
Health Check Core app and its dependencies, such
check endpoint.
as checking database availability.
HTTP Method Allows an incoming POST request to Before components that consume the
Middleware Description Order
Override override the method. updated method.
Redirect all HTTP requests to Before components that consume the
HTTPS Redirection
HTTPS. URL.
Security enhancement middleware Before responses are sent and after
HTTP Strict
that adds a special response header. components that modify requests.
Transport Security
Examples: Forwarded Headers, URL
(HSTS)
Rewriting.
Processes requests with MVC/Razor
MVC Terminal if a request matches a route.
Pages.
Interop with OWIN-based apps, Terminal if the OWIN Middleware
OWIN
servers, and middleware. fully processes the request.
Provides support for caching Before components that require
Response Caching
responses. caching.
Response Provides support for compressing Before components that require
Compression responses. compression.
Request Before localization sensitive
Provides localization support.
Localization components.
Routing Defines and constrains request routes. Terminal for matching routes.
Provides support for managing user Before components that require
Session
sessions. Session.
Provides support for serving static
Static Files Terminal if a request matches a file.
files and directory browsing.
Provides support for rewriting URLs Before components that consume the
URL Rewriting
and redirecting requests. URL.
Before components that are required
WebSockets Enables the WebSockets protocol.
to accept WebSocket requests.

You might also like