简体   繁体   中英

ASP.Net Core Web Api - Use production and test database connection strings

I am building a web api with asp.net core and I wanted to ask how you would handle production and test environments inside the api.The goal would be to publish the web api on the IIS and then for it to use the production db connection string. When I start my api locally I would like my api to connect to the test database without me changing a lot of stuff in the code.

In my Startup.cs I use Entity Framework like this:

services.AddDbContextFactory<ProdContext>(options =>
{
    string decrypted = ConnStringSecurity.Decrypt(Configuration.GetConnectionString("ProdDBConnection"));

    options.UseSqlServer(decrypted,
    sqlServerOptionsAction: sqlOptions =>
    {
        sqlOptions.EnableRetryOnFailure(
        maxRetryCount: 10,
        maxRetryDelay: TimeSpan.FromSeconds(5),
        errorNumbersToAdd: null
        );
    });
});

services.AddDbContextFactory<TestContext>(options =>
{
    string decrypted = ConnStringSecurity.Decrypt(Configuration.GetConnectionString("TestDBConnection"));

    options.UseSqlServer(decrypted,
    sqlServerOptionsAction: sqlOptions =>
    {
        sqlOptions.EnableRetryOnFailure(
        maxRetryCount: 10,
        maxRetryDelay: TimeSpan.FromSeconds(5),
        errorNumbersToAdd: null
        );
    });
});

In the Configure method I see that you can differentiate between development and production but I can't quite imagine how the DbContexts can be integrated in the if statement:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
    if (env.IsDevelopment())
    {
        //Use Test Db globally
        app.UseDeveloperExceptionPage();
    }
    else if(env.IsProduction())
    {
       //Use Prod Db globally
    }


}

Also in every Controller I inject the Context that I need so to make this happen I would have to check on every endpoint if I am currently in development or production. Is there a more efficient way to do this? The 'lazy' approach would be to publish two instances of my api, one for prod and one for test where the code is changed accordingly.

The Microsoft docs article Use multiple environments in ASP.NET Core contains an example:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

EDIT

I did not notice you have a DbContexts for each environment so I didn't take that into consideration in my answer. You should really just stick with one single DbContext instead of one for each environment unless you have a very good reason not to.

So, if you get rid of the "one DbContext per environment" idea you can just:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContextFactory<MyDbContext>(options =>
{

    var cs = builder.Environment.IsDevelopment() ? "TestDBConnection" : "ProdDBConnection";

    string decrypted = ConnStringSecurity.Decrypt(Configuration.GetConnectionString(cs));

    options.UseSqlServer(decrypted,
    sqlServerOptionsAction: sqlOptions =>
    {
        sqlOptions.EnableRetryOnFailure(
        maxRetryCount: 10,
        maxRetryDelay: TimeSpan.FromSeconds(5),
        errorNumbersToAdd: null
        );
    });
});

And finallly in your controller you can just take a dependency on a single DbContext


class MyController 
{
    public MyController(MyDbContext dbContext) { ... }
}

EDIT 2

My solution is dotnet core 6.0 (net6.0) and above. For net5 go with yas-ikeda 's answer instead.

You can inject IWebHostEnvironment to the Startup class by its constructor, which is the same one as you have among the parameters of Startup.Configure method. Then, you can use env.IsDevelopment() in the Startup.ConfigureServices method, where you set up DB context.

I wonder if you really want to different DB contexts in the development and production environment.

So, having a single context MyDbContext for both environments, Startup class becomes like below. Then, your controllers will get MyDbContext instance injected which connects to a different DB by the environment.

    public class Startup
    {
        public IConfiguration Configuration { get; }
        private readonly IWebHostEnvironment _env;

        public Startup(IConfiguration configuration, IWebHostEnvironment env)
        {
            Configuration = configuration;
            _env = env;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<MyDbContext>(options =>
            {
                string decrypted = ConnStringSecurity.Decrypt(Configuration.GetConnectionString(
                    _env.IsDevelopment() ?  "TestDBConnection" : "ProdDBConnection"));
                options.UseSqlServer(decrypted,
                    sqlOptions =>
                    {
                        sqlOptions.EnableRetryOnFailure(
                            maxRetryCount: 10,
                            maxRetryDelay: TimeSpan.FromSeconds(5),
                            errorNumbersToAdd: null
                            );
                    });
            });

firstly, its is not a good practice to store you database connection strings in configuration files where you push them into source code management (ex: github, devops, etc).

when you are developing and running your app locally, then you can make use of the "Secret.json" file to store sensitive data like db connection strings which will not be pushed to repository and will not even appear in git changes.

you can create this file by right clicking on the API project -> select "Manage user secrets". this will create a file in your pc. this is similar to appsetting.json but will be only available on your pc while developing locally.

you can set the db connection string like same as in appsetting.json as show below.

秘密.json

you can add all your sensitive key value pairs on the secrets.json when developing. the same keys in the appsetting.json will be overridden by secrets.json if they have the same key.

for production you can create application level variables by the same key value pair or use key-vault, or use app configuration if you are hosting in azure web app service.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM