OdeToCode IC Logo

.NET Core Opinion 7 – Startup Responsibilities

Thursday, February 14, 2019

Over the years I’ve noticed that application startup code tends to attract smaller bits of code in the same way that a protostar accretes cosmic material until reaching the point where nuclear fusion begins. I’ve seen this happen in the main function of C programs, and (back when we never had enough HRESUTs to hold the HINSTANCEs of our HWINDOWs), in the WinMain function of C++ programs. I’ve also seen this happen inside of global.asa in classic ASP, and in global.asax.cs for ASP.NET. It’s as if we say to ourselves, "I only have two new lines of code to execute when the program starts up, so what could it hurt to jam these two lines in the middle of the 527 method calls we already have in the startup function?"

This post is a plea to avoid nuclear fusion in the Startup and Program files of ASP.NET Core.

Starting Up

There is a lengthy list of startup tasks for modern server applications. Warm up the cache, create the connection pool, configure the IoC container, instantiate the logging sinks, and all of this happens before you getting to the actual business of application startup. In ASP.NET Core, I used to see most of this logic end up inside of Startup.cs. Some of this code is moving over to Program.cs as developers start to recognize Program.Main as a usable entry point for a web application.

The next few opinionated posts will discuss strategies for organizing startup and entry code, and look at approaches you can use for clean startup code.

To get started, let’s talk about the Startup class in ASP.NET Core. If you believe every class should have a single responsibility, then it is easy to think the Startup class should manage all startup tasks. But, as I’ve already pointed out, there is a lot of work to do when starting up today’s apps. The Startup class, despite its name, should not take responsibility for any of these tasks.

In Startup, there are three significant methods with specific, limited assignments:

  • The constructor, where you can inject dependencies to use in the other methods.

  • ConfigureServices, where you can setup the service provider for the app

  • Configure, which should be limited to arranging middleware components in the correct order.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
         // ...
    }

    public void ConfigureServices(IServiceCollection services)
    {
         // ...
    }

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

Of course, you can also have environment specific methods, like ConfigureDevelopment and ConfigureProduction, but the rules remain the same. Initialization tasks like warming the cache don’t need to live in Startup, which already has enough to do. Where should these tasks go? That’s the topic for the next post.