我正在尝试使用ASP.NET MVC和Microsoft Unity DI框架实现一个web应用程序。
在用户登录时,我将必要的类型映射注册到应用程序的Unity容器using a session-based lifetime manager that I found in another question here。
我的容器是这样初始化的:
// Global.asax.cs
public static UnityContainer CurrentUnityContainer { get; set; }
protected void Application_Start()
{
// ...other code...
CurrentUnityContainer = UnityConfig.Initialize();
// misc services - nothing data access related, apart from the fact that they all depend on IRepository<ClientContext>
UnityConfig.RegisterComponents(CurrentUnityContainer);
}
// UnityConfig.cs
public static UnityContainer Initialize()
{
UnityContainer container = new UnityContainer();
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
return container;
}
这是在登录时调用的代码:
// UserController.cs
UnityConfig.RegisterUserDataAccess(MvcApplication.CurrentUnityContainer, UserData.Get(model.AzureUID).CurrentDatabase);
// UnityConfig.cs
public static void RegisterUserDataAccess(IUnityContainer container, string databaseName)
{
container.AddExtension(new DataAccessDependencies(databaseName));
}
// DataAccessDependencies.cs
public class DataAccessDependencies : UnityContainerExtension
{
private readonly string _databaseName;
public DataAccessDependencies(string databaseName)
{
_databaseName = databaseName;
}
protected override void Initialize()
{
IConfigurationBuilder configurationBuilder = Container.Resolve<IConfigurationBuilder>();
Container.RegisterType<ClientContext>(new SessionLifetimeManager(), new InjectionConstructor(configurationBuilder.GetConnectionString(_databaseName)));
Container.RegisterType<IRepository<ClientContext>, RepositoryService<ClientContext>>(new SessionLifetimeManager());
}
}
// SessionLifetimeManager.cs
public class SessionLifetimeManager : LifetimeManager
{
private readonly string _key = Guid.NewGuid().ToString();
public override void RemoveValue(ILifetimeContainer container = null)
{
HttpContext.Current.Session.Remove(_key);
}
public override void SetValue(object newValue, ILifetimeContainer container = null)
{
HttpContext.Current.Session[_key] = newValue;
}
public override object GetValue(ILifetimeContainer container = null)
{
return HttpContext.Current.Session[_key];
}
protected override LifetimeManager OnCreateLifetimeManager()
{
return new SessionLifetimeManager();
}
}
只要一次只有一个用户登录,就可以很好地工作。数据被正确获取,仪表板按预期工作,一切都非常灵敏。
然后,一旦第二个用户登录,灾难就会降临。
提示调用RegisterUserDataAccess
的最后一个用户似乎总是具有“优先级”;他们的数据显示在仪表板上,没有其他数据。无论这是由登录发起的,还是通过我的web应用程序中的数据库访问选择启动的,该web应用程序调用相同的方法将用户的连接重新路由到他们有权访问的另一个数据库,最后绘制的数据库总是将他们的数据强加给web应用程序的所有其他用户。如果我理解正确的话,这是一个SessionLifetimeManager应该解决的问题--不幸的是,我似乎真的不能让它工作。
我真诚地怀疑,像这样一个简单而常见的用例-多个用户登录到一个MVC应用程序,每个用户都应该访问他们自己的独立数据-超出了Unity的能力范围,所以很明显,我在这里肯定做了一些非常错误的事情。我花了一天的大部分时间在互联网的深处寻找,我甚至不确定是否真的存在,不幸的是,我现在必须意识到我在这里完全迷失了方向。
以前有人处理过这个问题吗?以前有没有人处理过这个用例,如果有,有没有人能告诉我如何改变我的方法,让它不那么令人头疼?在这一点上,我完全绝望了,我正在考虑重写我的整个数据访问方法,只是为了让它工作-对于干净和可维护的代码来说,这不是最健康的心态。
非常感谢。
发布于 2018-10-16 03:41:03
谢谢你,穆罕默德。你的回答让我走上了正确的道路-我最终使用RepositoryFactory解决了这个问题,它在注册期间在InjectionFactory中实例化,并返回一个存储库,该存储库总是围绕指向当前登录用户当前选择的数据库的ClientContext。
// DataAccessDependencies.cs
protected override void Initialize()
{
IConfigurationBuilder configurationBuilder = Container.Resolve<IConfigurationBuilder>();
Container.RegisterType<IRepository<ClientContext>>(new InjectionFactory(c => {
ClientRepositoryFactory repositoryFactory = new ClientRepositoryFactory(configurationBuilder);
return repositoryFactory.GetRepository();
}));
}
// ClientRepositoryFactory.cs
public class ClientRepositoryFactory : IRepositoryFactory<RepositoryService<ClientContext>>
{
private readonly IConfigurationBuilder _configurationBuilder;
public ClientRepositoryFactory(IConfigurationBuilder configurationBuilder)
{
_configurationBuilder = configurationBuilder;
}
public RepositoryService<ClientContext> GetRepository()
{
var connectionString = _configurationBuilder.GetConnectionString(UserData.Current.CurrentPermission);
ClientContext ctx = new ClientContext(connectionString);
RepositoryService<ClientContext> repository = new RepositoryService<ClientContext>(ctx);
return repository;
}
}
// UserData.cs (multiton-singleton-hybrid)
public static UserData Current
{
get
{
var currentAADUID = (string)(HttpContext.Current.Session["currentAADUID"]);
return Get(currentAADUID);
}
}
public static UserData Get(string AADUID)
{
UserData instance;
lock(_instances)
{
if(!_instances.TryGetValue(AADUID, out instance))
{
throw new UserDataNotInitializedException();
}
}
return instance;
}
public static UserData Current
{
get
{
var currentAADUID = (string)(HttpContext.Current.Session["currentAADUID"]);
return Get(currentAADUID);
}
}
public static UserData Get(string AADUID)
{
UserData instance;
lock(_instances)
{
if(!_instances.TryGetValue(AADUID, out instance))
{
throw new UserDataNotInitializedException();
}
}
return instance;
}
发布于 2018-10-16 01:43:52
问题似乎源于您的注册调用,当使用unity多次注册同一类型时,最后一个注册调用获胜,在这种情况下,这将是任何用户最后登录的数据访问对象。Unity将把它作为默认注册,并将创建与该用户的数据库有连接的实例。
SessionLifetimeManager的存在是为了确保您在一个会话中只获得所解析对象的一个实例。
解决此问题的一种方法是使用命名注册语法将数据访问类型注册到映射到登录用户的键(可以是数据库名称)下,然后在解析端检索此用户键,并使用它解析用户的相应数据访问实现
https://stackoverflow.com/questions/52821647
复制相似问题