简介
我正在开发一个多租户应用程序,我正在使用EF6和Sql Server在ASP.NET MVC中编程。
我的数据库结构:
包含租户属性的1个基础数据库(例如名称/子域/目录)。
(目录是其指定数据库的名称)
每个客户1个数据库
要了解租户,我使用子域查找:
实体框架
在基础和默认应用数据库中,我添加了ADO.NET Entity data model
(edmx)文件。
控制器
为了获得正确的租户,我创建了一个新的自定义控制器来覆盖OnActionExecuting
方法。如果租户存在,我将租户的Id添加到路由变量。
// Override OnActionExecuting method which is called every time before the action is called
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var fullAddress = filterContext.HttpContext.Request.Headers["Host"].Split('.');
if (fullAddress.Length < 3)
{
// some code....
}
else
{
var tenantSubdomain = fullAddress[0];
Account currentTenant = db.Accounts.FirstOrDefault(t => t.Subdomain.Equals(tenantSubdomain, StringComparison.CurrentCultureIgnoreCase));
if (currentTenant != null)
filterContext.RouteData.Values["tenant"] = currentTenant.Id;
else
filterContext.Result = new HttpStatusCodeResult(404);
}
base.OnActionExecuting(filterContext);
}
直到这里一切正常。现在我因为创建和存储与租户分配的数据库的连接而陷入困境。
要连接数据库,我需要使用以下代码:
public SkycountAppEntities dbApp = new SkycountAppEntities(GetConnString(tenant.Catalogue));
//GetConnString renders the connectionstring which includes the tenants catalogue.
但是我在哪里放置这条线,所以我不必在每个动作中都调用它?或者我可以将它与身份验证cookie一起缓存在哪里吗?
有人可以指导我正确的方向吗?
更新
以这种方式工作但现在我必须在每个控制器的每个动作中创建连接。
// POST: Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login([Bind(Include = "Username,Password")] User user, string returnUrl)
{
using (SkycountAppEntities dbApp = new SkycountAppEntities(DbEntityFramework.RenderConnectionString(_SkycountAccount.Catalog)))
{
User _user = dbApp.Users.FirstOrDefault(u => u.Username.Equals(user.Username));
if(_user != null && _user.Active && Crypto.VerifyHashedPassword(_user.Password, user.Password))
{
FormsAuthentication.SetAuthCookie(user.Username, false);
if (String.IsNullOrEmpty(returnUrl) || !Url.IsLocalUrl(returnUrl))
return RedirectToAction("Index", "Home");
else
return Redirect(returnUrl);
} else
{
TempData["failed"] = true;
return RedirectToAction("Login", new { returnUrl = returnUrl });
}
}
}
答案 0 :(得分:0)
我总是将IoC用于多租户应用程序。在IoC中,我使用PerWebRequest LifeStyle注册Adapter,以便从HttpContext获取当前租户的配置。我的addapter进入构造函数Func从HttpContext获取Url。
public class CurrentTenantAdapter : ICurrentTenantAdapter
{
private readonly IGlobalCache cache;
private readonly IMongoRepository repository;
public CurrentTenantAdapter(Func<string> getTenantIdFunc, IMongoRepository repository, IGlobalCache cache)
{
this.GetTenantIdFunc = getTenantIdFunc;
this.repository = repository;
this.cache = cache;
}
public async Task<ITenant> GetTenantAsync()
{
string tenantId = GetTenantIdFunc();
if (string.IsNullOrEmpty(tenantId))
{
return null;
}
Tenant tenant = await this.cache.GetAsync<Tenant>(tenantId);
if (tenant == null)
{
tenant = await this.repository.QueryAsync(new GetTenantById(tenantId));
if (tenant != null)
{
await this.cache.StoreAsync(tenantId, tenant, null);
}
}
return tenant;
}
public string GetTenantId()
{
return GetTenantIdFunc();
}
protected Func<string> GetTenantIdFunc { get; set; }
}
}
您可以在存储库中使用此适配器。例如,存储库可以使用来自租户配置的连接字符串创建新的EF上下文。我使用MongoDb与此适配器连接到正确的数据库。
private async Task<IMongoCollection<T>> ConnectDbAndGetCollectionAsync<T>(string databaseName)
{
var isMainModel = IsMainModel<T>();
if (string.IsNullOrEmpty(databaseName))
{
databaseName = mainDatabaseName.Value;
if (isMainModel == false && this.tenantConfiguration != null)
{
ITenant tenant = await this.tenantConfiguration.GetTenantAsync();
DatabasesConfiguration databaseConfiguration = tenant.DatabasesConfiguration;
if (databaseConfiguration != null)
{
if (databaseConfiguration.MongoDatabaseConfiguration != null)
{
databaseName = databaseConfiguration.MongoDatabaseConfiguration.DatabaseName;
}
}
}
}
var attribute = AttributesHelper.GetAttributeValue<MongoCollectionName>(typeof(T));
string collectionName = attribute.CollectionName;
IMongoDatabase db = await GetDatabaseAsync(databaseName);
return await Task.FromResult(db.GetCollection<T>(collectionName));
}