Keep Your Startup Clean

When I open a .NET Core application for the first time I start by reading the Startup file. Unfortunately there is an ugly pattern out there that turns this into a spaghetti code mess. They often look something like this:

public void ConfigureServices(IServiceCollection services)
{


    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddSingleton<IJwtFactory, JwtFactory>();

    var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtOptions));

    services.Configure<JwtOptions>(options =>
    {
      options.Issuer = jwtAppSettingOptions[nameof(JwtOptions.Issuer)];
      options.Audience = jwtAppSettingOptions[nameof(JwtOptions.Audience)];
      options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
    });

    var tokenValidationParameters = new TokenValidationParameters
    {
      ValidateIssuer = true,
      ValidIssuer = jwtAppSettingOptions[nameof(JwtOptions.Issuer)],

      ValidateAudience = true,
      ValidAudience = jwtAppSettingOptions[nameof(JwtOptions.Audience)],

      ValidateIssuerSigningKey = true,
      IssuerSigningKey = _signingKey,

      RequireExpirationTime = false,
      ValidateLifetime = true,
      ClockSkew = TimeSpan.Zero
    };

    services.AddAuthentication(options =>
    {
      options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
      options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

    }).AddJwtBearer(configureOptions =>
    {
      configureOptions.ClaimsIssuer = jwtAppSettingOptions[nameof(JwtOptions.Issuer)];
      configureOptions.TokenValidationParameters = tokenValidationParameters;
      configureOptions.SaveToken = true;
    });

    services.AddAuthorization(options =>
    {
      options.AddPolicy("AuthorizedApiUsr", policy => policy.RequireClaim(Constants.Strings.JwtClaimIdentifiers.Rol, Constants.Strings.JwtClaims.ApiAccess));
    });

    services.AddMvc()
      .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
      .AddMetrics();
}

This isn't fun to read. Since we spend most of our time reading code, it's not great to maintain either. By using extension methods you can clean this up to something more readable.

Add a simple file named something like ServiceCollectionExtensions.cs. Then create a public static class with IServiceCollection extension methods. Here's a quick example.

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddDataStores(this IServiceCollection services, IConfiguration configuration)
    {
        services
            .AddDbContextPool<ApplicationDbContext>(options =>
            {
                options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"));
            });
        return services;
    }
}

Now in your Startup.cs file you can simply use services.AddDataStores(Configuration);. Eventually you turn the mess above into something much more readable.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataStores(Configuration);
    services.AddIdentityCustom();
    services.AddAuthenticationCustom(Configuration);
    services.AddAuthorizationCustom();
    services.AddMvcCustom();
}

One Last Thing...

If you have a question or see a mistake, please comment below.

If you found this post helpful, please share it with others. It's the best thanks I can ask for & it gives me momentum to keep writing!

Matt Ferderer
Software Developer focused on making great user experiences. I enjoy learning, sharing & helping others make amazing things.
Let's Connect