Middleware is software that's assembled into an app pipeline to handle requests and responses. Each component performs operations on an HttpContext and:
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.
The request handling pipeline is composed as a series of middleware components. Each component performs operations on an HttpContext and either invokes the next middleware in the pipeline or terminates the request.
Program.cs
var builder =WebApplication.CreateBuilder(args);// Add services to the container.builder.Services.AddRazorPages();builder.Services.AddControllersWithViews();var app =builder.Build();// Configure the HTTP request pipeline.if (!app.Environment.IsDevelopment()){app.UseExceptionHandler("/Error");app.UseHsts();}app.UseHttpsRedirection();app.UseStaticFiles();app.UseAuthorization();app.MapGet("/hi", () =>"Hello!");app.MapDefaultControllerRoute();app.MapRazorPages();app.Run();
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.
var builder =WebApplication.CreateBuilder(args);var app =builder.Build();app.Use(async (context, next) => { // Do work that can write to the Response.awaitnext.Invoke(); // Do logging or other work that doesn't write to the Response.});app.Run();
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.
If you don't call app.UseRouting, the Routing middleware runs at the beginning of the pipeline by default.
The Endpoint middleware in the preceding diagram executes the filter pipeline for the corresponding app type—MVC or Razor Pages.
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.
Map supports nesting.
Map can also be used to match multiple segments at once.
MapWhen branches the request pipeline based on the result of the given predicate.
The following table shows the requests and responses from http://localhost:1234 using the preceding code.
Request
Response
localhost:1234
Hello from non-Map delegate.
localhost:1234/map1
Map Test
localhost:1234/map1/seg1
Multiple Segment Test
localhost:1234/?branch=main
Branch used = main
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.
Run delegates don't receive a next parameter. The first Run delegate is always terminal and terminates the pipeline. Some middleware components may expose Run[Middleware] methods that run at the end of the pipeline:
var builder =WebApplication.CreateBuilder(args);var app =builder.Build();app.UseHttpsRedirection();app.UseStaticFiles();app.Run(async context => {awaitcontext.Response.WriteAsync("Hello from 2nd delegate.");});app.Run();
If another Use or Run delegate is added after the Run delegate, it's not called.
Test Middleware
Middleware can be tested in isolation with TestServer. It allows you to:
Instantiate an app pipeline containing only the components that you need to test.
Send custom requests to verify middleware behavior.
[Fact]publicasyncTaskMiddlewareTest_ReturnsNotFoundForRequest() { // Arrangeusingvar host =awaitnewHostBuilder() .ConfigureWebHost(webBuilder => { webBuilder .UseTestServer() .ConfigureServices(services => {services.AddMyServices(); }) .Configure(app => {app.UseMiddleware<MyMiddleware>(); }); }) .StartAsync(); // Action // send a request using HttpClientvar response =awaithost.GetTestClient().GetAsync("/"); // send a request using HttpContext var server =host.GetTestServer();server.BaseAddress=newUri("https://example.com/A/Path/");var context =awaitserver.SendAsync(c => {c.Request.Method=HttpMethods.Post;c.Request.Path="/and/file.txt";c.Request.QueryString=newQueryString("?and=query"); }); // AssertAssert.NotEqual(HttpStatusCode.NotFound,response.StatusCode);Assert.Equal(HttpStatusCode.NotFound,response.StatusCode);Assert.True(context.RequestAborted.CanBeCanceled);Assert.Equal(HttpProtocol.Http11,context.Request.Protocol);Assert.Equal("POST",context.Request.Method);Assert.Equal("https",context.Request.Scheme);Assert.Equal("example.com",context.Request.Host.Value);Assert.Equal("/A/Path",context.Request.PathBase.Value);Assert.Equal("/and/file.txt",context.Request.Path.Value);Assert.Equal("?and=query",context.Request.QueryString.Value);Assert.NotNull(context.Request.Body);Assert.NotNull(context.Request.Headers);Assert.NotNull(context.Response.Headers);Assert.NotNull(context.Response.Body);Assert.Equal(404,context.Response.StatusCode);Assert.Null(context.Features.Get<IHttpResponseFeature>().ReasonPhrase);}
Custom Middleware
Creating a middleware component by calling Microsoft.AspNetCore.Builder.UseExtensions.Use. The Use extension method adds a middleware delegate defined in-line to the application's request pipeline.
There are two overloads available for the Use extension:
One takes a HttpContext and a Func<Task>.
Invoke the Func<Task> without any parameters.
The other takes a HttpContext and a RequestDelegate.
Invoke the RequestDelegate by passing the HttpContext.
usingSystem.Globalization;namespaceMiddleware.Example;publicclassRequestCultureMiddleware {privatereadonlyRequestDelegate _next; // A public constructor with a parameter of type RequestDelegate.publicRequestCultureMiddleware(RequestDelegate next) { _next = next; }/* A public method named Invoke/InvokeAsync. This method must: - Return a Task. - Accept a first parameter of type HttpContext. */publicasyncTaskInvokeAsync(HttpContext context) {var cultureQuery =context.Request.Query["culture"];if (!string.IsNullOrWhiteSpace(cultureQuery)) {var culture =newCultureInfo(cultureQuery);CultureInfo.CurrentCulture= culture;CultureInfo.CurrentUICulture= culture; } // Call the next delegate/middleware in the pipeline.await_next(context); }}// exposing the middleware through IApplicationBuilderpublicstaticclassRequestCultureMiddlewareExtensions {publicstaticIApplicationBuilderUseRequestCulture(thisIApplicationBuilder builder) {returnbuilder.UseMiddleware<RequestCultureMiddleware>(); }}