Clean Startup in ASP.NET Core

Quick one today.

As I build more ASP.NET Core applications and the applications get larger and larger I've found my Startup.cs file getting more and more busy and harder to read. I also noticed that I was staring to duplicate startup code between projects using the same shared libraries.

So, I've taken some direction from the ASP.NET Core framework itself and have started creating extension methods to group my application startup code.  For an example of how that looks, here is my "ConfigureServices" method from a current side project.

public class Startup
    {
        public IConfiguration Configuration { get; }
        public IWebHostEnvironment Environment { get; }

        public Startup(IConfiguration configuration, IWebHostEnvironment environment)
        {
            Configuration = configuration;
            Environment = environment;
        }

        public void ConfigureServices(IServiceCollection services)
        {   
            services.AddDbContext<ApplicationDbContext>(b =>
            {
                b.UseSqlServer(Configuration.GetConnectionString("InertiaFxIdentity"), sql => sql.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name));
            });
            
            services.AddLocalizationConfigurations();
            services.AddOnFileFxWeb(Environment, Configuration);
            services.AddOnFileFxIdentity(Environment, Configuration);
            services.AddInertiaCmsForOnFileFx<Startup>(Configuration);
            services.AddOnFileFxServices(Configuration);
        }

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

Apart from setting up my data context I am calling 5 extension methods to configure my service collection.  Instead of sifting through the details of each of these extensions I can quickly determine that this application is using Localization, a shared CMS library and has some application specific services.

Setting Up and Email Provider

A simple example that removes a decent amount of cognitive load from my Startup file is setting up an email provider.  The application supports 3 possible ways of sending email; SendGrid, MailTrap and through a Gmail account. Based on the value of an "EmailProvider" configuration value the application should register a different "IEmailService" class.

public static  class ServiceConfiguration
{   
    public static void AddEmailProvider(this IServiceCollection services, IConfiguration configuration)
    {
        var emailProvider = configuration.GetValue<string>("InertiaFx:EmailProvider")?.ToLower();

        switch (emailProvider)
        {
            case "sendgrid":
            	services.AddTransient<IEmailService, SendGridEmailService>();
            	break;
            case "mailtrap":
            	services.Configure<MailTrapSettings>(configuration.GetSection("MailTrap"));
            	services.AddTransient<IEmailService, MailTrapEmailService>();
            	break;
            default:
            	services.AddTransient<IEmailService, GmailEmailService>();
            	break;
        }
    }
}

The code is only about 20 lines long but there is no need for this clog up my Startup class and I can come directly here when I need to work on how email is configured.

Conclusion

The email example here is pretty simple but this type of code can quickly grow especially if you are customizing controller setup or adding a lot of authorization policies.

The warning here, as with a lot of programming, is to not take this too far and forcing yourself to dig through layer up on layer of configuration method extensions just to find where things are setup.

Cover image by Braden Collum on Unsplash