ASP.NET Startup class organization
Each ASP.NET Startup
class contains two methods that are called by the host when the app starts:
- a
Configure
method to create the app’s request processing pipeline usingUseXxx
extension methods - an optional
ConfigureServices
method to register and configure the app’s services usingAddXxx
extension methods
They are typically used like so:
Large configurations
Larger projects usually have dozens of services and middleware components set up in these two methods, often with very large configuration objects, complex in-line middleware logic and configuration-dependent execution paths. This leads to hundreds of lines of scrollable code in each of the Startup
’s method, which is not only painful to navigate and maintain, but can also lead to subtle bugs, since e.g. a wrong order of middleware components added in the Configure
method can lead to faulty behavior at runtime.
C# extension methods can help us tackle this issue by extending the IServiceCollection
and IApplicationBuilder
types. For each service or middleware Xxx
configuration that we want to extract out of the Startup
class we thus create a separate static
class XxxConfigurationExtension
that contains extension methods for either or both of those types. These new types are best kept in a separate directory, such as ConfigurationExtensions
or StartupExtensions
.
If the AddXxxConfig
or UseXxxConfig
methods require access to the injectable services of Configure
or ConfigureServices
, these services can be passed as direct arguments when invoking them.
Once this is done, the refactored Startup
class looks much cleaner:
Separation of concerns
One extra benefit provided by these extension methods is the ability to extract pieces of code that shouldn’t really even be there in the first place. For example, the usage of Entity Framework (EF) requires a registration of the DbContext
-derived types in the service container.
Since the Startup
class is typically a part of the Web/API project, this breaks the dependency inversion principle by requiring an explicit dependency on the data store used by the data access/infrastructure layer project, i.e. the Microsoft.EntityFrameworkCore
assembly in case of EF. This can also easily be fixed by creating an extension method in the infrastructure project itself that adds the necessary contexts into the service container.
Then, we can configure the infrastructure services from the Startup
class like so:
As a rule of thumb, all of our services that are implemented in a separate project in the solution should have a similar IServiceCollection
extension methods that configure and register them in the service container.
Note also that the frequently-used EF CLI tool ef
requires the Microsoft.EntityFrameworkCore.Design
package to run, and that it also expects to find a CreateHostBuilder
method that configures the host without running the app in the project specified as --startup-project
. Since CreateHostBuilder
is only found in the API/Web project that contains the Program
class, the dependency on EF cannot be removed entirely in that typical scenario.