In my previous post, I looked at the code behind WebApplicationBuilder
, including some of its helper classes like ConfigureHostBuilder
and BootstrapHostBuilder
. At the end of the post, we had created an instance of the WebApplicationBuilder
and called Build()
to create a WebApplication
. In this post, we look at a bit of the code behind WebApplication
, and focus on how the middleware and endpoints are configured.
The WebApplication
: a brief recap
This post follows on directly from the previous post about WebApplicationBuilder
, as well as the introduction to WebApplication
, so I suggest you start with those posts if you haven't already read them. That said, I'm not going to be as deep in the code in this post compared to the previous one!
As I described in my previous post, WebApplicationBuilder
is where you do most of your application configuration, while WebApplication
is used for three separate things:
- It's where you configure your middleware pipeline, because it implements
IApplicationBuilder
. - It's where you configure your endpoints using
MapGet()
,MapRazorPages()
etc, because it implementsIEndpointRouteBuilder
. - It's what you run to actually start your application by calling
Run()
because it implementsIHost
.
At the end of the previous post, we saw that when you call Build()
on a WebApplicationBuilder
instance, a private instance of the generic host Host
is created, and passed to the WebApplication
constructor. It's from this point that this post continues, to give a little insight into WebApplication
before we start looking at middleware pipelines
WebApplication
: a relatively thin wrapper around three types.
In comparison to the WebApplicationBuilder
constructor, the WebApplication
constructor is relatively simple:
public sealed class WebApplication : IHost, IApplicationBuilder, IEndpointRouteBuilder, IAsyncDisposable
{
private readonly IHost _host;
private readonly List<EndpointDataSource> _dataSources = new();
internal IDictionary<string, object?> Properties => ApplicationBuilder.Properties;
internal static string GlobalEndpointRouteBuilderKey = "__GlobalEndpointRouteBuilder";
internal WebApplication(IHost host)
{
_host = host;
ApplicationBuilder = new ApplicationBuilder(host.Services);
Properties[GlobalEndpointRouteBuilderKey] = this;
}
// ...
}
The constructor and field initializers do 4 primary things:
- Save the provided
Host
in the_host
field. This is the sameHost
type as when you use the generic host directly, as in the ASP.NET Core 3.x/5. - Create a new list of
EndpointDataSource
. These are used to configure the endpoints in your application, including Razor Pages, Controllers, API endpoints, and the new "minimal" APIs. - Create a new instance of
ApplicationBuilder
. This is used to build the middleware pipeline, and is essentially the same type that's been used since version 1.0! - Set the
__GlobalEndpointRouteBuilder
property on theApplicationBuilder
. This property is used to "communicate" to other middleware that we're using the newWebApplication
, which has some different defaults, as you'll see a bit later.
WebApplication
mostly delegates the implementation of the IHost
, IApplicationBuilder
, and IEndpointRouteBuilder
to its private properties, and implements them explicitly. For example, for IApplicationBuilder
, much of the implementation is delegated to the ApplicationBuilder
created in the constructor:
IDictionary<string, object?> IApplicationBuilder.Properties => ApplicationBuilder.Properties;
IServiceProvider IApplicationBuilder.ApplicationServices
{
get => ApplicationBuilder.ApplicationServices;
set => ApplicationBuilder.ApplicationServices = value;
}
IApplicationBuilder IApplicationBuilder.Use(Func<RequestDelegate, RequestDelegate> middleware)
{
ApplicationBuilder.Use(middleware);
return this;
}
Where things get interesting, is when you call RunAsync()
, which builds the middleware pipeline.
Starting the WebApplication
and building the middleware pipeline
The standard way to start your application is to call app.Run()
on your WebApplication
. This calls into WebApplication.RunAsync
, which in turn calls an IHost
extension method, HostingAbstractionsHostExtensions.RunAsync()
, which is the same one that you'd use with the generic host in 3.x/5. This in turn calls IHost.StartAsync
, which starts the complex startup interactions I've written about previously.

As shown in the diagram above, the Host
runs the IHostedService
s registered in the application as part of the startup sequence. As of the "great re-platforming" in ASP.NET Core 3.x, the web server also runs as an IHostedService
, so this is when the middleware pipeline is built and Kestrel starts listening.
GenericWebHostService
is where the middleware pipeline is built, and finally is passed to Kestrel to run your app. There's nothing different in this process for WebApplication
compared to the generic Host
, so rather than dive through the (extensive!) code, we'll take a look at how WebApplication
sets up the middleware pipeline instead.
WebApplication
's middleware pipeline
One of the big differences with WebApplication
compared to the generic host is that WebApplication
sets up various middleware by default. In the previous post I showed that WebApplicationBuilder
calls GenericHostBuilderExtensions.ConfigureWebHostDefaults()
. This is commonly used with the generic host too, and sets up several defaults:
- Configures Kestrel
- Adds the HostFiltering middleware
- Adds the ForwardedHeaders middleware if the
ASPNETCORE_FORWARDEDHEADERS_ENABLED
environment variable is set totrue
. - Enables IIS integration
In addition to these, WebApplicationBuilder
sets up extra middleware. This is where the ConfigureApplication
method I mentioned in the previous post comes in. Depending on how you configure your WebApplication
, ConfigureApplication
adds additional middleware to your pipeline.
The code for this is a little complex, as it requires handling multiple edge cases. Rather than try and keep track of the code in too much detail, we'll look at some examples instead, and see what the resulting middleware pipeline looks like.
The empty pipeline
We'll start with the most basic (and somewhat pointless) application, in which we don't add any extra middleware or endpoints to the application:
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();
app.Run();
This setup is the most basic of configurations, and results in a middleware pipeline that contains:
HostFilteringMiddleware
DeveloperExceptionPageMiddleware
- An "empty" middleware built from
WebApplication
'sApplicationBuilder
.
something like the following:

The HostFilteringMiddleware
is added thanks to the hidden call to ConfigureWebHostDefaults()
, as described in the last section. The ForwardedHeadersMiddleware
wasn't added as the environment variable wasn't added.
The DeveloperExceptionPage
is now added automatically when you're running in the Development
environment, adding the following to the start of every middleware pipeline:
if (context.HostingEnvironment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
The final piece of middleware in the pipeline is built using the middleware added directly to the WebApplication
's ApplicationBuilder
in Program.cs. In this example we didn't add any, so we essentially add an "empty" pipeline inside the middleware.
Obviously an application with just the default middleware isn't much use, so let's do the basics and add some middleware to WebApplication
.
A pipeline with extra middleware
In this example, we add some additional middleware to the pipeline: the static file middleware, and the HTTPS redirection middleware:
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();
// Add some additional middleware
app.UseHttpsRedirection();
app.UseStaticFiles();
app.Run();
With this setup we still have the same three core middleware as in the previous section, but the WebApplication.ApplicationBuilder
pipeline now contains two additional middleware
HostFilteringMiddleware
DeveloperExceptionPageMiddleware
- The
WebApplication.ApplicationBuilder
pipeline, containingHttpsRedirectionMiddleware
StaticFilesMiddleware
so now we have something that looks like this:

We still don't have any endpoints yet, so in the next example we implement a "Hello World!" endpoint:
A "Hello world" pipeline
In the following example, we add a single endpoint for the home page which returns the text "Hello World!" into the example I showed in the previous case:
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
// Add a single endpoint
app.MapGet("/", () => "Hello World!");
app.Run();
Adding an endpoint using MapGet()
adds an entry to the WebApplication
's collection of EndpointDataSource
s, which causes the ConfigureApplication
to automatically add additional middleware:
HostFilteringMiddleware
DeveloperExceptionPageMiddleware
EndpointRoutingMiddleware
(AKARoutingMiddleware
)- The
WebApplication.ApplicationBuilder
pipeline, containingHttpsRedirectionMiddleware
StaticFilesMiddleware
EndpointMiddleware
Notice that the RoutingMiddleware
is automatically added to the middleware pipeline before the start of the pipeline defined in Program.cs, and the EndpointMiddleware
is automatically added to the pipeline at the end. The resulting pipeline looks something like the following:

I find it very interesting that WebApplication
hides away the routing middleware from users. The fact that the RoutingMiddleware
and EndpointMiddleware
are so closely related, and that they impose strict ordering requirements (e.g. the AuthorizationMiddleware
must be placed between them), makes them a relatively difficult concept for newcomers to grasp. With .NET 6 and WebApplication
, there's fewer things for users to worry about!
It's not all rosy though. Some middleware generally assumes that it will be called before
UseRouting()
. For example, theExceptionHandlerMiddleware
,RewriterMiddleware
,StatusCodePagesMiddleware
all had to be updated to handle this new pattern.
But what if you need the RoutingMiddleware
to be at a specific point in the pipeline? Maybe you have middleware that needs to be before the RoutingMiddleware
, for example?
A pipeline with UseRouting()
In the next example, we explicitly add the EndpointRoutingMiddleware
to the WebApplication.ApplicationBuilder
pipeline by calling UseRouting()
. This is closer to what you'd expect to see in a .NET 3.x/5 app, where UseRouting()
is placed in the middle of your pipeline
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
// EndpointRoutingMiddleware in the middle of the pipeline.
app.UseRouting();
app.MapGet("/", () => "Hello World!");
app.Run();
As you might expect, the resulting pipeline contains all the same middleware as before, but arranged in a slightly different order:
HostFilteringMiddleware
DeveloperExceptionPageMiddleware
- The
WebApplication.ApplicationBuilder
pipeline, containingHttpsRedirectionMiddleware
StaticFilesMiddleware
EndpointRoutingMiddleware
(RoutingMiddleware
)
EndpointMiddleware
Which can be visualised something like this:

There's an interesting interplay between the "main" middleware pipeline, and the WebApplication.ApplicationBuilder
pipeline, where the WebApplicationBuilder
has to be careful to preserve the overall order when UseRouting()
is specified. Hopefully this won't often be required, but when it is, you should still be able to use WebApplicationBuilder
if you wish, rather than being forced to fall back to the generic host.
Finally, lets look at what happens if you add the other side of endpoint routing, the EndpointMiddleware
.
A pipeline with UseEndpoints()
As I mentioned earlier, endpoint routing works with a pair of middleware, the EndpointRoutingMiddleware
(AKA RoutingMiddleware
) which selects the endpoint, and the EndpointMiddleware
, which executes the endpoint. The EndpointMiddleware
is typically added to the pipeline at the end, by calling UseEndpoints()
, but with WebApplicationBuilder
it's added automatically. Let's see what happens if we also add it explicitly.
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapGet("/", () => "Hello World!");
// Add the EndpointMiddleware explicitly, and add a new endpoint
app.UseEndpoints(x =>
{
x.MapGet("/ping", () => "pong")
});
app.Run();
With the above configuration, you end up with the following:
HostFilteringMiddleware
DeveloperExceptionPageMiddleware
- The
WebApplication.ApplicationBuilder
pipeline, containingHttpsRedirectionMiddleware
StaticFilesMiddleware
,EndpointRoutingMiddleware
(RoutingMiddleware
), andEndpointMiddleware
EndpointMiddleware
If you read the above list closely, you'll see that the EndpointMiddleware
appears twice! That's not a bug, but rather a consequence of the fact that the WebApplicationBuilder
can't tell if the EndpointMiddleware
was added to the WebApplication.ApplicationBuilder
pipeline. Luckily the "outer" middleware is completely benign.

You might also notice that I registered endpoints both directly on WebApplication
and also in the UseEndpoints()
call. However, those endpoints are all registered with the WebApplication
's _dataSources
, thanks to some tricky twiddling with properties in WebApplicationBuilder. The end result is that the second middleware is never called.
That covers most of the edge cases related to building the middleware pipeline in WebApplication
. I think it's quite interesting to see the changes made here, though as I've mentioned before, I wish there was a better delineation between middleware and endpoints in WebApplication
. For example, I wish it looked more like this:
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
// I wish the "Endpoints" property was a thing
app.Endpoints.MapGet("/", () => "Hello World!");
app.Endpoints.MapGet("/ping", () => "pong");
app.Run();
But as it stands, there's no denying that WebApplication
and WebApplicationBuilder
provide a simpler API than the generic Host
implementation they wrap. Hopefully that makes it easier to teach to newcomers!
Summary
In this post we saw how the new "minimal hosting" WebApplication
builds the middleware pipeline for your application. We started by looking at the constructor of the WebAppliation
, to get a feel for how it works, and then looked at some example pipelines. For each pipeline I showed the code you write, and the resulting middleware pipeline. This showed how WebApplication
automatically (conditionally) adds the DeveloperExceptionPageMiddleware
at the start of your pipeline, and wraps your pipeline in the routing middleware. Overall, this can simplify middleware ordering issues, but it's good to be aware of what's happening.
Implementing the minimal hosting API required quite a few changes to middleware to accommodate the new pattern. In the next post, we look at some other things in .NET Core that had to change!