WebApplication and the Host
Program.cs has two phases: configure a WebApplicationBuilder, build a WebApplication, then call Run to start the server and wait.
static WebApplicationBuilder WebApplication.CreateBuilder(string[] args)Every ASP.NET Core program has the same shape. First you configure, then you run. The object you configure is a WebApplicationBuilder. The object you run is a WebApplication. Together they are called the host: the machinery that owns configuration, logging, the server and the application's lifetime.
The two phases
WebApplication.CreateBuilder(args) returns a builder that already has defaults applied. Configuration is read from appsettings.json and from environment variables. Logging is sent to the console. Kestrel is selected as the server. You then add to two places: builder.Services, which is where the application's own types are registered, and builder.Configuration, which is where settings come from.
builder.Build() returns the WebApplication. From that point the builder is finished. On the application object you add middleware and endpoints instead.
What Run starts
app.Run() does not return quickly. It starts Kestrel listening, then blocks until the process is asked to stop, by Ctrl+C or by the SIGTERM signal a container runtime sends. It then shuts down and returns, so code written after it executes only during shutdown. await app.RunAsync() is the same thing in asynchronous form.
The shorthand
WebApplication.Create(args) performs CreateBuilder and Build in one call. It is the same code path, but it leaves you no builder, so it suits only a program with nothing to register.
Example
var builder = WebApplication.CreateBuilder(args);
// Phase 1: configure. builder.Configuration already holds appsettings.json
// and the environment variables. The indexer returns string?, so supply a
// fallback to get a non-null string.
string greeting = builder.Configuration["Greeting"] ?? "hello";
var app = builder.Build();
// Phase 2: describe behaviour. The builder is not used again.
app.MapGet("/", () => greeting);
app.MapGet("/env", () => app.Environment.EnvironmentName);
// Starts the server and blocks until the process is told to stop.
app.Run();
// Reached only during shutdown, after Run() has returned.
Console.WriteLine("stopped");When to use it
- Add a configuration value or a service registration to an existing Program.cs without breaking the build order.
- Work out why code placed after app.Run() never seems to execute.
- Pin a service to a known port during local development.
More examples
The two-line form and the one-line form
WebApplication.Create is worth recognising because sample code uses it to save a line. Use the longhand in anything real: the moment you need a setting, a database connection or a logger of your own, you need the builder back, and converting later means editing the top of the file anyway.
// These two programs behave identically.
// Longhand: create the builder, configure it, then build the application.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Shorthand, for a program with nothing to configure:
//
// var app = WebApplication.Create(args);
//
// WebApplication.Create calls CreateBuilder and then Build for you. The
// cost is that no builder is left to register services or settings on.
app.MapGet("/", () => "ok");
app.Run();Behaving differently per environment, and choosing the port
Almost every real Program.cs has at least one of these environment checks, because the safe choice in development is the unsafe choice in production. Passing a URL to Run is useful when you want one fixed port, for instance so a local reverse proxy or a teammate's script can rely on it.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// app.Environment.EnvironmentName comes from ASPNETCORE_ENVIRONMENT.
// IsDevelopment() is a string comparison against "Development".
if (app.Environment.IsDevelopment())
{
// Puts the full exception text in the response body. Never in production:
// it reveals file paths and stack traces to whoever sent the request.
app.UseDeveloperExceptionPage();
}
app.MapGet("/", () => "ok");
// Run(url) sets the listening address and then starts, instead of taking
// the address from configuration or from launchSettings.json.
app.Run("http://localhost:5080");
Discussion