يهدف MultiteNancyserver إلى أن تكون حزمة خفيفة الوزن لإضافة دعم متعدد المستأجرين إلى أي قاعدة كود بسهولة. يتأثر تصميمه بشدة من هوية ASP.NET الأساسية. يمكنك إضافة دعم متعدد المستأجرين إلى النموذج الخاص بك دون إضافة أي خصائص رئيسية للمستأجر إلى أي فئات أو كيانات. باستخدام ASP.NET Core ، يمكن استرداد المستأجر الحالي عن طريق اسم مجال مخصص أو المجال الفرعي أو اسم المضيف الجزئي أو رأس طلب HTTP أو مسار عنوان URL أو الجزئي أو معلمة سلسلة الاستعلام أو مطالبة المستخدم المصادقة أو تنفيذ محلل طلب مخصص. باستخدام Entity Framework Core ، تتم إضافة مفاتيح المستأجر كخصائص Shadow (أو خصائص ملموسة اختياريًا) وتنفيذها من خلال مرشحات الاستعلام العالمية ، يمكن تعيين جميع الخيارات القابلة للتكوين من الافتراضات أو المبالغة في كل كيان. يسلط المثال أدناه الضوء على كيفية استخدام MultiteNancyserver مع ASP.NET Core Identity و IdentityServer4 معًا. يمكنك العثور على العديد من عينات العمل الكاملة المدمجة مع IdentityServer4 ، ASP.NET Core Identity (باستخدام أنواع مختلفة من المفاتيح مثل String و Int64) ، و Contity Framework Core في REPO.
حدد نموذج المستأجر الخاص بك ، أو الوراثة من المستأجر ، أو مجرد استخدام Tenancytenant كما هو. في هذا المثال ، سنرث من Tenancytenant وإضافة اسم عرض.
public class Tenant : TenancyTenant
{
// Custom property for display name of tenant.
public string Name { get ; set ; }
} مثال على إضافة دعم متعدد المستأجرين إلى 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." ) ;
}
} مثال على تكوين التطبيق مع دعم متعدد المستأجرين لـ ASP.NET Core MVC و IdentityServer4.
public void Configure ( IApplicationBuilder app )
{
// other code removed for brevity
app . UseStaticFiles ( ) ;
app . UseMultiTenancy < Tenant > ( ) ;
app . UseIdentityServer ( ) ;
app . UseAuthentication ( ) ;
app . UseMvcWithDefaultRoute ( ) ;
} مثال على DBContext مع دعم متعدد المستأجرين لهوية ASP.NET الأساسية و 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: المعلومات: طلب بدء تشغيل http/1.1 post http: // localhost : 5020/account/login؟ returnurl = ٪ 2fgrants application/x-www-form-urlencded 267
microsoft.entityframeworkcore.database.command: المعلومات: DebCommand (3ms) [المعلمات = [__ normalizedcanonicalname_0 = ' localhost ' (size = 256)] ، commandtype = 'text' ، commandtimeout = '30 '] [u]. [concurrencystamp] ، [u]. [name] ، [u].
multiteTenancyserver.http.httptenancyprovider: Debug: Tenant test_tenant_1 وجدت بواسطة DomainParser لقيمة LocalHost في طلب http: // localhost : 5020/account/login؟ returnurl = ٪ 2frants.
Microsoft.EntityFrameworkCore.database.Command: المعلومات: DebCommand المنفذة (13ms) [المعلمات = [___ tenantid_0 = ' test_tenant_1 ' (size = 4000) ، @ybormizedusername_0 = 'alice' (size = 256) ، commandtype = 'extride ، ... ، [ u].
microsoft.entityframeworkcore.database.command: المعلومات: DebCommand المنفذة (1ms) [المعلمات = [___ tenantid_0 = ' test_tenant_1 ' (الحجم = 4000) ، __ الموضوع = '3AB99036-8AC1-4270-8A1E-39098896. commandType = 'text' ، commonetimeout = '30 '] SELECT [p]. [key] ، [p]. [clientID] ، [p] . ([p]. [tenantid] = ___ tenantid_0)) و ([p].