Kyle Barnes
Kyle Barnes

Reputation: 781

AutoFac / .NET Core - Register DBcontext

I have a new .NET Core Web API project that has the following projects structure:

API -> Business / Domain -> Infrastructure

The API is very thin with only the API methods. The Business / Domain layer has all my business logic. And finally, my Infrastructure layer has my DB classes using EF Core 2.0.

I know using .NET Core built-in Dependency Injection I can add a reference from the API project to the Infrastructure project, then add the following code in the StartUp.cs file:

services.AddDbContext<MyContext>(options => options.UseSqlServer(connectionString));

However, I would like to maintain a more traditional separation of concerns. So far I have added a module in my Infrastructure layer that attempts to make the registration like so:

builder.Register(c =>
        {
            var config = c.Resolve<IConfiguration>();

            var opt = new DbContextOptionsBuilder<MyContext>();
            opt.UseSqlServer(config.GetSection("ConnectionStrings:MyConnection:ConnectionString").Value);

            return new MyContext(opt.Options);
        }).AsImplementedInterfaces().InstancePerLifetimeScope();

The DBContext, however, is not getting registered. Any class that attempts to access the injected DBContext cannot resolve the parameter.

Is there a way to register the DBContext in a separate project using AuftoFac in a .NET Core Web API Project?

Upvotes: 36

Views: 32424

Answers (5)

rdadkins
rdadkins

Reputation: 353

Here's an implementation I use - it mimics EF Core 3.1 registration with Autofac 4.9.4. Be sure to adjust scopes per your requirements.

public static void RegisterDbContext<TContext>(this ContainerBuilder builder)
    where TContext : DbContext
{
    builder.Register(componentContext =>
        {
            var serviceProvider = componentContext.Resolve<IServiceProvider>();
            var configuration = componentContext.Resolve<IConfiguration>();
            var dbContextOptions = new DbContextOptions<TContext>(new Dictionary<Type, IDbContextOptionsExtension>());
            var optionsBuilder = new DbContextOptionsBuilder<TContext>(dbContextOptions)
                .UseApplicationServiceProvider(serviceProvider)
                .UseSqlServer(configuration.GetConnectionString("MyConnectionString"),
                    serverOptions => serverOptions.EnableRetryOnFailure(5, TimeSpan.FromSeconds(30), null));

            return optionsBuilder.Options;
        }).As<DbContextOptions<TContext>>()
        .InstancePerLifetimeScope();

    builder.Register(context => context.Resolve<DbContextOptions<TContext>>())
        .As<DbContextOptions>()
        .InstancePerLifetimeScope();

    builder.RegisterType<TContext>()
        .AsSelf()
        .InstancePerLifetimeScope();
}

Upvotes: 7

Alex Herman
Alex Herman

Reputation: 2848

I use Autofac to register both HttpContextAccessor and DbContext.

builder
    .RegisterType<HttpContextAccessor>()
    .As<IHttpContextAccessor>()
    .SingleInstance();

builder
    .RegisterType<AppDbContext>()
    .WithParameter("options", DbContextOptionsFactory.Get())
    .InstancePerLifetimeScope();

DbContextOptionsFactory

public class DbContextOptionsFactory
{
    public static DbContextOptions<AppDbContext> Get()
    {
        var configuration = AppConfigurations.Get(
            WebContentDirectoryFinder.CalculateContentRootFolder());

        var builder = new DbContextOptionsBuilder<AppDbContext>();
        DbContextConfigurer.Configure(
            builder, 
            configuration.GetConnectionString(
                AppConsts.ConnectionStringName));

        return builder.Options;
    }
}

DbContextConfigurer

public class DbContextConfigurer
{
    public static void Configure(
        DbContextOptionsBuilder<AppDbContext> builder, 
        string connectionString)
    {
        builder.UseNpgsql(connectionString).UseLazyLoadingProxies();
    }
}

Upvotes: 26

&#199;ağlar Duman
&#199;ağlar Duman

Reputation: 153

Another simple solution for Autofac version 4.8.1

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().AddControllersAsServices();

        services.AddDbContext<MyContext>(options => options.UseSqlServer(Configuration.GetConnectionString("ConnectionStrings:MyConnection:ConnectionString")));

        var builder = new ContainerBuilder();

        builder.Populate(services);

        //...
        // Your interface registration
        //...

        builder.Build(Autofac.Builder.ContainerBuildOptions.None);
    }

Upvotes: 8

Alexander Leonov
Alexander Leonov

Reputation: 4794

I think that the problem is that you're trying to register MyContext() using AsImplementedInterfaces(). This is not how DbContext are getting registered usually. You should register and resolve class itself.

Upvotes: 22

Nkosi
Nkosi

Reputation: 247531

In the desired project you can create an extension method that adds the context to the collection

public static class MyDataExtensions {
    public static IServiceCollection AddMyData(this IServiceCollection services) {
        //...

        services.AddDbContext<MyContext>(options => options.UseSqlServer(connectionString));

        //...
    }
}

with that then in your start up it is just a matter of calling the extension exposed from the other project

services.AddMyData();

//...other settings

The API project is the composition root, so it needs to know all the relevant dependencies anyway. At least with this extension you do not have to make direct reference of the used db context,

Upvotes: 1

Related Questions