Home > Software engineering >  Cleaning up startup.cs with extension methods?
Cleaning up startup.cs with extension methods?

Time:08-02

I configure my database context by allowing changing of the connectionString based on the users cookie:

services.AddDbContext<HotdogContext>((serviceProvider, dbContextBuilder) =>
{
    string connectionString = "DatabaseUS"; // Default connection string
    var httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
    var dbNameCookie = httpContextAccessor.HttpContext.Request.Cookies.ContainsKey("country"); 
    if (dbNameCookie) // debug
    {
        Console.Write("Country cookie exists!");
    }

    var country = httpContextAccessor.HttpContext.Request.Cookies["country"]?.ToString() ?? "null";
    if (country.Equals("null"))
    {
        Console.WriteLine("Country cookie is null");
    }
    else if (country.Equals("USA"))
    {
        Console.WriteLine("Set USA connection string");
        connectionString = "DatabaseUS";
    }
    else if (country.Equals("AUS"))
    {
        connectionString = "DatabaseAUS";
        Console.WriteLine("Set Australia connection string");
    }
    else if (country.Equals("S.A."))
    {
        //connectionString = "DatabaseSA";
        Console.WriteLine("Set South America connection string");
    }
    dbContextBuilder.UseSqlServer(Configuration.GetConnectionString(connectionString));
});

This clutters the startup.cs if I add additional databaseContexts to the program. Is there a way I can hide this logic from startup.cs and use extension methods?

services.AddDbContext<HotdogContext>((serviceProvider, dbContextBuilder) =>
{
    string connectionString = "DatabaseUS"; // Default connection string
    var httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
    var country = dbContextBuilder.GetRequestedCountry(httpContextAccessor); // Extension method
    if(country !=null) connectionString = country;
    dbContextBuilder.UseSqlServer(Configuration.GetConnectionString(connectionString));
});

CodePudding user response:

One way is to define a service interface, then implement with your logic:

public interface IConnectionStringNameService
{
    string GetName();
}

public class ConnectionStringNameFromCookieService
    : IConnectionStringNameService
{
    private readonly HttpContext _httpContext;

    public ConnectionStringNameFromCookieService(
        HttpContextAccessor httpContextAccessor )
    {
        _httpContext = httpContextAccessor.HttpContext;
    }

    public string GetName()
    {
        var country = _httpContext.Request
            .Cookies[ "country" ]
            ?? "default value"; // or throw exception

        return country switch =>
        {
            "USA" => "DatabaseUS",
            "AUS" => "DatabaseAUS",
            "S.A." => "DatabaseSA",
            _ => "Default name or throw",
        }
    }
}

Then you register the service implementation with the container:

services.AddScoped<IConnectionStringNameService, ConnectionStringNameFromCookieService>();

Then you can resolve this service from the service provider when you need need the connection string name:

var connStrNameSvc = serviceProvider.GetRequiredService<IConnectionStringNameService>.();

var connStrName = connStrNameSvc.GetName();

In place (in the startup file), the above would abstract to:

services.AddDbContext<HotdogContext>(
    (serviceProvider, dbContextBuilder) =>
    {
        var connStrName = serviceProvider
            .GetRequiredService<IConnectionStringNameService>()
            .GetName();
    
        dbContextBuilder.UseSqlServer(
            Configuration.GetConnectionString( connStrName ) );
    });

But you can then use an extension method to abstract this call to a single, simple line:

public class BlahBlahExtensionMethods
{
    // come up with a better name
    public IServiceCollection AddDbContextUsingCookieForConnStrName<TDbContext>(
        this IServiceCollection services )
        where TDbContext : DbContext
    {
        return services.AddDbContext<TDbContext>(
            ( serviceProvider, dbContextBuilder ) =>
            {
                var connStrName = serviceProvider
                    .GetRequiredService<IConnectionStringNameService>()
                    .GetName();
            
                dbContextBuilder.UseSqlServer(
                    Configuration.GetConnectionString( connStrName ) );
            });
    }
}

Then in startup you'd only need:

services.AddDbContextUsingCookieForConnStrName<HotdogContext>();

Or you can inject the name provider into HotdogContext and use it in an OnConfiguring( DbContextOptionsBuilder ) method override.

  • Related