Search code examples
c#.net-coreentity-framework-coreconfigurationmicrosoft.extensions.configuration

Access environment-specific settings class inside factory method


My project is a Windows background service with .NET 8.

I want to use Microsoft Entity Framework Core in my current project. I also want to use the DbContextFactory to create an instance where needed.

Therefore I need to set this up in my Program.cs file.

Actually I need to explain a little bit more my current setup of my project. In appsettings.json there is a property value, which controls which database should be used.

This is my appsettings.json:

...
{
  "AppSettings": {
    "DaysBetweenExecution": 1,
    "ExecutionHour": 3,
    "ExecutionMinute": 0,
    "Environment": "Dev"
},
...

As you can see here, the Environment is set to Dev.

So I setup this AppSettings in my Program.cs like this:

...
builder.Services.AddOptions<AppSettings>()
                .BindConfiguration("AppSettings");
...

to use these settings in my application.

The AppSettings class looks like this:

namespace BrochureCreationService.ApplicationLogic.Models.AppSettings
{
    public class AppSettings
    {
        public int DaysBetweenExecution { get; set; } = -1; 
        public int ExecutionHour { get; set; } = -1;
        public int ExecutionMinute { get; set; } = -1;
        public string Environment { get; set; } = "Dev";
    }
}

This value Dev is being handled by the class Environment.

This is the Environment class:

using Microsoft.Extensions.Options;

namespace BrochureCreationService.ApplicationLogic.Models.Enviroment
{
    public class Environment : IEnvironment
    {
        private IOptions<AppSettings.AppSettings> _AppSettings;

        public Enums.Environment Current { get; set; }

        public Environment(IOptions<AppSettings.AppSettings> appSettings) { 
            _AppSettings = appSettings;
            Current = GetEnvironmentFromAppSettings();
        }

        public Enums.Environment GetEnvironmentFromAppSettings()
        {
            switch(_AppSettings.Value.Environment.ToLower())
            {
                case "dev":
                    return Enums.Environment.Dev;

                case "test":
                    return Enums.Environment.Test;

                case "ittest":
                case "it-test":
                    return Enums.Environment.ITTest;

                case "live":
                case "production":
                    return Enums.Environment.Live;

                default:
                    return Enums.Environment.Dev;
            }
        }

        public string GetConnectionString()
        {
            switch (_Environment.Current)
            {
                case Enums.Environment.Live:
                    return "connection string";

                case Enums.Environment.Test:
                    return "connection string test";

                case Enums.Environment.ITTest:
                    return "connection string it test";

                case Enums.Environment.Dev:
                    return "connection string dev";

                default:
                    return "connection string dev";
            }
        }
    }
}

As you can see, the AppSettings are injected in the constructor. This class provides me the correct connection string based on the Environment setting in appsettings.json.

Now my issue: I need to setup the factory for Entity Framework Core in Program.cs.

I want to do it like this :

builder.Services.AddDbContextFactory<IMP_GG_Context>(options =>
{
    var environment = new BrochureCreationService.ApplicationLogic.Models.Enviroment.Environment();
    options.UseSqlServer(environment.GetConnectionString());

}, ServiceLifetime.Scoped);

The issue here is, that this line is not working:

var environment = new BrochureCreationService.ApplicationLogic.Models.Enviroment.Environment();

...because Environment needs the AppSettings or better IOptions<AppSettings.AppSettings> in the constructor. How can I manage that in Program.cs?

Or are there better ways to do this?

Maybe, if it helps. Here is my full current code of Program.cs:

using Microsoft.Extensions.Logging.Configuration;
using Microsoft.Extensions.Logging.EventLog;
using BrochureCreationService.GUI;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddWindowsService(options =>
{
    options.ServiceName = "Brochure Creation Service";
});

LoggerProviderOptions.RegisterProviderOptions<EventLogSettings, EventLogLoggerProvider>(builder.Services);
builder.Services.AddOptions<AppSettings>().BindConfiguration("AppSettings");
builder.Services.AddOptions<WordSettings>().BindConfiguration("WordSettings");
builder.Services.AddOptions<PdfSettings>().BindConfiguration("PdfSettings");
builder.Services.AddOptions<EmailSettings>().BindConfiguration("EmailSettings");
builder.Services.AddSingleton<IEnvironment, BrochureCreationService.ApplicationLogic.Models.Enviroment.Environment>();
builder.Services.AddSingleton<IServiceController, ServiceController>();
builder.Services.AddTransient<IExecution, Execution>();
//... some more dependencies with AddTransient

builder.Services.AddScoped<IIMP_GG_Context, IMP_GG_Context>();

builder.Services.AddDbContextFactory<IMP_GG_Context>(options =>
{
    var appSettingsOptions = Configuration.GetSection("AppSettings").Get<AppSettings>();
    var environment = new BrochureCreationService.ApplicationLogic.Models.Enviroment.Environment(appSettingsOptions);
    options.UseSqlServer(environment.GetConnectionString());

}, ServiceLifetime.Scoped);

builder.Services.AddHostedService<WindowsBackgroundService>();
IHost host = builder.Build();
host.Run();

Solution

  • There is another overload for AddDbContextFactory which you can inject IServiceProvider, so easily it's possible to get the IEnvironment service from DI:

    
    builder.Services.AddDbContextFactory<ApplicationDbContext>((provider, options) =>
    {
        var environment = provider.GetRequiredService<IEnvironment>();
        
        options.UseSqlServer(environment.GetConnectionString());
    }, ServiceLifetime.Scoped);