OdeToCode IC Logo

How To Stop Worrying About ASP.NET Startup Conventions

Tuesday, February 9, 2016

There are 2 kinds of developers. Those who love conventions and those who loathe conventions. The former group sees conventions as a means to remove boilerplate code and expose the essence of software by avoiding ceremony. The later group tends to view conventions as dangerous magic. It’s also the later group, I’ve found, which tends to dislike the Startup class in ASP.NET Core applications.

public class Startup
{
    public void ConfigureServices() { }
    public void Configure() { }
}

Questions around Startup generally revolve around the following themes:

1. Why isn’t there a required interface available for Startup to implement?

2. How do I know what methods are needed and what parameters the methods will accept?

The short answer to both of these questions is that conventions offer more flexibility than contracts.

Naming Conventions

Programming against conventions is frustrating when you don’t know the conventions. The software won’t behave correctly, and in many convention oriented systems you won’t see any error message or know how to start debugging. Conventions are indistinguishable from magic.

Fortunately, for the startup scenario, the runtime does give you some actionable error messages when you miss a convention. For example, if you don’t have a proper class name for the startup code, ASP.NET will give you the following.

A type named Startup could not be found

Here, the runtime will not only tell you what is missing, but also give you hints where you can find some wiggle room. You can have a dedicated startup class for any given environment, and use Startup itself as a default fall back. StartupDevelopment, StartupProduction, and StartupYourEnvironmentNameHere can all live in the same project and provide the startup configuration for their given environment name.

The same convention also applies to the Configure method in a startup class. In other words, once the runtime locates the startup class for the application, it will look for Configure and ConfigureServices methods that optionally have the environment name as a suffix.

A public method named Configure could not be found

One difference between Configure and ConfigureServices is that ConfigureServices is entirely optional. If you don’t provide the method, the runtime will carry on with only default services installed.

These types of conventions are impossible to enforce using interfaces. Attributes could make the purpose of a class more explicit, but attributes would add ceremony with little benefit.

Versus Interfaces

Another difference between the Configure and ConfigureServices methods is how the ConfigureServices method can only take a parameter of type IServiceCollection. The Configure method, on the other hand, is an injectable method. You can take any number and types of parameters in Configure as long as the underlying IoC container can locate a service to populate the parameter.

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

    public void Configure(IApplicationBuilder app,
                          IApplicationLifetime lifetime,
                          IHostingEnvironment hostingEnvironment,
                          IApplicationEnvironment applicationEnvironment)
    {
        // ....   
    }        
}

The type of flexibility demonstrated by Configure is impossible to describe using a C# interface, and it is not the sort of flexibility you want to lose just to have a simplistic compile time check, at least not when the error messages are so explicitly good. Don’t worry about the conventions, the runtime will let you know when you goof.