Edit

App startup in ASP.NET Core

Note

This isn't the latest version of this article. For the current release, see the .NET 10 version of this article.

Warning

This version of ASP.NET Core is no longer supported. For more information, see the .NET and .NET Core Support Policy. For the current release, see the .NET 10 version of this article.

This article describes how ASP.NET Core apps start up and how to configure services and the app's request pipeline.

For Blazor startup guidance, which adds to or supersedes the guidance in this article, see ASP.NET Core Blazor startup.

The Program file

ASP.NET Core apps initialize and configure startup in the app's Program file (Program.cs).

The first part of the Program file focuses on building the app. This phase utilizes WebApplication.CreateBuilder to initialize a new instance of the WebApplicationBuilder class with preconfigured defaults before the app is started. The ASP.NET Core project templates assign the web application builder to a variable named builder:

var builder = WebApplication.CreateBuilder(args);

Properties of the web application builder include:

The app is built by calling WebApplicationBuilder.Build, which returns the built WebApplication. The ASP.NET Core project templates assign the built web application to a variable named app:

var app = builder.Build();

The next part of the Program file focuses on establishing the HTTP request handling pipeline as a series of middleware components. Each middleware performs operations on an HttpContext and either invokes the next middleware in the pipeline or terminates the request. By convention, middleware components are added to the pipeline by invoking an extension method that starts with "Use." For more information, see ASP.NET Core middleware.

The Run method starts the app and blocks the calling thread until the host is shut down:

app.Run();

When Run executes, the app transitions to an active, running process:

  1. Hosted services start.

    The host loops through all registered hosted services (IHostedService instances) and calls their StartAsync methods. Unless the app opts into concurrent hosted service startup (.NET 8 or later), hosted services start sequentially in the order of their DI container registrations. For more information, see Background tasks with hosted services in ASP.NET Core.

  2. The middleware pipeline is built.

    When builder.Build is called, dependencies are resolved, but the actual processing pipeline isn't completely set. When app.Run executes, the framework finalizes the HTTP middleware pipeline. The declared middleware methods and endpoint mappings are compiled into a single, high-performance execution delegate sequence. For more information, see ASP.NET Core middleware.

  3. The web server (Kestrel by default) is started.

    The host looks inside its dependency container, locates the registered server implementation (usually Kestrel), and triggers its startup cycle. Kestrel then:

    • Looks up the defined hosting URLs and ports from configuration, environment variables, or command-line arguments.
    • Opens and allocates physical network sockets.
    • Binds ports and begins listening for incoming traffic.

    For more information, see .NET Generic Host in ASP.NET Core, Web server implementations in ASP.NET Core, and Kestrel web server in ASP.NET Core.

  4. Application started lifetime events are triggered.

    The IHostApplicationLifetime service fires its ApplicationStarted token, which invokes callbacks registered on the token. Any callbacks, database seeders, or other custom event listeners that are wired up to wait for the token to fire are triggered to start processing.

  5. The main execution thread is blocked while the app runs.

    Run (app.Run()) synchronously waits until shutdown.

  6. The app is ready to process requests.

    At this point, the command shell logs hosting diagnostics:

    info: Microsoft.Hosting.Lifetime[14]
          Now listening on: https://localhost:7123
    info: Microsoft.Hosting.Lifetime[14]
          Now listening on: http://localhost:5123
    info: Microsoft.Hosting.Lifetime[0]
          Application started. Press Ctrl+C to shut down.
    

The app remains in this state indefinitely, passing incoming web traffic down the middleware pipeline and sending responses.

When shutdown is signaled, for example when Ctrl+c is detected in the command shell running the app or a container orchestration tool sends a SIGTERM event, Run unblocks and the following actions take place:

  1. ApplicationStopping tokens are triggered, which allows the app to run logic before the shutdown process begins.

  2. The Kestrel server is shut down, which disables new connections. The server waits for requests on existing connections to complete for as long as the shutdown timeout allows. The server sends the connection close header for further requests on existing connections.

  3. The host shuts down registered hosted services. Unless the app opts into stopping hosted services concurrently (.NET 8 or later), hosted services stop sequentially in the reverse order of their DI container registrations. For more information, see Background tasks with hosted services in ASP.NET Core.

  4. ApplicationStopped event handlers are triggered, which allows the app to run logic after the app has shut down.

  5. Console execution gracefully exits with an exit code of 0.

The Startup class configures services and the app's request pipeline.

The Startup class

ASP.NET Core apps use a startup class, which is named Startup by convention. The Startup class:

  • Optionally includes a ConfigureServices method to configure the app's services. A service is a reusable component that provides app functionality. Services are registered in ConfigureServices and consumed across the app via dependency injection (DI) or ApplicationServices.
  • Includes a Configure method to create the app's request processing pipeline.

ConfigureServices and Configure are called by the ASP.NET Core runtime when the app starts:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        ...
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        ...
    }
}

The Startup class is specified when the app's host is built. The Startup class is typically specified by calling WebHostBuilderExtensions.UseStartup on the host builder:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

The host provides services that are available to the Startup class constructor. The app adds additional services via ConfigureServices. Both the host and app services are available in Configure and throughout the app.

Only the following service types can be injected into the Startup constructor when using the Generic Host (IHostBuilder):

public class Startup
{
    private readonly IWebHostEnvironment _env;

    public Startup(IConfiguration configuration, IWebHostEnvironment env)
    {
        Configuration = configuration;
        _env = env;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        if (_env.IsDevelopment())
        {
        }
        else
        {
        }
    }
}

Most services aren't available until the Configure method is called.

Note

The private field in the preceding example for IWebHostEnvironment is named with an underscore (_env). It's also acceptable to adopt a coding convention that uses the same name as the injected IWebHostEnvironment (env) when the private field uses the this keyword (this.env = env in the constructor and env.IsDevelopment() in the ConfigureServices method).

The ASP.NET Core project templates prior to .NET 8 and C# 12 don't adopt primary constructors, but the preceding code can be refactored to adopt a primary constructor if your organization uses a .NET 8 or later SDK and the app targets C# 12 or later (for example, <LangVersion>12.0</LangVersion>). For more information, see Declare primary constructors for classes and structs (C# documentation tutorial) and Primary constructors (C# Guide).

Multiple Startup classes

When the app defines separate Startup classes for different environments (for example, StartupDevelopment), the appropriate Startup class is selected at runtime. The class whose name suffix matches the current environment is prioritized. If the app is run in the Development environment and includes both a Startup class and a StartupDevelopment class, the StartupDevelopment class is used. For more information, see Use multiple environments.

The ConfigureServices method

The optional ConfigureServices method is:

  • Called by the host before the Configure method to configure the app's services.
  • Where configuration options are set by convention.

The host may configure some services before Startup methods are called. For more information, see ASP.NET Core fundamentals overview.

For features that require substantial setup, there are Add{Service} extension methods on IServiceCollection, such as:

  • AddDbContext
  • AddDefaultIdentity
  • AddEntityFrameworkStores
  • AddRazorPages
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {

        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));
        services.AddDefaultIdentity<IdentityUser>(
            options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores<ApplicationDbContext>();

        services.AddRazorPages();
    }
}

Adding services to the service container makes them available within the app and in the Configure method. The services are resolved via dependency injection or from ApplicationServices.

The Configure method

The Configure method is used to specify how the app responds to HTTP requests. The request pipeline is configured by adding middleware components to an IApplicationBuilder instance. IApplicationBuilder is available to the Configure method, but it isn't registered in the service container. Hosting creates an IApplicationBuilder and passes it directly to Configure.

The ASP.NET Core templates configure the pipeline with support for:

The following example demonstrates middleware for a typical Razor Pages app:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    }

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

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }
}

The preceding sample is for Razor Pages; the MVC version is similar.

Each Use extension method adds one or more middleware components to the request pipeline. For instance, UseStaticFiles configures middleware to serve static files.

Each middleware component in the request pipeline is responsible for invoking the next component in the pipeline or short-circuiting the chain, if appropriate.

Additional services, such as IWebHostEnvironment, ILoggerFactory, or anything defined in ConfigureServices, can be specified in the Configure method signature. These services are injected if they're available.

For more information on how to use IApplicationBuilder and the order of middleware processing, see ASP.NET Core middleware.

Configure services without a Startup class

To configure services and the request processing pipeline without using a Startup class, call ConfigureServices and Configure convenience methods on the host builder. Multiple calls to ConfigureServices append to one another. If multiple Configure method calls exist, the last Configure call is used.

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.ConfigureServices(services =>
                {
                    ...
                })
                .Configure(app =>
                {
                    ...
                });
        });
}

Startup filters

While an app typically creates an explicit middleware execution pipeline, a startup filter (IStartupFilter) is useful for:

  • Creating a shared library/NuGet package that automatically loads custom middleware without requiring the app to explicitly call the middleware's "Use" method. For example, the library's consumer isn't required to make an app.UseImageProcessingMiddleware call for an image-processing middleware in the app's request processing pipeline.
  • Guaranteeing a piece of middleware executes before or after other middleware, regardless of how a developer modifies the app's request processing pipeline.

A startup filter implementation provides an IStartupFilter.Configure method that receives and returns an Action<IApplicationBuilder>. The IApplicationBuilder interface is used to configure the app's request pipeline. For more information, see Create a middleware pipeline with IApplicationBuilder.

Each startup filter implementation can add one or more middlewares to the request pipeline. The filters are invoked in the order they're added to the service container. Filters can add middleware before or after passing control to the next filter, thus they append to the beginning or end of the pipeline.

The following example demonstrates how to register a middleware with IStartupFilter. The CustomResponseHeaderFilter startup filter uses middleware to append a custom header (X-Custom-Header) to all of the app's responses before other middlewares execute.

CustomResponseHeaderFilter.cs:

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;

public class CustomResponseHeaderFilter : IStartupFilter
{
    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        return builder =>
        {
            // 1. Add middleware that runs BEFORE subsequent middlewares
            builder.Use(async (context, nextMiddleware) =>
            {
                context.Response.Headers.Append("X-Custom-Header", "VALUE");
                await nextMiddleware();
            });

            // 2. Call the rest of the application's configuration pipeline
            next(builder);

            // 3. (Optional) Add middleware that runs AFTER the rest of the pipeline
        };
    }
}

The startup filter implementation is registered in the Program file:

builder.Services.AddTransient<IStartupFilter, CustomResponseHeaderFilter>();

The startup filter implementation is registered in Startup.ConfigureServices:

services.AddTransient<IStartupFilter, CustomResponseHeaderFilter>();

Middleware execution order is set by the order of startup filter registrations:

  • Multiple implementations might interact with the same objects. If ordering is important, order their service registrations to match the order that their middlewares should run.
  • Libraries can add middleware with one or more implementations that run before or after other app middleware registered with IStartupFilter. To invoke a startup filter middleware before a middleware added by a library's startup filter:
    • Position the startup filter service registration before the library is added to the service container.
    • To invoke afterward, position the service registration after the library is added.

Note

You can't extend the ASP.NET Core app with startup filters when you override the Configure delegate. For more information, see WebApplicationFactory Client returns NotFound for all requests with Overriding Configure method (dotnet/aspnetcore #45372).

Add configuration at startup from an external assembly

An IHostingStartup implementation allows adding enhancements to an app at startup from an external assembly outside of the app's Program file or Startup class. For more information, see Use hosting startup assemblies in ASP.NET Core.

The Startup class (ConfigureServices and Configure methods)

Although supported in ASP.NET Core apps that target .NET 6 or later, using a Startup class isn't recommended. For more information, see Migrate from ASP.NET Core in .NET 5 to .NET 6.

For information on using the ConfigureServices and Configure methods with the minimal hosting model, see the following:

Measure startup performance

The ASP.NET Core hosting EventSource emits the ServerReady event, which represents the point where the server is ready to respond to requests and can be used to measure startup time. For more information, see Logging in .NET and ASP.NET Core.

Additional resources