This core feature is useful to verify other core features that we have built because it is developed as a microservice. It’s integrated with Finbuckle to identity the tenant. We provide multiple choices for tenant data store:
We also provide the gRPC API to manage tenant information and tenant settings, so MultiTenant itself is a service with the full implementation of:
To use multi-tenant, we must register services with Tenant type
var tenantBuilder = builder.Services
// default Finbuckle services
.AddMultiTenant<Tenant>(options =>
{
// configure tenant options
})
.WithBasePathStrategy(options => options.RebaseAspNetCorePathBase = true)
// AND/OR other strategies
;
After that, we have 2 choices:
So we will register services for tenant host inside tenant microservice.
...
using Juice.MultiTenant.Api.DependencyInjection;
...
tenantBuilder.ConfigureTenantHost(builder.Configuration, options =>
{
options.DatabaseProvider = "PostgreSQL";
options.ConnectionName = "PostgreConnection";
options.Schema = "App";
});
Then register services for tenant client inside other microservices.
...
using Juice.MultiTenant.Grpc.Finbuckle.DependencyInjection;
...
tenantBuilder.ConfigureTenantClient(builder.Configuration, builder.Environment.EnvironmentName);
...
using Juice.MultiTenant.EF.DependencyInjection;
...
tenantBuilder.ConfigureTenantEFDirectly(builder.Configuration, options =>
{
options.DatabaseProvider = "PostgreSQL";
options.ConnectionName = "PostgreConnection";
options.Schema = "App";
}, builder.Environment.EnvironmentName);
...
using Juice.MultiTenant.Finbuckle.DependencyInjection;
...
tenantBuilder.JuiceIntegration()
.WithGprcStore(tenantGrpcEndpoint)
.WithDistributedCacheStore()
// .WithEFStore(...)
// you can add many stores at the same time
;
We provide a custom WithPerTenantAuthenticationConventions extension beside the original WithPerTenantAuthenticationConventions from Finbuckle. It allows you to handle cross-tenant authorization yourself.
using Juice.MultiTenant.AspNetCore;
...
services
.AddMultiTenant()
.WithBasePathStrategy(options => options.RebaseAspNetCorePathBase = true)
.ConfigureTenantEFDirectly(configuration, options =>
{
options.DatabaseProvider = "PostgreSQL";
options.ConnectionName = "TenantDbConnection";
options.Schema = "App";
}, environment.EnvironmentName)
.WithPerTenantOptions<OpenIdConnectOptions>((options, tc) =>
{
options.Authority = authority + $"/{tc.Identifier}";
})
.WithPerTenantAuthenticationCore() // it's required
.WithPerTenantAuthenticationConventions(crossTenantAuthorize: (authTenant, currentTenant, principal) =>
authTenant == null // root tenant
&& (principal?.Identity?.IsAuthenticated ?? false) // authenticated
&& principal.IsInRole("admin"))
.WithRemoteAuthenticationCallbackStrategy()
;
The library can be accessed via Nuget and npmjs:
Please follow up on Data Isolation with Entity Framework Core to understand this section.
We usually have two options for storing per-tenant application data:
In the second way, we will create a TenantDbContext that implement IMultiTenantDbContext interface. So we provide a MultiTenantDbContext abstraction class that implement these interfaces:
You can inherit MultiTenantDbContext abstraction and mark your entity IsMultiTenant(), and/or IsAuditable().
using Juice.EF.Extensions;
using Juice.EF.MultiTenant;
...
public class TenantContentDbContext : MultiTenantDbContext
{
public DbSet<TenantContent> TenantContents { get; set; }
public TenantContentDbContext(IServiceProvider serviceProvider, DbContextOptions options) : base(options)
{
ConfigureServices(serviceProvider);
}
protected override void ConfigureModel(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TenantContent>(options =>
{
options.ToTable(nameof(TenantContent), Schema);
options.IsMultiTenant();
options.IsAuditable();
});
}
}
Or using the [MultiTenant] attribute in entity class
using Finbuckle.MultiTenant;
[MultiTenant]
public class TenantContent
{
...
}
See MultiTenantDbContext for more information.
The library can be accessed via Nuget: