1、AbpMultiTenancyModule模块,DefaultTenantStoreOptions存储配置租户信息TenantConfiguration数组,每个租户包括Guid,Name,ConnectionStrings
[DependsOn(
typeof(AbpDataModule),
typeof(AbpSecurityModule)
)]
public class AbpMultiTenancyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
Configure<DefaultTenantStoreOptions>(configuration);
}
}
}
比如,如何从配置或ITenantStore得到租户信息
services.Configure<DefaultTenantStoreOptions>(options =>
{
options.Tenants = new[]
{
new TenantConfiguration(_tenant1Id, "tenant1")
{
ConnectionStrings =
{
{ ConnectionStrings.DefaultConnectionStringName, "tenant1-default-value"},
{"db1", "tenant1-db1-value"}
}
},
new TenantConfiguration(_tenant2Id, "tenant2")
};
});
services.Configure<DbConnectionOptions>(options =>
{
options.ConnectionStrings.Default = "default-value";
options.ConnectionStrings["db1"] = "db1-default-value";
});
另外关注MultiTenancyOptions
public class MultiTenancyOptions
{
/// <summary>
/// A central point to enable/disable multi-tenancy.
/// Default: false.
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// Database style for tenants.
/// Default: <see cref="MultiTenancyDatabaseStyle.Hybrid"/>.
/// </summary>
public MultiTenancyDatabaseStyle DatabaseStyle { get; set; } = MultiTenancyDatabaseStyle.Hybrid;
}
2、ICurrentTenant 当前租户,依赖ICurrentTenantAccessor来确定,关注Change方法IDisposable Change(Guid? id, string name = null);
public IDisposable Change(Guid? id, string name = null)
{
return SetCurrent(id, name);
}
private IDisposable SetCurrent(Guid? tenantId, string name = null)
{
var parentScope = _currentTenantAccessor.Current;
_currentTenantAccessor.Current = new BasicTenantInfo(tenantId, name);
return new DisposeAction(() =>
{
_currentTenantAccessor.Current = parentScope;
});
}
3、ICurrentTenantAccessor,依赖BasicTenantInfo信息来确定(null值使用host,不是null值使用租户信息),TenantId是指示是否赋值,AsyncLocal<BasicTenantInfo>
4、ITenantResolver,这里有一个传递ITenantResolveContext ,传递入IServiceProvider,通过ITenantResolveContributor的实现来确定当前租户TenantResolveResult,ITenantResolveContributor,从GetTenantIdOrNameFromHttpContextOrNull实现方法在Volo.Abp.AspNetCore.MultiTenancy模块,获取租户的Id和名称有五种方法,Cookie,Domain,Header,QueryString,Route,实现在模块Volo.Abp.AspNetCore.MultiTenancy里面
public interface ITenantResolveContributor
{
string Name { get; }
void Resolve(ITenantResolveContext context);
}
public abstract class TenantResolveContributorBase : ITenantResolveContributor
{
public abstract string Name { get; }
//TODO: We can make this async
public abstract void Resolve(ITenantResolveContext context);
}
public abstract class HttpTenantResolveContributorBase : TenantResolveContributorBase
{
。。。。
protected abstract string GetTenantIdOrNameFromHttpContextOrNull([NotNull] ITenantResolveContext context, [NotNull] HttpContext httpContext);
。。。。
}
[DependsOn(
typeof(AbpMultiTenancyModule),
typeof(AbpAspNetCoreModule)
)]
public class AbpAspNetCoreMultiTenancyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<TenantResolveOptions>(options =>
{
options.TenantResolvers.Add(new QueryStringTenantResolveContributor());
options.TenantResolvers.Add(new RouteTenantResolveContributor());
options.TenantResolvers.Add(new HeaderTenantResolveContributor());
options.TenantResolvers.Add(new CookieTenantResolveContributor());
});
}
}
public const string DefaultTenantKey = "__tenant";
其实现TenantResolver,通过遍历TenantResolveOptions下面的List<ITenantResolveContributor> TenantResolvers,获取得TenantIdOrName
6、ITenantStore,查找租户
[Dependency(TryRegister = true)]
public class DefaultTenantStore : ITenantStore, ITransientDependency
{
private readonly DefaultTenantStoreOptions _options;
public DefaultTenantStore(IOptionsSnapshot<DefaultTenantStoreOptions> options)
{
_options = options.Value;
}
public Task<TenantConfiguration> FindAsync(string name)
{
return Task.FromResult(_options.Tenants?.FirstOrDefault(t => t.Name == name));
}
public Task<TenantConfiguration> FindAsync(Guid id)
{
return Task.FromResult(_options.Tenants?.FirstOrDefault(t => t.Id == id));
}
}
8、IConnectionStringResolver,解决连接字符串,MultiTenantConnectionStringResolver是替换实现服务, _connectionResolver.ShouldBeOfType<MultiTenantConnectionStringResolver>();
有实现如何获取字符串,包括从 ITenantStore查到TenantConfiguration或DbConnectionOptions或DefaultTenantStoreOptions配置文件
No tenant in current context:默认DbConnectionOptions
Overrided connection strings for tenant1:对应租户DefaultTenantStoreOptions
Undefined connection strings for tenant2:没有对应租户字符串使用默认字符串
public override string Resolve(string connectionStringName = null)
{
//No current tenant, fallback to default logic
if (_currentTenant.Id == null)
{
return base.Resolve(connectionStringName);
}
using (var serviceScope = _serviceProvider.CreateScope())
{
var tenantStore = serviceScope
.ServiceProvider
.GetRequiredService<ITenantStore>();
//租户存储获取,tenant-management进行管理
var tenant = AsyncHelper.RunSync(() => tenantStore.FindAsync(_currentTenant.Id.Value)); //TODO: Can we avoid from RunSync?
// 当前租户没有连接字符串,则返回默认连接字符串
if (tenant?.ConnectionStrings == null)
{
return base.Resolve(connectionStringName);
}
//Requesting default connection string
if (connectionStringName == null)
{
return tenant.ConnectionStrings.Default ??
Options.ConnectionStrings.Default;
}
//Requesting specific connection string
var connString = tenant.ConnectionStrings.GetOrDefault(connectionStringName);
if (connString != null)
{
return connString;
}
/* Requested a specific connection string, but it's not specified for the tenant.
* - If it's specified in options, use it.
* - If not, use tenant's default conn string.
*/
var connStringInOptions = Options.ConnectionStrings.GetOrDefault(connectionStringName);
if (connStringInOptions != null)
{
return connStringInOptions;
}
return tenant.ConnectionStrings.Default ??
Options.ConnectionStrings.Default;
}
}
9、多租户的中间件
public async Task Invoke(HttpContext httpContext)
{
//实现是遍历Cookie,Domain,Header,QueryString,Route不同ITenantResolveContributor
//,获取Tenant的ID和Name
var resolveResult = _tenantResolver.ResolveTenantIdOrName();
_tenantResolveResultAccessor.Result = resolveResult;
//获取配置文件,包括字符串
TenantConfiguration tenant = null;
if (resolveResult.TenantIdOrName != null)
{
tenant = await FindTenantAsync(resolveResult.TenantIdOrName);
if (tenant == null)
{
//TODO: A better exception?
throw new AbpException(
"There is no tenant with given tenant id or name: " + resolveResult.TenantIdOrName
);
}
}
//设置为当前租户
using (_currentTenant.Change(tenant?.Id, tenant?.Name))
{
await _next(httpContext);
}
}