ASP.NET MVC 5 + EF6 + Ninject - 多租户数据库
ASP.NET MVC 5 + EF6 + Ninject - Multitenancy Database
我有一个业务 ASP.NET MVC5 应用程序,其中每个客户都有自己的数据库。我想将 EF6 和 Ninject 用于 DI。对于登录,我使用 ASP.NET 身份。
对于每个用户,存在一个指定数据库名称的 UserClaim:
- 用户 ID = 1 |索赔类型 = "db_name" | ClaimValue = "Customer0001"
- 用户 ID = 2 |索赔类型 = "db_name" | ClaimValue = "Customer0002"
等等......这意味着它是一个带有 "shared" 数据库的网络应用程序,用于用户身份验证,另一方面,每个客户都有自己的数据库 - 所有数据库都位于同一个数据库中服务器(MS SQL 服务器)。
用户需要登录,登录后他应该从他的个人数据库中接收数据(在UserClaim-Table中指定)。
对于Ninject我想我必须做这样的事情
private void AddBindings() {
kernel.Bind<EFDBContext>().ToMethod(c => new EFDBContext("db_name"));
}
但是如何将 UserClaim 放入绑定中? (我不想使用会话,因为会话可能会丢失)。
绑定后需要执行哪些步骤?
例如,在 AccountRepository 中,EFDBContext 需要 "db_name" > 但我如何才能得到它?
public class AccountRepository : IAccountRepository {
private EFDBContext context = new EFDBContext("db_name");
}
最后我可以更改此 class 中的连接字符串??
public class EFDBContext : DbContext {
public EFDBContext(string db_name) : base("EFDBContext") {
}
}
@Hooman Bahreini 回答后更新
NinjectDependencieResolver.cs
private void AddBindings() {
kernel.Bind<ICustomerRepository>().To<CustomerRepository>().WithConstructorArgument("http_current_context", HttpContext.Current);
}
CustomerRepository.cs
public class CustomerRepository : ICustomerRepository {
private CustomerDBContext context;
public CustomerRepository(HttpContext httpContext) {
string db_name = "";
var claimValue = ((ClaimsPrincipal)HttpContext.Current.User).Claims.FirstOrDefault(c => c.Type == "db_name");
if(claimValue != null) {
db_name = claimValue.Value.ToString();
}
context = new CustomerDBContext(db_name);
}
public IEnumerable<Test> Tests {
get { return context.Test; }
}
}
DB 上下文文件
public class CustomerDBContext : DbContext {
public CustomerDBContext(string db_name) : base("CustomerDBContext") {
string temp_connection = Database.Connection.ConnectionString.Replace(";Initial Catalog=;", ";Initial Catalog=" + db_name + ";");
Database.Connection.ConnectionString = temp_connection;
}
public DbSet<Test> Test { get; set; }
}
您可以访问来自 HttpContext
的用户声明:
var claimValue = ((ClaimsPrincipal)HttpContext.Current.User)
.Claims
.FirstOrDefault(c => c.Type == "db_name");
对于您的 ninject 代码,您可以为 HttpContext
创建一个扩展方法:
public static HttpcontextExtensions
{
public static string GetDbName(this HttpContext context)
{
return ((ClaimsPrincipal)context.Current.User)
.Claims
.FirstOrDefault(c => c.Type == "db_name");
}
}
并使用以下 ninject 绑定:
kernel.Bind<ICustomerRepository>()
.To<CustomerRepository>()
.WithConstructorArgument("db_name", HttpContext.GetDbName());
有关访问 ninject 中的 HttpContext
的详细信息,请参阅 this document。
在您的示例中,CustomerRepository
依赖于 HttpContext
,这不是一个好的设计。 CustomerRepository
需要一个数据库名称,这就是应该在构造函数中传递的内容。与此相关的是Nikola’s 4th law of IoC
Every constructor of a class being resolved should not have any
implementation other than accepting a set of its own dependencies.
举个例子,你的测试项目中没有任何HttpContext
,这使得单元测试CustomerRepository
变得复杂。
P.S。我不知道你的设计,但也许从 HttpContext
获取 db-name 不是一个理想的解决方案......用户可能会注销或清除他们的浏览器历史记录,你将丢失你的 db-name.
我有一个业务 ASP.NET MVC5 应用程序,其中每个客户都有自己的数据库。我想将 EF6 和 Ninject 用于 DI。对于登录,我使用 ASP.NET 身份。
对于每个用户,存在一个指定数据库名称的 UserClaim:
- 用户 ID = 1 |索赔类型 = "db_name" | ClaimValue = "Customer0001"
- 用户 ID = 2 |索赔类型 = "db_name" | ClaimValue = "Customer0002"
等等......这意味着它是一个带有 "shared" 数据库的网络应用程序,用于用户身份验证,另一方面,每个客户都有自己的数据库 - 所有数据库都位于同一个数据库中服务器(MS SQL 服务器)。
用户需要登录,登录后他应该从他的个人数据库中接收数据(在UserClaim-Table中指定)。
对于Ninject我想我必须做这样的事情
private void AddBindings() {
kernel.Bind<EFDBContext>().ToMethod(c => new EFDBContext("db_name"));
}
但是如何将 UserClaim 放入绑定中? (我不想使用会话,因为会话可能会丢失)。
绑定后需要执行哪些步骤?
例如,在 AccountRepository 中,EFDBContext 需要 "db_name" > 但我如何才能得到它?
public class AccountRepository : IAccountRepository {
private EFDBContext context = new EFDBContext("db_name");
}
最后我可以更改此 class 中的连接字符串??
public class EFDBContext : DbContext {
public EFDBContext(string db_name) : base("EFDBContext") {
}
}
@Hooman Bahreini 回答后更新
NinjectDependencieResolver.cs
private void AddBindings() {
kernel.Bind<ICustomerRepository>().To<CustomerRepository>().WithConstructorArgument("http_current_context", HttpContext.Current);
}
CustomerRepository.cs
public class CustomerRepository : ICustomerRepository {
private CustomerDBContext context;
public CustomerRepository(HttpContext httpContext) {
string db_name = "";
var claimValue = ((ClaimsPrincipal)HttpContext.Current.User).Claims.FirstOrDefault(c => c.Type == "db_name");
if(claimValue != null) {
db_name = claimValue.Value.ToString();
}
context = new CustomerDBContext(db_name);
}
public IEnumerable<Test> Tests {
get { return context.Test; }
}
}
DB 上下文文件
public class CustomerDBContext : DbContext {
public CustomerDBContext(string db_name) : base("CustomerDBContext") {
string temp_connection = Database.Connection.ConnectionString.Replace(";Initial Catalog=;", ";Initial Catalog=" + db_name + ";");
Database.Connection.ConnectionString = temp_connection;
}
public DbSet<Test> Test { get; set; }
}
您可以访问来自 HttpContext
的用户声明:
var claimValue = ((ClaimsPrincipal)HttpContext.Current.User)
.Claims
.FirstOrDefault(c => c.Type == "db_name");
对于您的 ninject 代码,您可以为 HttpContext
创建一个扩展方法:
public static HttpcontextExtensions
{
public static string GetDbName(this HttpContext context)
{
return ((ClaimsPrincipal)context.Current.User)
.Claims
.FirstOrDefault(c => c.Type == "db_name");
}
}
并使用以下 ninject 绑定:
kernel.Bind<ICustomerRepository>()
.To<CustomerRepository>()
.WithConstructorArgument("db_name", HttpContext.GetDbName());
有关访问 ninject 中的 HttpContext
的详细信息,请参阅 this document。
在您的示例中,CustomerRepository
依赖于 HttpContext
,这不是一个好的设计。 CustomerRepository
需要一个数据库名称,这就是应该在构造函数中传递的内容。与此相关的是Nikola’s 4th law of IoC
Every constructor of a class being resolved should not have any implementation other than accepting a set of its own dependencies.
举个例子,你的测试项目中没有任何HttpContext
,这使得单元测试CustomerRepository
变得复杂。
P.S。我不知道你的设计,但也许从 HttpContext
获取 db-name 不是一个理想的解决方案......用户可能会注销或清除他们的浏览器历史记录,你将丢失你的 db-name.