Multitenancyserver bertujuan untuk menjadi paket ringan untuk menambahkan dukungan multi-tenancy ke basis kode apa pun dengan mudah. Desainnya sangat dipengaruhi dari identitas inti ASP.NET. Anda dapat menambahkan dukungan multi-tenancy ke model Anda tanpa menambahkan properti kunci penyewa ke kelas atau entitas apa pun. Menggunakan ASP.NET Core, penyewa saat ini dapat diambil dengan nama domain khusus, sub-domain, nama host parsial, header permintaan HTTP, jalur URL anak atau parsial, parameter string kueri, klaim pengguna yang diautentikasi, atau implementasi parser permintaan khusus. Menggunakan inti Entity Framework, tombol penyewa ditambahkan sebagai properti bayangan (atau properti konkret opsional) dan ditegakkan melalui filter kueri global, semua opsi yang dapat dikonfigurasi dapat diatur dari default atau overidden per entitas. Contoh di bawah ini menyoroti cara menggunakan multitenancyserver dengan identitas inti ASP.NET dan IdentityServer4 bersama -sama. Anda dapat menemukan banyak sampel kerja penuh yang terintegrasi dengan IdentityServer4, Asp.net Core Identity (menggunakan berbagai jenis kunci seperti string dan int64), dan inti kerangka kerja entitas dalam repo sampel.
Tentukan model penyewa Anda sendiri, atau warisan dari TenancyTenant, atau hanya menggunakan TenancyTenant apa adanya. Dalam contoh ini kita akan mewarisi dari TenancyTenant dan menambahkan nama tampilan.
public class Tenant : TenancyTenant
{
// Custom property for display name of tenant.
public string Name { get ; set ; }
} Contoh Menambahkan Dukungan Multi-Tenancy ke ASP.NET Core.
public void ConfigureServices ( IServiceCollection services )
{
var connectionString = Configuration . GetConnectionString ( "DefaultConnection" ) ;
var migrationsAssembly = typeof ( AppDbContext ) . GetTypeInfo ( ) . Assembly . GetName ( ) . Name ;
services . AddDbContext < AppDbContext > ( options =>
{
options . UseSqlServer ( connectionString , sql => sql . MigrationsAssembly ( migrationsAssembly ) ) ;
} ) ;
// Add Multi-Tenancy Server defining TTenant<TKey> as type Tenant with an ID (key) of type string.
services . AddMultiTenancy < Tenant , string > ( )
// Add one or more IRequestParser (MultiTenancyServer.AspNetCore).
. AddRequestParsers ( parsers =>
{
// Parsers are processed in the order they are added,
// typically 1 or 2 parsers should be all you need.
parsers
// www.tenant1.com
. AddDomainParser ( )
// tenant1.tenants.multitenancyserver.io
. AddSubdomainParser ( ".tenants.multitenancyserver.io" )
// from partial hostname
. AddHostnameParser ( "^(regular_expression)$" )
// HTTP header X-TENANT = tenant1
. AddHeaderParser ( "X-TENANT" )
// /tenants/tenant1
. AddChildPathParser ( "/tenants/" )
// from partial path
. AddPathParser ( "^(regular_expression)$" )
// ?tenant=tenant1
. AddQueryParser ( "tenant" )
// Claim from authenticated user principal.
. AddClaimParser ( "http://schemas.microsoft.com/identity/claims/tenantid" )
// Add custom request parser with lambda.
. AddCustomParser ( httpContext => "tenant1" ) ;
// Add custom request parser implementation.
. AddMyCustomParser ( ) ;
} )
// Use in memory tenant store for development (MultiTenancyServer.Stores)
. AddInMemoryStore ( new Tenant [ ]
{
new Tenant ( )
{
Id = "TENANT_1" ,
CanonicalName = "Tenant1" ,
NormalizedCanonicalName = "TENANT1"
}
} )
// Use EF Core store for production (MultiTenancyServer.EntityFrameworkCore).
. AddEntityFrameworkStore < AppDbContext , Tenant , string > ( )
// Use custom store.
. AddMyCustomStore ( ) ;
// Add ASP.NET Core Identity
services . AddIdentity < User , Role > ( )
. AddEntityFrameworkStores < AppDbContext > ( )
. AddDefaultTokenProviders ( ) ;
// Add Identity Server 4
var builder = services . AddIdentityServer ( )
. AddAspNetIdentity < User > ( )
// Add the config data from DB (clients, resources)
. AddConfigurationStore < AppDbContext > ( options =>
{
options . ConfigureDbContext = b =>
b . UseSqlServer ( connectionString ,
sql => sql . MigrationsAssembly ( migrationsAssemblyName ) ) ;
} )
// Add the operational data from DB (codes, tokens, consents)
. AddOperationalStore < AppDbContext > ( options =>
{
options . ConfigureDbContext = b =>
b . UseSqlServer ( connectionString ,
sql => sql . MigrationsAssembly ( migrationsAssemblyName ) ) ;
} ) ;
if ( Environment . IsDevelopment ( ) )
{
builder . AddDeveloperSigningCredential ( ) ;
}
else
{
throw new Exception ( "Key not configured." ) ;
}
} Contoh mengkonfigurasi aplikasi dengan dukungan multi-tenancy untuk ASP.NET Core MVC dan IdentityServer4.
public void Configure ( IApplicationBuilder app )
{
// other code removed for brevity
app . UseStaticFiles ( ) ;
app . UseMultiTenancy < Tenant > ( ) ;
app . UseIdentityServer ( ) ;
app . UseAuthentication ( ) ;
app . UseMvcWithDefaultRoute ( ) ;
} Contoh DBContext dengan dukungan multi-tenancy untuk identitas inti ASP.NET dan IdentityServer4.
public class AppDbContext :
// ASP.NET Core Identity EF Core
IdentityDbContext < User , Role , string , UserClaim , UserRole , UserLogin , RoleClaim , UserToken > ,
// IdentityServer4 EF Core
IConfigurationDbContext , IPersistedGrantDbContext ,
// MultiTenancyServer EF Core
ITenantDbContext < Tenant , string >
{
private static object _tenancyModelState ;
private readonly ITenancyContext < Tenant > _tenancyContext ;
public AppDbContext (
DbContextOptions < AppDbContext > options ,
ITenancyContext < Tenant > tenancyContext )
: base ( options )
{
// The request scoped tenancy context.
// Should not access the tenancyContext.Tenant property in the constructor yet,
// as the request pipeline has not finished running yet and it will likely be null.
_tenancyContext = tenancyContext ;
}
// IdentityServer4 implementation.
public DbSet < Client > Clients { get ; set ; }
public DbSet < IdentityResource > IdentityResources { get ; set ; }
public DbSet < ApiResource > ApiResources { get ; set ; }
public DbSet < PersistedGrant > PersistedGrants { get ; set ; }
// MultiTenancyServer implementation.
public DbSet < Tenant > Tenants { get ; set ; }
protected override void OnModelCreating ( ModelBuilder builder )
{
base . OnModelCreating ( builder ) ;
// IdentityServer4 configuration.
var configurationStoreOptions = new ConfigurationStoreOptions ( ) ;
builder . ConfigureClientContext ( configurationStoreOptions ) ;
builder . ConfigureResourcesContext ( configurationStoreOptions ) ;
var operationalStoreOptions = new OperationalStoreOptions ( ) ;
builder . ConfigurePersistedGrantContext ( operationalStoreOptions ) ;
// MultiTenancyServer configuration.
var tenantStoreOptions = new TenantStoreOptions ( ) ;
builder . ConfigureTenantContext < Tenant , string > ( tenantStoreOptions ) ;
// Add multi-tenancy support to model.
var tenantReferenceOptions = new TenantReferenceOptions ( ) ;
builder . HasTenancy < string > ( tenantReferenceOptions , out _tenancyModelState ) ;
// Configure custom properties on Tenant (MultiTenancyServer).
builder . Entity < Tenant > ( b =>
{
b . Property ( t => t . Name ) . HasMaxLength ( 256 ) ;
} ) ;
// Configure properties on User (ASP.NET Core Identity).
builder . Entity < User > ( b =>
{
// Add multi-tenancy support to entity.
b . HasTenancy ( ( ) => _tenantId , _tenancyModelState , hasIndex : false ) ;
// Remove unique index on NormalizedUserName.
b . HasIndex ( u => u . NormalizedUserName ) . HasName ( "UserNameIndex" ) . IsUnique ( false ) ;
// Add unique index on TenantId and NormalizedUserName.
b . HasIndex ( tenantReferenceOptions . ReferenceName , nameof ( User . NormalizedUserName ) )
. HasName ( "TenantUserNameIndex" ) . IsUnique ( ) ;
} ) ;
// Configure properties on Role (ASP.NET Core Identity).
builder . Entity < Role > ( b =>
{
// Add multi-tenancy support to entity.
b . HasTenancy ( ( ) => _tenancyContext . Tenant . Id , _tenancyModelState , hasIndex : false ) ;
// Remove unique index on NormalizedName.
b . HasIndex ( r => r . NormalizedName ) . HasName ( "RoleNameIndex" ) . IsUnique ( false ) ;
// Add unique index on TenantId and NormalizedName.
b . HasIndex ( tenantReferenceOptions . ReferenceName , nameof ( Role . NormalizedName ) )
. HasName ( "TenantRoleNameIndex" ) . IsUnique ( ) ;
} ) ;
// Configure properties on Client (IdentityServer4).
builder . Entity < Client > ( b =>
{
// Add multi-tenancy support to entity.
b . HasTenancy ( ( ) => _tenancyContext . Tenant . Id , _tenancyModelState , hasIndex : false ) ;
// Remove unique index on ClientId.
b . HasIndex ( c => c . ClientId ) . IsUnique ( false ) ;
// Add unique index on TenantId and ClientId.
b . HasIndex ( tenantReferenceOptions . ReferenceName , nameof ( Client . ClientId ) ) . IsUnique ( ) ;
} ) ;
// Configure properties on IdentityResource (IdentityServer4).
builder . Entity < IdentityResource > ( b =>
{
// Add multi-tenancy support to entity.
b . HasTenancy ( ( ) => _tenancyContext . Tenant . Id , _tenancyModelState , hasIndex : false ) ;
// Remove unique index on Name.
b . HasIndex ( r => r . Name ) . IsUnique ( false ) ;
// Add unique index on TenantId and Name.
b . HasIndex ( tenantReferenceOptions . ReferenceName , nameof ( IdentityResource . Name ) ) . IsUnique ( ) ;
} ) ;
// Configure properties on ApiResource (IdentityServer4).
builder . Entity < ApiResource > ( b =>
{
// Add multi-tenancy support to entity.
b . HasTenancy ( ( ) => _tenancyContext . Tenant . Id , _tenancyModelState , hasIndex : false ) ;
// Remove unique index on Name.
b . HasIndex ( r => r . Name ) . IsUnique ( false ) ;
// Add unique index on TenantId and Name.
b . HasIndex ( tenantReferenceOptions . ReferenceName , nameof ( ApiResource . Name ) ) . IsUnique ( ) ;
} ) ;
// Configure properties on ApiScope (IdentityServer4).
builder . Entity < ApiScope > ( b =>
{
// Add multi-tenancy support to entity.
b . HasTenancy ( ( ) => _tenancyContext . Tenant . Id , _tenancyModelState , hasIndex : false ) ;
// Remove unique index on Name.
b . HasIndex ( s => s . Name ) . IsUnique ( false ) ;
// Add unique index on TenantId and Name.
b . HasIndex ( tenantReferenceOptions . ReferenceName , nameof ( ApiScope . Name ) ) . IsUnique ( ) ;
} ) ;
// Configure properties on PersistedGrant (IdentityServer4).
builder . Entity < PersistedGrant > ( b =>
{
// Add multi-tenancy support to entity.
b . HasTenancy ( ( ) => _tenancyContext . Tenant . Id , _tenancyModelState ) ;
} ) ;
}
public override int SaveChanges ( bool acceptAllChangesOnSuccess )
{
// Ensure multi-tenancy for all tenantable entities.
this . EnsureTenancy ( _tenancyContext ? . Tenant ? . Id , _tenancyModelState , _logger ) ;
return base . SaveChanges ( acceptAllChangesOnSuccess ) ;
}
public override Task < int > SaveChangesAsync ( bool acceptAllChangesOnSuccess , CancellationToken cancellationToken = default )
{
// Ensure multi-tenancy for all tenantable entities.
this . EnsureTenancy ( _tenancyContext ? . Tenant ? . Id , _tenancyModelState , _logger ) ;
return base . SaveChangesAsync ( acceptAllChangesOnSuccess , cancellationToken ) ;
}
} public class TenantReferenceOptions
{
// Summary:
// If set to a non-null value, the store will use this value as the name for the
// tenant's reference property. The default is "TenantId".
public string ReferenceName { get ; set ; }
// Summary:
// True to enable indexing of tenant reference properties in the store, otherwise
// false. The default is true.
public bool IndexReferences { get ; set ; }
// Summary:
// If set to a non-null value, the store will use this value as the name of the
// index for any tenant references. The name is also a format pattern of {0:PropertyName}.
// The default is "{0}Index", eg. "TenantIdIndex".
public string IndexNameFormat { get ; set ; }
// Summary:
// Determines if a null tenant reference is allowed for entities and how querying
// for null tenant references is handled.
public NullTenantReferenceHandling NullTenantReferenceHandling { get ; set ; }
}
public enum NullTenantReferenceHandling
{
// Summary:
// A null tenant reference is NOT allowed for the entity, where possible a NOT NULL
// or REQUIRED constraint should be set on the tenant reference, querying for entities
// with a null tenant reference will match NO entities.
// This is the default option.
NotNullDenyAccess = 0 ,
// Summary:
// A null tenant reference is allowed for the entity, where possible an NULLABLE
// or OPTIONAL constraint should be set on the tenant reference, querying for entities
// with a null tenant reference will match those expected results.
// This may be useful where globally defined system entities are set with a null
// tenant reference.
NullableEntityAccess = 1 ,
// Summary:
// A null tenant reference is NOT allowed for the entity, where possible a NOT NULL
// or REQUIRED constraint should be set on the tenant reference, querying for entities
// with a null tenant reference will match ALL entities across all tenants.
// For obvious security reasons, this is typically not recommended; however, this
// can be useful for admin reporting across all tenants.
NotNullGlobalAccess = 2
} Microsoft.aspnetcore.hosting.internal.webhost: Informasi: Permintaan Mulai http/1.1 Posting http: // localhost : 5020/akun/login? ReturnUrl =%2Fgrants Aplikasi/X-WWW-Form-Burlencoded 267
Microsoft.entityframeworkcore.database.command: Informasi: dieksekusi dbCommand (3ms) [parameter = [@__ dinormalisasi CanNonicalName_0 = ' localhost ' (size = 256)], commandType = 'Teks', commandtimeout = '30 '] Select Top (1) [u]. [u]. [Nama], [u]. [NormalizedCanonicalName] dari [penyewa] sebagai [u] di mana [u]. [NormalizedCanonicalName] = @__ dinormalizedCanonicname_0
Multitenancyserver.http.htttptenancyprovider: debug: penyewa test_tenant_1 ditemukan oleh domainparser untuk nilai localhost dalam permintaan http: // localhost : 5020/akun/login? ReturnUrl =%2fgrants.
Microsoft.entityframeworkcore.database.command: Informasi: dieksekusi dbCommand (13ms) [parameter = [@___ tenantid_0 = ' test_tenant_1 ' (size = 4000), @__ normalizedUserName_0 = 'alice' (size = 256)], commandtype = 'command'] [command '] (u) (size = 256)], commandtype =' command '] [command'] = 'ALICE' (size = 256)], commandType = 'command' command '] ='] (size ') (size = 256)], commandType =' Command 'command'] = 'ALICE' (size = 256)], commandType = 'Command' command ' [u]. [Nama pengguna] dari [pengguna] sebagai [u] di mana (@___ tenantid_0 tidak nol dan ([u]. [TenantId] = @___ tenantid_0)) dan ([u]. [NormalizedUserName] = @__ dinormalisasiName_0)
Microsoft.entityframeworkcore.database.command: Informasi: dieksekusi dbCommand (1ms) [parameter = [@___ tenantid_0 = ' test_tenant_1 ' (ukuran = 4000), @__), 1AB99036-8AC1-4270-8A1E-2E-398 (3AB99036-8AC1-4270-8A1E-2E-398 ('3AB99036-8AC1-4270-8A1E-3198 (' 3AB9988888 ('3AB9988 (' 3AB998 (8AC198 (3AB998 (4AB998 ( CommandType = 'Text', CommandTimeOut = '30 '] Pilih [p]. [KEY], [p]. [ClientId], [p]. [CreationTime], [p]. [Data], [p]. [Expiration], [p]. [Subjek], [p] [PENTAND], [p]. [Tipe] dari [not not -not] [p]@[penantid], [p]. [Tipe] dari [not not -not -not] [p]@[penantid], [p]. [Type] dari [not not -notsgrants] [p] [penantid], [p]. [TYPED] dari [not not -notsgrants] [p] [pewek], [P] ([p]. [TenantId] = @___ TenantID_0)) dan ([p]. [Subject] = @__ SubjekID_0)