Search code examples
entity-framework-coreasp.net-core-webapisimple-injector

How do I access AppSettings.json fie in the DBContext of a .NET Core WebApi using Entity Framework Core and Simple Injector?


I am building an ASP.NET Core WebApi service using Entity Framework Core and the Simple Injector IoC Container. The application use a postgresql DB via Npgsql.EntityFrameworkCore.PostgeSQL.

Here is a code snippet from my Startup and ServicesInstaller:

public class Startup
{
    public IConfiguration Configuration { get; }
    private IConfigurationRoot configurationRoot;
    private Container container;

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;

        // Build configuration info
        configurationRoot = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", optional: true,
                reloadOnChange: true)
            .Build();
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();

        // Initialize Container
        container = new SimpleInjector.Container();
        container.Options.ResolveUnregisteredConcreteTypes = false;
        container.ConfigureServices();
        
        services.AddSimpleInjector(container, options =>
        {
            options.AddAspNetCore()
            .AddControllerActivation();
            options.AddLogging();
        });
    }
}

ServicesInstaller:

public static class ServicesInstaller
{
    public static void ConfigureServices(this Container container)
    {
        container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();

        //Assembly.Load will not re-load already loaded Assemblies
        container.Register<IFooContext, FooContext>(Lifestyle.Scoped);
        container.Register<FooContext>(Lifestyle.Scoped);
    }
}

Here is a code snippet from my DB Context class:

public interface IFooContext
{
}
    
public class FooContext : DbContext, IFooContext
{
    public FooContext()
    {
    }
    
    protected override void OnConfiguring(
        DbContextOptionsBuilder optionbuilder)
    {
        optionbuilder.UseNpgsql(
            "Server=.;Port=5432;Database=...;User ID=...;Password=...;");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    }
}

Currently I'm hardwiring my connection string to the PostGreSQL DB. I would like to be able to retrieve the connection string from my AppSettings.json file in the DB Context. I believe the correct place to do that would be within the OnConfiguring() method.

Is that correct?

Given this model, how do I correctly access the AppSettings.json file in the DBContext class?


Solution

  • When it comes to integration in ASP.NET Core icw Simple Injector and Entity Framework you've got 2 options:

    1. Register the DbContext in Simple Injector directly. This will let Simple Injector manage the lifetime of the DbContext.
    2. Register the DbContext in the framework's configuration system (i.e. IServiceCollection). In that case the DbContext can still be injected into other registrations you made with Simple Injector, because Simple Injector will "pull in" (a.k.a. cross wire) that dependency from the framework's configuration system.

    In most cases, you should prefer option 1 because letting Simple Injector manage the dependency also allows Simple Injector to verify and diagnose that registration.

    Due to the coupling between Entity Framework and the .NET Core configuration system, however, options 1 can can be easier to achieve. This is reflected in the Simple Injector documentation. It states:

    In some cases, however, framework and third-party components are tightly coupled to this new configuration system. Entity Framework’s DbContext pooling feature is an example where this tight coupling was introduced—pooling can practically only be used by configuring it in Microsoft’s IServiceCollection. As application developer, however, you wish to use Simple Injector to compose your application components. But those application components need to be fed with those framework and third-party services from time to time, which means framework components need to be pulled in from the .NET configuration system.

    This means that you can follow all Microsoft documentation about registering a DbContext. You can, for instance, register your DbContext as follows:

    // For the full example, see: https://simpleinjector.org/aspnetcore
    public class Startup
    {
        private Container container = new SimpleInjector.Container();
    
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
    
        public IConfiguration Configuration { get; }
    
        public void ConfigureServices(IServiceCollection services)
        {
            // ASP.NET default stuff here
            services.AddControllers();
    
            services.AddLogging();
    
            services.AddSimpleInjector(container, options =>
            {
                options.AddAspNetCore()
                    .AddControllerActivation();
    
                options.AddLogging();
            });
            
            // Add DbContext to IServiceCollection using AddDbContext.
            services.AddDbContext<FooContext>(
                options => options.UseNpgsql(
                    "Server=.;Port=5432;Database=...;User ID=...;Password=...;"));
    
            InitializeContainer();
        }
    
        private void InitializeContainer()
        {
            // Register all other classes using SImple Injector here. e.g.:
            container.Register<ITimeProvider, TimeProvider>(Lifestyle.Singleton);
        }
    
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseSimpleInjector(container);
    
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }
    
            app.UseStaticFiles();
            app.UseRouting();
            app.UseAuthorization();
    
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
    
            container.Verify();
        }
    }
    

    Because of Simple Injector's ability to cross wire dependencies, you can still inject FooContext into any class that is registered in, and created by Simple Injector.

    NOTE: Even though you register FooContext into the IServiceCollection, make sure you register any many classes as possible in Simple Injector. When using Simple Injector as your DI Container of choice, all your application components should be registered into Simple Injector. DbContext is the rare exception. Framework components, however, should still be registered into the IServiceCollection.