Application startup in ASP.NET Core

By Steve Smith, Tom Dykstra, and Luke Latham

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:

  • Can optionally include a ConfigureServices method to configure the app's services.
  • Must include a Configure method to create the app's request processing pipeline.

ConfigureServices and Configure are called by the runtime when the app starts:

public class Startup
{
    // Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        ...
    }

    // Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app)
    {
        ...
    }
}

Specify the Startup class with the WebHostBuilderExtensions UseStartup<TStartup> method:

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

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

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

A common use of dependency injection into the Startup class is to inject:

public class Startup
{
    private readonly IHostingEnvironment _env;
    private readonly IConfiguration _config;
    private readonly ILoggerFactory _loggerFactory;

    public Startup(IHostingEnvironment env, IConfiguration config, 
        ILoggerFactory loggerFactory)
    {
        _env = env;
        _config = config;
        _loggerFactory = loggerFactory;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        var logger = _loggerFactory.CreateLogger<Startup>();

        if (_env.IsDevelopment())
        {
            // Development service configuration

            logger.LogInformation("Development environment");
        }
        else
        {
            // Non-development service configuration

            logger.LogInformation($"Environment: {_env.EnvironmentName}");
        }

        // Configuration is available during startup.
        // Examples:
        //   _config["key"]
        //   _config["subsection:suboption1"]
    }
}

An alternative to injecting IHostingEnvironment is to use a conventions-based approach. The app can define separate Startup classes for different environments (for example, StartupDevelopment), and 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.

To learn more about WebHostBuilder, see the Hosting topic. For information on handling errors during startup, see Startup exception handling.

The ConfigureServices method

The ConfigureServices method is:

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

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 IApplicationBuilder.ApplicationServices.

The web host may configure some services before Startup methods are called. Details are available in the Host in ASP.NET Core topic.

For features that require substantial setup, there are Add[Service] extension methods on IServiceCollection. A typical web app registers services for Entity Framework, Identity, and MVC:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc();

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();
}

SetCompatibilityVersion for ASP.NET Core MVC

The SetCompatibilityVersion method allows an app to opt-in or opt-out of potentially breaking behavior changes introduced in ASP.NET Core MVC 2.1 or later. These potentially breaking behavior changes are generally in how the MVC subsystem behaves and how your code is called by the runtime. By opting in, you get the latest behavior, and the long-term behavior of ASP.NET Core.

The following code sets the compatibility mode to ASP.NET Core 2.1:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc()
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

We recommend you test your app using the latest version (CompatibilityVersion.Version_2_1). We anticipate that most apps won't have breaking behavior changes using the latest version.

Apps that call SetCompatibilityVersion(CompatibilityVersion.Version_2_0) are protected from potentially breaking behavior changes introduced in the ASP.NET Core 2.1 MVC and later 2.x versions. This protection:

  • Does not apply to all 2.1 and later changes, it's targeted to potentially breaking ASP.NET Core runtime behavior changes in the MVC subsystem.
  • Does not extend to the next major version.

The default compatibility for ASP.NET Core 2.1 and later 2.x apps that do not call SetCompatibilityVersion is 2.0 compatibility. That is, not calling SetCompatibilityVersion is the same as calling SetCompatibilityVersion(CompatibilityVersion.Version_2_0).

The following code sets the compatibility mode to ASP.NET Core 2.1, except for the following behaviors:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc()
     // Include the 2.1 behaviors
     .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
     // Except for the following.
     .AddMvcOptions(options =>
     {
       // Don't combine authorize filters (keep 2.0 behavior).
       options.AllowCombiningAuthorizeFilters = false;
       // All exceptions thrown by an IInputFormatter will be treated
       // as model state errors (keep 2.0 behavior).
       options.InputFormatterExceptionPolicy =
                      InputFormatterExceptionPolicy.AllExceptions;
     });
}

For apps that encounter breaking behavior changes, using the appropriate compatibility switches:

  • Allows you to use the latest release and opt out of specific breaking behavior changes.
  • Gives you time to update your app so it works with the latest changes.

The MvcOptions class source comments have a good explanation of what changed and why the changes are an improvement for most users.

At some future date, there will be an ASP.NET Core 3.0 version. Old behaviors supported by compatibility switches will be removed in the 3.0 version. We feel these are positive changes benefitting nearly all users. By introducing these changes now, most apps can benefit now, and the others will have time to update their apps.

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 (reference source).

The ASP.NET Core templates configure the pipeline with support for a developer exception page, BrowserLink, error pages, static files, and ASP.NET Core MVC:

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

    app.UseStaticFiles();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller}/{action=Index}/{id?}");
    });
}

Each Use extension method adds a middleware component to the request pipeline. For instance, the UseMvc extension method adds the Routing Middleware to the request pipeline and configures MVC as the default handler.

Each middleware component in the request pipeline is responsible for invoking the next component in the pipeline or short-circuiting the chain, if appropriate. If short-circuiting doesn't occur along the middleware chain, each middleware has a second chance to process the request before it's sent to the client.

Additional services, such as IHostingEnvironment and ILoggerFactory, may also be specified in the method signature. When specified, additional services are injected if they're available.

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

Convenience methods

ConfigureServices and Configure convenience methods can be used instead of specifying a Startup class. Multiple calls to ConfigureServices append to one another. Multiple calls to Configure use the last method call.

public class Program
{
    public static IHostingEnvironment HostingEnvironment { get; set; }
    public static IConfiguration Configuration { get; set; }

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

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                HostingEnvironment = hostingContext.HostingEnvironment;
                Configuration = config.Build();
            })
            .ConfigureServices(services =>
            {
                services.AddMvc();
            })
            .Configure(app =>
            {
                var loggerFactory = app.ApplicationServices
                    .GetRequiredService<ILoggerFactory>();
                var logger = loggerFactory.CreateLogger<Program>();
                logger.LogInformation("Logged in Configure");

                if (HostingEnvironment.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    app.UseExceptionHandler("/Error");
                }

                // Configuration is available during startup. Examples:
                // Configuration["key"]
                // Configuration["subsection:suboption1"]

                app.UseMvcWithDefaultRoute();
                app.UseStaticFiles();
            });
}

Startup filters

Use IStartupFilter to configure middleware at the beginning or end of an app's Configure middleware pipeline. IStartupFilter is useful to ensure that a middleware runs before or after middleware added by libraries at the start or end of the app's request processing pipeline.

IStartupFilter implements a single method, Configure, which receives and returns an Action<IApplicationBuilder>. An IApplicationBuilder defines a class to configure an app's request pipeline. For more information, see Create a middleware pipeline with IApplicationBuilder.

Each IStartupFilter implements one or more middlewares in the request pipeline. The filters are invoked in the order they were added to the service container. Filters may add middleware before or after passing control to the next filter, thus they append to the beginning or end of the app pipeline.

The sample app (how to download) demonstrates how to register a middleware with IStartupFilter. The sample app includes a middleware that sets an options value from a query string parameter:

public class RequestSetOptionsMiddleware
{
    private readonly RequestDelegate _next;
    private IOptions<AppOptions> _injectedOptions;

    public RequestSetOptionsMiddleware(
        RequestDelegate next, IOptions<AppOptions> injectedOptions)
    {
        _next = next;
        _injectedOptions = injectedOptions;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        Console.WriteLine("RequestSetOptionsMiddleware.Invoke");

        var option = httpContext.Request.Query["option"];

        if (!string.IsNullOrWhiteSpace(option))
        {
            _injectedOptions.Value.Option = WebUtility.HtmlEncode(option);
        }

        await _next(httpContext);
    }
}

The RequestSetOptionsMiddleware is configured in the RequestSetOptionsStartupFilter class:

public class RequestSetOptionsStartupFilter : IStartupFilter
{
    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        return builder =>
        {
            builder.UseMiddleware<RequestSetOptionsMiddleware>();
            next(builder);
        };
    }
}

The IStartupFilter is registered in the service container in ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IStartupFilter, RequestSetOptionsStartupFilter>();
    services.AddMvc();
}

When a query string parameter for option is provided, the middleware processes the value assignment before the MVC middleware renders the response:

Browser window showing the rendered Index page. The value of Option is rendered as 'From Middleware' based on requesting the page with the query string parameter and value of option set to 'From Middleware'.

Middleware execution order is set by the order of IStartupFilter registrations:

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

Adding 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 Startup class. For more information, see Enhance an app from an external assembly.

Additional resources