Search code examples
c#asp.net-coredependency-injectionautofacdbcontext

Autofac ResolvedParameter/ComponentContext not working


I am using asp.net core along with Entity Framework Core. My scenario here is, I want to change the connection string at runtime based on HttpContext query string value.

I am trying to pass ResolvedParameter with Reflection components as documented. But, It is not getting registered when I resolve this. Here below, I have attached my code snippet.

Autofac Registration class:

public class DependencyRegistrar : IDependencyRegistrar
{
    public virtual void Register(ContainerBuilder builder)
    {
        builder.RegisterType(typeof(DbContextOptionsFactory))
            .As(typeof(IDbContextOptionsFactory))
            .InstancePerRequest();

        builder.RegisterType<AppDbContext>()
            .As(typeof(IDbContext))
            .WithParameter(
                new ResolvedParameter(
                    (pi, cc) => pi.Name == "options",
                    (pi, cc) => cc.Resolve<IDbContextOptionsFactory>().Get()));

        builder.RegisterGeneric(typeof(Repository<>))
            .As(typeof(IRepository<>))
            .SingleInstance();
    }
}

public interface IDbContextOptionsFactory
{
    DbContextOptions<AppDbContext> Get();
}

public class DbContextOptionsFactory : IDbContextOptionsFactory
{
    public DbContextOptions<AppDbContext> Get()
    {
        try
        {
            IConfigurationRoot configuration = new ConfigurationBuilder()
                                            .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
                                            .AddJsonFile("appsettings.json")
                                            .Build();

            var builder = new DbContextOptionsBuilder<AppDbContext>();

            if (EngineContext.Current.Resolve<IHttpContextAccessor>().HttpContext.Request.QueryString.ToString().ToLower().Contains("app1"))
                DbContextConfigurer.Configure(builder, configuration.GetConnectionString("app1"));
            else if (EngineContext.Current.Resolve<IHttpContextAccessor>().HttpContext.Request.QueryString.ToString().ToLower().Contains("app2"))
                DbContextConfigurer.Configure(builder, configuration.GetConnectionString("app2"));

            return builder.Options;
        }
        catch (Exception)
        {
            throw;
        }
    }
}

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

AppDbContext:

public class AppDbContext : DbContext, IDbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options)
    {

    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        Assembly assemblyWithConfigurations = typeof(IDbContext).Assembly;
        builder.ApplyConfigurationsFromAssembly(assemblyWithConfigurations);
    }

    //..
    //..
}

During runtime, I am getting below error.

An unhandled exception occurred while processing the request.

DependencyResolutionException: None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'AppDbContext' can be invoked with the available services and parameters: Cannot resolve parameter 'Microsoft.EntityFrameworkCore.DbContextOptions1[AppDbContext] options' of constructor 'Void .ctor(Microsoft.EntityFrameworkCore.DbContextOptions1[AppDbContext])'. Autofac.Core.Activators.Reflection.ReflectionActivator.GetValidConstructorBindings(ConstructorInfo[] availableConstructors, IComponentContext context, IEnumerable parameters)

I have tried as answered here..but, not working.


Solution

  • I have resolved this issue by doing change as Guru Stron's comment and subsequent changes. Here is my change.

    Step 1: Changed Autofac Registration class as below:

    public class DependencyRegistrar : IDependencyRegistrar
    {
        public virtual void Register(ContainerBuilder builder)
        {
            //Removed this code
            //builder.RegisterType(typeof(DbContextOptionsFactory))
            //    .As(typeof(IDbContextOptionsFactory))
            //    .InstancePerRequest();
    
            //Added this code
            builder.Register(c => c.Resolve<IDbContextOptionsFactory>().Get())
                .InstancePerDependency();  // <-- Changed this line
    
            builder.RegisterType<AppDbContext>()
                .As(typeof(IDbContext))
                .WithParameter(
                    new ResolvedParameter(
                        (pi, cc) => pi.Name == "options",
                        (pi, cc) => cc.Resolve<IDbContextOptionsFactory>().Get()))
                .InstancePerDependency();  // <-- Added this line
    
            builder.RegisterGeneric(typeof(Repository<>))
                .As(typeof(IRepository<>))
                .InstancePerDependency();  // <-- Changed this line
        }
    }
    

    Now, if you getting the error:

    InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext.

    By add below code in AppDbContext class to resolve above error.

    Step 2: Modified AppDbContext (Added OnConfiguring() method)

    public class AppDbContext : DbContext, IDbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options)
            : base(options)
        {
    
        }
    
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
    
            Assembly assemblyWithConfigurations = typeof(IDbContext).Assembly;
            builder.ApplyConfigurationsFromAssembly(assemblyWithConfigurations);
        }
    
        //Added this method
        protected override void OnConfiguring(DbContextOptionsBuilder dbContextOptionsBuilder)
        {
            base.OnConfiguring(dbContextOptionsBuilder);
        }
    
        //..
        //..
    }