Reputation: 3344
I am trying to configure multiple tenants in ASP.NET Core 8 MVC project.
I am using
My program.cs
is shown here:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// 2. Configure multi-tenancy.
builder.Services.AddMultiTenant<AppTenantInfo>()
.WithEFCoreStore<TenantDbContext, AppTenantInfo>()
.WithHostStrategy();
// 1. Register TenantDbContext.
builder.Services.AddDbContext<TenantDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("MultiTenantConnection")));
builder.Services.TryAddScoped<ITenantInfo>(sp =>
sp.GetRequiredService<IMultiTenantContext<AppTenantInfo>>().TenantInfo);
// Optionally, add a fallback registration for ITenantInfo.
builder.Services.AddScoped<ITenantInfo>(sp =>
{
// Try to resolve the multi-tenant context.
var multiTenantContext = sp.GetService<IMultiTenantContext<AppTenantInfo>>();
if (multiTenantContext?.TenantInfo != null)
{
return multiTenantContext.TenantInfo;
}
// Fallback: return a default tenant info(adjust as needed for your scenario).
return new AppTenantInfo
{
Id = "default",
Identifier = "default",
Name = "Default Tenant",
ConnectionString = builder.Configuration.GetConnectionString("DefaultConnection")
};
});
//builder.Services.TryAddScoped<IMultiTenantContext<AppTenantInfo>, MultiTenantContext<AppTenantInfo>>();
// Configure the main application DbContext using the tenant-specific connection string.
builder.Services.AddDbContext<CliniqueDbContext>((serviceProvider, options) =>
{
// Resolve the current tenant information.
var tenantInfo = serviceProvider.GetRequiredService<ITenantInfo>() as AppTenantInfo;
// Use the tenant's connection string (ensure it’s available in production scenarios).
options.UseNpgsql(tenantInfo?.ConnectionString);
});
// 4. Register the Identity DbContext for authentication
builder.Services.AddDbContext<ApplicationDbContext>((serviceProvider, options) =>
{
var tenantInfo = serviceProvider.GetRequiredService<ITenantInfo>() as AppTenantInfo;
// Here we use the tenant's connection string to store Identity data.
options.UseNpgsql(tenantInfo?.ConnectionString);
});
// ...
var app = builder.Build();
// Enable multi-tenancy middleware.
app.UseMultiTenant();
I have 3 database contexts
TenantDbContext
:
Running the initial migration for this context will create the database that stores tenant configuration (including connection strings).
CliniqueDbContext
:
Migrations here will update your business data database (patients, appointments, etc.) for the current tenant.
ApplicationDbContext
:
Migrations for this context will set up the Identity database for user authentication and roles.
I have configured host file and launchsetting.json
to test multi-tenancy in development environment.
Host file:
127.0.0.1 cli1.clinique360.com
127.0.0.1 cli2.clinique360.com
lauchsettings.json
:
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:46541",
"sslPort": 44367
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://*:5146",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7292;http://localhost:5146",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Clinique360.Web": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "http://*:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
But when I run the application it returns null for multiTenantContext
:
builder.Services.AddScoped<ITenantInfo>(sp =>
{
// Try to resolve the multi-tenant context.
var multiTenantContext = sp.GetService<IMultiTenantContext<AppTenantInfo>>();
if (multiTenantContext?.TenantInfo != null)
{
return multiTenantContext.TenantInfo;
}
// Fallback: return a default tenant info(adjust as needed for your scenario).
return new AppTenantInfo
{
Id = "default",
Identifier = "default",
Name = "Default Tenant",
ConnectionString = builder.Configuration.GetConnectionString("DefaultConnection")
};
});
What can be the issue?
Upvotes: 1
Views: 47
Reputation: 742
From my understanding, ITenantInfo is not available for DI from version 7 onwards. As per finbuckle's version history page, (I)MultiTenantContext and (I)TenantInfo are no longer available via dependency injection. Use IMultiTenantContextAccessor instead. MultiTenantDbContext and MultiTenantIdentityDbContext will require a new constructor that injects IMultiTenantContextAccessor or IMultiTenantContext. Please see this link for more info: https://www.finbuckle.com/MultiTenant/Docs/v7.0.2/History?redirectedToLatest
Instead, try this:
builder.Services.AddDbContext<TenantDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("MultiTenantConnection")));
//when you register your appdbcontext, you don't have to specify connection strings or it's database provider;
builder.Services.AddDbContext<CliniqueDbContext>();
builder.Services.AddDbContext<ApplicationDbContext>();
builder.Services.AddMultiTenant<AppTenantInfo>()
.WithEFCoreStore<TenantDbContext, AppTenantInfo>()
.WithHostStrategy();
//.WithStaticStrategy("default_tenant");
var app = builder.Build();
app.UseMultiTenant();
app.Run();
In your CliniqueDbContext and AppDbContext, inject IMultiTenantContextAccessor like this to access TenantInfo and set the default:
public class AppDbContext : DbContext
{
private readonly ITenantInfo? _tenant;
private readonly IWebHostEnvironment _env;
private readonly IConfiguration _config;
public AppDbContext (
DbContextOptions<AppDbContext> options,
IWebHostEnvironment env,
IMultiTenantContextAccessor multiTenantContextAccessor,
IConfiguration config)
: base(options)
{
_tenant = multiTenantContextAccessor.MultiTenantContext?.TenantInfo;
_env = env;
_config = config;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (_tenant is null)
{
_tenant = new AppTenantInfo(){
Id = "default",
Identifier = "default",
Name = "Default Tenant",
ConnectionString = _config.GetConnectionString("DefaultConnection")
};
}
optionsBuilder.UseNpgsql(_tenant.ConnectionString);
base.OnConfiguring(optionsBuilder);
}
}
Upvotes: 0