I'm working on a presentation for my day job on ASP.NET 5 ASP.NET Core 1.0 and its new features, as most of my coworkers haven't been exposed to it yet. Part of this process is to demo the new features like tag helpers, but part of it is also to explain what some of the new files like project.json are doing.

In the process of making this presentation, I felt that I didn't understand the Startup.cs file enough, so this blog post is going to be a log of what I discovered about about it. Hopefully it helps someone else as much as it helped me.

But, as always, let's see the code first. Note that this Startup.cs file was generated for me by Visual Studio 2015 for an ASP.NET Core 1.0 RC1 web project with no authentication. Here's the file:

public class Startup
{
    public IConfigurationRoot Configuration { get; set; }

    public Startup(IHostingEnvironment env)
    {
        // Set up configuration sources.
        var builder = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .AddEnvironmentVariables();
        Configuration = builder.Build();
    }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services.AddMvc();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        if (env.IsDevelopment())
        {
            app.UseBrowserLink();
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        app.UseIISPlatformHandler();

        app.UseStaticFiles();

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

    // Entry point for the application.
    public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}

We're going to take this method-by-method and sometimes line-by-line, and first up is the constructor Startup().

Startup()

The constructor uses a property of type IConfiguration and looks like this:

public IConfigurationRoot Configuration { get; set; }

public Startup(IHostingEnvironment env)
{
    // Set up configuration sources.
    var builder = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json")
        .AddEnvironmentVariables();
    Configuration = builder.Build();
}

First of all, what is IConfigurationRoot? It represents the root of the configuration file that was formerly represented by the web.config file. (see this GitHub issue). It represents all of the configuration necessary to run the app.

By default, the configuration doesn't have anything in it. However, it can parse JSON files, and hence we use AddJsonFile("appsettings.js") to load the values from the app settings into the Configuration. The last line called is AddEnvironmentVariables(), which just adds any environmental values to the configuration. Simple enough, and yet powerful at the same time.

ConfigureServices()

The next method we encounter is ConfigureServices(), and the default code looks like this:

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();
}

ConfigureServices exists for the explicit reason of adding services to ASP.NET. ASP.NET Core supports Dependency Injection natively, and as such this method is adding services to the DI container (if what I just wrote doesn't make sense or you would like a more thorough explanation of DI, see this blog post on MSDN).

In fact, Dependency Injection is not just a first-class citizen in ASP.NET Core 1.0, but the default way of adding services to our runtime and environment. Let's say we wanted to add EntityFramework for SQL Server to our app, and we have a defined context called MoviesContext. We could inject EF into the services layer by modifying ConfigureServices like so:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<AppSettings>(Configuration.GetSubKey("AppSettings"));

    services.AddEntityFramework()
            .AddSqlServer()
            .AddDbContext<MovieContext>();

    // Add MVC services to the services container.
    services.AddMvc();
}

That new line would inject, in order, the DLLs for Entity Framework, the DLLs for EF-to-SQL-Server, and the Context, thus making them all available to that app via the DI container. Note that all this is doing is making the specified services available through the DI container, and that you could pretty easily add your own services as well.

Configure()

The last method we see in the Startup.cs file is Configure(). This method exists for us to control the ASP.NET pipeline.

Let's make something clear here: ASP.NET Core assumes that no frameworks are being used unless you explicitly tell it that they are, and that is why Configure() exists. You use this method to tell ASP.NET what frameworks you would like to use for this app. This allows you full, detailed control over the HTTP pipeline.

Let's walk though the method line-by-line.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseBrowserLink();
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }
    ...

First note the three parameters:

  • IApplicationBuilder allows us to add services to the pipeline for this app.
  • IHostingEnvironment stores information about the current hosted environment.
  • ILoggerFactory allows us to setup and add log providers.

The first line of Configure() adds the logger console and the debug logger, but the if/else clauses are more intriguing. Those clauses are injecting pieces into the pipeline depending on whether we are running in Development mode or not. If we are running in Development, then we should show the error page unencumbered (the app.UseDeveloperExceptionPage() method), as well as hook our application into Visual Studio's BrowserLink feature, which allows us to test our application in multiple browsers at once (the app.UseBrowserLink() method). If we're not running in Development mode, we should redirect to Home/Error to allow our custom exception handling to take over.

This snippet takes over the functionality formerly provided by <customErrors> in the old web.config, as well as giving us more functionality to play with when switching environments.

Now let's see the second half of the method

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory)
{
    ...
    app.UseIISPlatformHandler();

    app.UseStaticFiles();

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

UseIISPlatformHandler() injects middleware to handle IIS requests, which will handle IIS-specific things like Windows Authentication. If I was going to use another server (e.g. Kestrel) then I could remove this line.

UseStaticFiles() does pretty much what it sounds like it does; it adds the usage of the static files (e.g. CSS, JS, images, etc) to the pipeline so that the app can serve them to the browser.

Now, because ASP.NET Core assumes we aren't using anything until we tell it we are, we must tell it we are using MVC, hence the call to UseMvc(). Also note that if we are using convention routing, we define our routes here rather that in a RouteConfig.cs file (which do not exist in default ASP.NET Core 1.0 applications).

Let's say we wanted to use a Welcome page that would appear to the user whenever they showed up on the site. We could do this by adding a piece of "middleware" to the pipeline like so:

app.UseWelcomePage();

Now, when you load the page, you will be redirected to a default "Welcome" page. Just like creating your own services, it is entirely possible to create your own middleware and add that to the pipeline (and I'm hoping to do just that in a future blog post).

Summary

The Startup.cs file establishes the entry point and environment for your ASP.NET Core application; it creates services and injects dependencies so that the rest of the app can use them. The three methods in a default Startup.cs file each handle a different part of setting up the environment:

  • The constructor Startup() allows us to setup or include the configuration values.
  • ConfigureServices() allows us to add services to the Dependency Injection container.
  • Configure() allows us to add middleware and services to the HTTP pipeline.

Happy Coding!

NOTE (9/30/2016): This post has been updated to use ASP.NET Core 1.0 and includes a better explaination of UseBrowserLink()