在Simple Injector中使用自定义参数解析类 [英] Resolving a class with a custom parameter in Simple Injector

查看:84
本文介绍了在Simple Injector中使用自定义参数解析类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用简单注入器作为DI容器创建WPF MVVM应用程序。现在,当我尝试从Simple Injector解析视图时遇到一些问题,因为我需要在构造时将参数传递到构造函数中(而不是在将视图注册到容器时,因此这不是)适用:简单注入器将值传递到构造函数中)。

I'm creating a WPF MVVM application using Simple Injector as DI container. Now I'm having some issues when I'm trying to resolve a view from Simple Injector, because I'm in need of passing a parameter into my constructor at construction time (not when registering the view to the container, thus this is not applicable: Simple Injector pass values into constructor).

什么我追求的是这样的事情:

What I'm after is something like this:

var item = container.GetInstance<MyType>(myParameter);

我读过几个地方,这在Simple Injector中是不可能的,因为不应该这样做(包括此处: https://simpleinjector.codeplex.com/discussions/397080 )。

I've read several places that this is not possible in Simple Injector because it should not be done (including here: https://simpleinjector.codeplex.com/discussions/397080).

是真的,如果是这样,我该怎么做呢?

我有多个视图模型和通过特定键查找的模型的集合,并且我要传递给视图的参数是视图模型使用的关键。我发现这是必要的,因为视图模型和模型在应用程序的多个位置使用,并且需要保持同步/如果它们具有相同的键,则必须是相同的实例。我认为我无法使用生存期范围解决此问题,并且在注册到容器时我无法知道密钥。我还了解到, ViewModelLocator 方法可能是ServiceLocator(anti-?)模式,但是,这是目前为止我所能做到的最好的模式。

I have a collection of multiple view models and models which are looked up by a specific key, and the parameter I want to pass into the view is the key for the view model to use. I've found this necessary because the view models and the models are used in multiple locations of the application, and need to stay in sync / be the same instances if they have the same key. I don't think I'm able to use lifetime scope to solve this, and there is no way I know the keys when I'm registering to the container. I've also understood that the ViewModelLocator approach might be the ServiceLocator (anti-?)pattern, however, currently it is the best I've got.

我的构造函数当前看起来像这样,我希望在输入密钥的同时解析IViewModelLocator:

My constructor currently looks like this, and I want the IViewModelLocator to be resolved, while I pass in the key:

public FillPropertiesView(IViewModelLocator vml, object key)
{
    // .. Removed code

    // Assign the view model
    var viewModel = vml.GetViewModel<FillPropertiesViewModel>(key);
    DataContext = viewModel;
}

IViewModelLocator 看起来类似于以下内容(模型也有类似的接口)。

The IViewModelLocator looks like the following (and a similar interface exists for models).

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容器的某些功能吗?

  • What is the best way to be able to resolve the view with the view model key?
  • Do I have to do some refactoring to enable this?
  • Am I missing out on some of the power of the DI container since I have created my own dictionary based ViewModelLocator?

我有如上面的ViewModelLocator所示,我使用它的原因是为了保持可混合性(基本上,在Blend中打开时,它只是为我提供了设计时间数据)。如果我不必同时打开不同的窗口,则视图的每个实例的运行时视图模型可能都是相同的(不依赖于键)(请参阅下一段)。但是,当ViewModel提取模型时,上述问题是相同的,而ModelLocator 需要来提取现有模型(如果存在)的密钥。

I have shown the ViewModelLocator above, and the reason I'm using this is to keep blendability (basically it just provides me with design time data when opened in Blend). The runtime view model could have been the same for every instance of the view (not dependent on the key), if I did not have to have different windows opened at the same time (see next paragraph). The issue described above, however, is the same when the ViewModel is fetching a model, and the ModelLocator needs a key to fetch an existing model if it exists.

这是针对PowerPoint的VSTO应用程序的一部分(这会影响设计的某些部分)。选择屏幕上的对象后,将打开一个任务面板(基本上是上面所述的 FillPropertiesView )。选定的对象具有提供给视图的键,以便视图从ViewModelLocator提取正确的视图模型。然后,视图模型将通过使用IModelLocator(类似于IViewModelLocator)和相同的键来获得对模型的引用。同时,控制器将使用相同的键从ModelLocator中获取模型。控制器侦听来自模型的更改事件并更新屏幕上的对象。每当选择一个新对象时,都将复制相同的过程,并且同时可以打开多个窗口,这些窗口同时与相同或不同的对象(即,具有唯一视图模型的多个任务窗格)进行交互。

This is part of a VSTO application targeting PowerPoint (which affects some parts of the design). When an object on screen is selected, a task panel is opened (this is basically the FillPropertiesView explained above). The object that is selected has a key that is supplied to the view, in order for the view to extract the correct view model from the ViewModelLocator. The view model will then get a reference to a model by using a IModelLocator (similar to the IViewModelLocator) and the same key. At the same time, a controller will fetch the model from the ModelLocator by using the same key. The controller listens to change events from the model and updates the objects on screen. The same process is replicated whenever a new object is selected, and at the same time multiple windows could be open that interacts with the same or different objects simultaneously (that is, with multiple task panes all with unique view models).

到目前为止,我已经使用默认的无参数构造函数解析了视图,然后在视图模型中注入了方法调用:

Up until now I have resolved the view with a default, parameterless constructor, and then injected the view model with a method call afterwards:

// 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;
}

我从来没有在容器中注册视图,但是解决了具体类型像这样:

I never had to register the view with the container, but resolved the concrete type like this:

string key = "testkey" // read from the selected object
var view = container.GetInstance<FillPropertiesView>();
var vml = container.GetInstance<IViewModelLocator>();
view.SetViewModel(vml, key);

当我尝试重构它时,这个问题浮出水面,这样我就不必调用SetViewModel()每种方法都可以手动解决,并手动解决视图模型等问题。当我还必须在视图模型中执行手动启动以相同的方式启动模型时,这变得非常混乱。

This issue surfaced when I tried to refactor this so that I did not have to call the SetViewModel() method every and manually resolve the view models etc. It got very messy when I also had to do this manual initiation within the view model to initiate the model in the same way.

ViewModelLocator当前用作DI容器的包装器,即视图模型已在Simple Injector中注册

The ViewModelLocator is currently working as a wrapper around the DI container, i.e. the view models are registered in Simple Injector.

注册类似于以下内容(在名为CompositionHost的类中):

The registrations are like the following (in a class called 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处于黑暗状态,无法为您提供关于任何可能的错误配置的建议。

Injecting a service locator into your classes is (almost) never the way to go, because this disallows compile time checking of dependencies and runtime dependency analysis. For that reason I can also advice to register ALL your root types (such as your views) since otherwise Simple Injector is left in the dark and is not able to advice you about any possible misconfigurations that you might have.

由于您拥有始终一起缓存的View + ViewModel对,但可能取决于被多个View + ViewModel对重用的Model实例,因此我建议采用以下设计。

Since you have View + ViewModel pairs that are always cached together, but might depend on Model instance that are reused by multiple View + ViewModel pairs, I suggest the following design.

为视图和视图模型定义抽象:

Define an abstraction for views and view models:

public interface IView<TModel>
{
    IViewModel<TModel> ViewModel { get; }
}

public interface IViewModel<TModel>
{
    TModel Model { get; set; }
}

定义用于按键检索/缓存视图的抽象。

Define an abstraction for retrieving/caching view by key.

public interface IViewProvider<TView, TModel> where TView : IView<TModel>
{
    TView GetViewByKey(object key);
}

有了这些抽象,您的视图如下所示:

With these abstractions your view can look as follows:

public class FillPropertiesView : IView<FillPropertiesModel>
{
    public FillPropertiesView(FillPropertiesViewModel viewModel)
    {
        this.ViewModel = viewModel;
    }

    public IViewModel<FillPropertiesModel> ViewModel { get; private set; }
}

您的控制器可以依赖 IViewProvider< TView,TModel> 抽象,这样它们就可以在传入新密钥时重新加载视图:

And your controllers can depend upon the IViewProvider<TView, TModel> abstraction so they can reload the view when a new key is coming in:

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}组合来存储元素。

This implementation depends on a (previously undefined) IModelProvider<TModel> abstraction. This is basically your old ModelLocator, but by using a generic type you can make the implementation much easier, because we can have one instance of this type per TModel (the same holds for ViewProvider), which saves you from having to do things with storing elements with the { Type + key } combination.

您可以按以下方式注册所有内容:

You can register this all as follows:

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 Injector将为您批量注册它们。使用 RegisterOpenGeneric ,您可以指定一个开放泛型抽象,并告诉Simple Injector当请求该抽象的近泛版本时使用哪个实现。最后一行搜索您的应用程序,以查找所有控制器类型并将其注册到系统中。

With RegisterManyForOpenGeneric you let Simple Injector search the supplied assemblies to look for implementations of the given open generic abstraction and Simple Injector will batch-register them for you. With RegisterOpenGeneric you specify an open-generic abstraction and tell Simple Injector which implementation to use when a close-generic version of that abstraction is requested. The last line searches through your application to look for all controller types and registers them in the system.

这篇关于在Simple Injector中使用自定义参数解析类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆