0

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?

Steven
  • 166,672
  • 24
  • 332
  • 435
JohnB
  • 3,921
  • 8
  • 49
  • 99
  • You can try to take a look at this other question, I think the first answer meets your requirements. https://stackoverflow.com/questions/59499635/how-to-read-connection-string-from-appsettings-json-in-a-dbcontext-file-in-asp-n – Danielle Lpz Mar 22 '21 at 16:37

1 Answers1

0

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.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • So by doing the work in ConfigureServices(), I no longer need to override OnConfiguring() in FooContext? – JohnB Mar 23 '21 at 03:21
  • I'm still a little confused here. In my example, I'm registering services on a SimpleInjector.Container object. In the sample you provided, you are working against a ServiceCollection (I assume not using SimpleInjector). I'm new to using SimpleInjector and trying to figure it out. – JohnB Mar 23 '21 at 03:28
  • @JohnB: In my answer, only `DbContext` is registered in the `ServiceCollection`. I rewrote my answer; hopefully it makes things clearer. Also read the referenced integration pages of the Simple Injector documentation. – Steven Mar 23 '21 at 07:56