IoC DI for multitenant DbContext with .Net Core Identity

Viewed 1039

I have .Net Core app and want to setup IoC/DI for DbContext with .Net Core Identity:

public class DataContext : IdentityDbContext<User, IdentityRole<int>, int, IdentityUserClaim<int>, IdentityUserRole<int>, IdentityUserLogin<int>, IdentityRoleClaim<int>, IdentityUserToken<int>>, IDataContext
{
    private readonly string _dbName;

    public DataContext(string dbName)
    {
        this._dbName = dbName;
    }

    public virtual DbSet<Container> Containers { get; set; }

    public virtual DbSet<Note> Notes { get; set; }

    public virtual DbSet<Post> Posts { get; set; }

    public virtual DbSet<Email> Emails { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(ConnectionStrings.DataContext.Replace(":dbname", this._dbName));
        base.OnConfiguring(optionsBuilder);
    }
}

Interface:

public interface IDataContext
{
    DbSet<Container> Containers { get; set; }

    DbSet<Note> Notes { get; set; }

    DbSet<Post> Posts { get; set; }

    DbSet<Email> Emails { get; set; }
}

In Startup.cs I have the follwoing line:

services.AddDbContext<DataContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DataContext")));

But that connection string doesn't contain db name.

Should I include into interface such tables as: AspNetUsers, AspNetRoles, ...?

And I want to write in Startup.cs something like that:

services.AddScoped<IDataContext, DataContext>();

And use interface everywhere instead the whole class.

Can I do that? And how can I do that?

1 Answers

When you use Entity Framework's AddDbContext method it will register the context in the service collection for you.

 services.AddDbContext<DataContext>(ServiceLifetime.Scoped);

But you could register your interface like the following.

services.AddScoped(typeof(IDataContext), provider => provider.GetService<DataContext>());

Then you can work with the interface. But the Identity Services will still use the concrete type.


PS: Do you know the repository pattern? It enables you to easely seperate the data layer from the service layer. This helps a lot for example for unit tests.


The problem with your DataContext constructor is that the dependency container doesn't know how to resolve the string in the constructor. Generally when you work with configurations in asp.net core you can use the IOptions type and its infrastructure.

In the startup you can add the options and configure it.

public class DatabaseOptions
{
    public string DbName { get; set; }
}

services.AddOptions();
services.Configure<DatabaseOptions>(options => options.DbName = "myDbName");

And then you can change the constuctor of your DataContext to the following.

public DataContext(IOptions<DatabaseOptions> options)

If you do not know the configuration values at the startup you could write an context type which you configure in a middleware or an action filter.

public class DbConfigurationContext
{
    public string DbName { get; set; }
}

public class DbConfigurationContextFilter : IAsyncActionFilter
{
    private readonly DbConfigurationContext _dbConfiguration;

    public DbConfigurationContextFilter(DbConfigurationContext dbConfiguration)
    {
        _dbConfiguration = dbConfiguration;
    }

    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        _dbConfiguration.DbName = context.HttpContext.Request.Query["DbName"];
        await next();
    }
}


services.AddTransient<DbConfigurationContext>();
services.AddTransient<DbConfigurationContextFilter>();
services.AddMvc(setup =>
{
    setup.Filters.AddService(typeof(DbConfigurationContextFilter));
})

And then you could resolve this DbConfigurationContext in your DbContext. Of corse you could also use an interface.

public DataContext(DbConfigurationContext configurationContext)
Related