我正在使用Simple Injector作为DI容器创建一个WPF MVVM应用程序。现在,当我试图从Simple Injector解析视图时,我遇到了一些问题,因为我需要在构造时将参数传递到构造函数中(而不是在将视图注册到容器时,因此这不适用:Simple Inject将值传递到构造函数)。
我想要的是这样的东西:
var item = container.GetInstance<MyType>(myParameter);
我在Simple Injector中读到过几个地方,这是不可能的,因为不应该这样做(包括这里:https://simpleinjector.codeplex.com/discussions/397080)。
这是真的吗?如果是,我怎么能这样做
背景信息
我有一个多个视图模型和模型的集合,这些模型由一个特定的键查找,我想传递到视图中的参数是视图模型要使用的键。我发现这是必要的,因为视图模型和模型在应用程序的多个位置使用,并且如果它们具有相同的键,则需要保持同步/是相同的实例。我不认为我能够使用生存期作用域来解决这个问题,而且当我注册到容器时,我不可能知道密钥。我也知道ViewModelLocator
方法可能是ServiceLocator(反?)模式,但是,目前它是我所拥有的最好的。
我的构造函数目前看起来是这样的,我希望IViewModelLocator得到解析,同时我传递密钥:
public FillPropertiesView(IViewModelLocator vml, object key)
{
// .. Removed code
// Assign the view model
var viewModel = vml.GetViewModel<FillPropertiesViewModel>(key);
DataContext = viewModel;
}
IViewModelLocator
如下所示(模型也有类似的接口)。
public interface IViewModelLocator
{
// Gets the view model associated with a key, or a default
// view model if no key is supplied
T GetViewModel<T>(object key = null) where T : class, IViewModel;
}
现在我有以下问题:
- 使用视图模型键解析视图的最佳方法是什么
- 我必须进行一些重构才能实现这一点吗
- 自从我创建了自己的基于字典的
ViewModelLocator
以来,我是否错过了DI容器的一些功能
扩展信息
我已经在上面展示了ViewModelLocator,我使用它的原因是为了保持可混合性(基本上,它只是在Blend中打开时为我提供设计时数据)。如果我不必同时打开不同的窗口(见下一段),则视图的每个实例的运行时视图模型都可以是相同的(不依赖于键)。然而,当ViewModel获取模型时,上述问题是相同的,并且ModelLocator需要一个键来获取现有模型(如果存在)。
这是针对PowerPoint的VSTO应用程序的一部分(它会影响设计的某些部分)。当选择屏幕上的对象时,将打开一个任务面板(这基本上是上面解释的FillPropertiesView)。所选对象具有提供给视图的键,以便视图从ViewModelLocator中提取正确的视图模型。然后,视图模型将通过使用IModelLocator(类似于IViewModelLocator)和相同的键来获得对模型的引用。同时,控制器将使用相同的键从ModelLocator获取模型。控制器侦听模型中的更改事件,并更新屏幕上的对象。每当选择新对象时,都会复制相同的过程,同时可以打开多个窗口,这些窗口可以同时与相同或不同的对象交互(也就是说,多个任务窗格都具有唯一的视图模型)。
到目前为止,我已经用一个默认的无参数构造函数解析了视图,然后用一个方法调用注入了视图模型:
// Sets the view model on a view
public void SetViewModel(IViewModelLocator vml, object key)
{
// Assign the view model
_viewModel = vml.GetViewModel<FillPropertiesViewModel>(key);
DataContext = _viewModel;
}
我从未向容器注册过视图,但解决了如下具体类型:
string key = "testkey" // read from the selected object
var view = container.GetInstance<FillPropertiesView>();
var vml = container.GetInstance<IViewModelLocator>();
view.SetViewModel(vml, key);
当我试图重构它时,这个问题浮出水面,这样我就不必每次都调用SetViewModel()方法并手动解析视图模型等。当我还必须在视图模型中手动初始化以同样的方式初始化模型时,这变得非常混乱。
视图模型定位器
ViewModelLocator当前作为DI容器的包装器工作,即视图模型在Simple Injector中注册。
注册如下(在一个名为CompositionHost的类中):
container.RegisterSingle<IViewModelLocator, ViewModelLocator>();
container.RegisterSingle<IModelLocator, ModelLocator>();
实现如下所示:
// Base implementation used by ViewModelLocator and ModelLocator
public class ServiceLocator<TService> where TService : class
{
private readonly Dictionary<CombinedTypeKey, TService> _instances =
new Dictionary<CombinedTypeKey, TService>();
// Gets a service instance based on the type and a key.
// The key makes it possible to have multiple versions of the same service.
public T GetInstance<T>(object key = null) where T : class, TService
{
var combinedKey = new CombinedTypeKey(typeof(T), key);
// Check if we already have an instance
if (_instances.ContainsKey(combinedKey))
{
return _instances[combinedKey] as T;
}
// Create a new instance
// CompositionHost is a static reference to the DI container (and
// should perhaps be injected, however, that is not the main issue here)
var instance = CompositionHost.GetInstance<T>();
_instances.Add(combinedKey, instance);
return instance;
}
// A combined key to ease dictionary operations
private struct CombinedTypeKey
{
private readonly object _key;
private readonly Type _type;
public CombinedTypeKey(Type type, object key)
{
_type = type;
_key = key;
}
// Equals and GetHashCode() are overridden
}
}
public class ViewModelLocator : IViewModelLocator
{
private readonly ServiceLocator<IViewModel> _viewModelLocator;
public ViewModelLocator(ServiceLocator<IViewModel> locator)
{
_viewModelLocator = locator;
// Dummy code that registers design time data is removed
}
// IViewModel is just an empty interface implemented by the view models
public T GetViewModel<T>(object key = null) where T : class, IViewModel
{
return _viewModelLocator.GetInstance<T>(key);
}
}
在类中注入服务定位器(几乎)永远不会成功,因为这不允许在编译时检查依赖关系和运行时依赖关系分析。出于这个原因,我还可以建议注册所有的根类型(如视图),因为否则Simple Injector将被蒙在鼓里,无法就您可能存在的任何错误配置向您提供建议。
由于您有始终缓存在一起的View+ViewModel对,但可能依赖于多个View+ViewModel对重用的Model实例,因此我建议采用以下设计。
定义视图和视图模型的抽象:
public interface IView<TModel>
{
IViewModel<TModel> ViewModel { get; }
}
public interface IViewModel<TModel>
{
TModel Model { get; set; }
}
定义按键检索/缓存视图的抽象。
public interface IViewProvider<TView, TModel> where TView : IView<TModel>
{
TView GetViewByKey(object key);
}
使用这些抽象,您的视图可以如下所示:
public class FillPropertiesView : IView<FillPropertiesModel>
{
public FillPropertiesView(FillPropertiesViewModel viewModel)
{
this.ViewModel = viewModel;
}
public IViewModel<FillPropertiesModel> ViewModel { get; private set; }
}
您的控制器可以依赖于IViewProvider<TView, TModel>
抽象,因此当新密钥进入时,它们可以重新加载视图:
public class FillPropertiesController : Controller
{
IViewProvider<FillPropertiesView, FillPropertiesModel> viewProvider;
FillPropertiesView view;
public FillPropertiesController(
IViewProvider<FillPropertiesView, FillPropertiesModel> provider) {
this.viewProvider = provider;
}
public void Reinitialize(object key) {
this.view = this.viewProvider.GetViewByKey(key);
}
}
IViewProvider<TView, TModel>
的实现可能如下所示:
public class ViewProvider<TView, TModel> : IViewProvider<TView, TModel>
where TView : class, IView<TModel> {
Dictionary<object, TView> views = new Dictionary<object, TView>();
Container container;
IModelProvider<TModel> modelProvider;
public ViewProvider(Container container,
IModelProvider<TModel> modelProvider) {
this.container = container;
this.modelProvider = modelProvider;
}
public TView GetViewByKey(object key) {
TView view;
if (!this.views.TryGetValue(key, out view)) {
this.views[key] = view = this.CreateView(key);
}
return view;
}
private TView CreateView(object key) {
TView view = this.container.GetInstance<TView>();
view.ViewModel.Model = this.modelProvider.GetModelByKey(key);
return view;
}
}
此实现依赖于(以前未定义的)IModelProvider<TModel>
抽象。这基本上是您的旧ModelLocator
,但通过使用泛型类型,您可以使实现更加容易,因为我们可以为每个TModel提供一个这种类型的实例(ViewProvider也是如此),这使您不必使用{type+key}组合存储元素。
您可以按以下方式注册:
Assembly asm = Assembly.GetExecutingAssembly();
container.RegisterManyForOpenGeneric(typeof(IView<>), asm);
container.RegisterManyForOpenGeneric(typeof(IViewModel<>), asm);
container.RegisterOpenGeneric(typeof(IViewProvider<,>),
typeof(ViewProvider<,>), Lifestyle.Singleton);
container.RegisterOpenGeneric(typeof(IModelProvider<>),
typeof(ModelProvider<>), Lifestyle.Singleton);
var controllers =
from type in asm.GetTypes()
where type.IsSubClassOf(typeof(Controller))
where !type.IsAbstract
select type;
controllers.ToList().ForEach(t => container.Register(t));
container.Verify();
使用RegisterManyForOpenGeneric
,您可以让Simple Injector搜索提供的程序集,以查找给定的开放泛型抽象的实现,Simple Inject将为您批量注册它们。使用RegisterOpenGeneric
,您可以指定一个开放的泛型抽象,并在请求该抽象的封闭泛型版本时告诉Simple Injector要使用哪个实现。最后一行搜索应用程序以查找所有控制器类型,并将它们注册到系统中。