在简单注入器中解析具有自定义参数的类

2024-05-15

我正在使用以下命令创建 WPF MVVM 应用程序简易注射器作为 DI 容器。现在,当我尝试从简单注入器解析视图时遇到一些问题,因为我需要在构造时将参数传递到构造函数中(而不是在将视图注册到容器时,因此这不是适用的:简单注入器将值传递到构造函数中 https://stackoverflow.com/q/13747027/521773).

我所追求的是这样的:

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

我读过几个地方,这在简单注入器中是不可能的,因为它不应该完成(包括这里:https://simpleinjector.codeplex.com/discussions/397080 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;
}

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

现在我有以下问题:

  • 使用视图模型键解析视图的最佳方法是什么?
  • 我是否需要进行一些重构才能实现此功能?
  • 自从我创建了自己的基于字典以来,我是否错过了 DI 容器的一些功能ViewModelLocator?

扩展信息

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

这是针对 PowerPoint 的 VSTO 应用程序的一部分(影响设计的某些部分)。当选择屏幕上的对象时,将打开一个任务面板(这基本上是填充属性视图上面解释过)。所选对象具有提供给视图的键,以便视图从 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);
    }

}

将服务定位器注入到您的类中(几乎)永远不是可行的方法,因为这不允许在编译时检查依赖项和运行时依赖分析 https://simpleinjector.readthedocs.org/en/latest/diagnostics.html。出于这个原因,我还可以建议注册您的所有根类型(例如您的视图),否则 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 +键 } 组合。

您可以按如下方式注册这一切:

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();

With RegisterManyForOpenGeneric您让 Simple Injector 搜索提供的程序集以查找给定开放通用抽象的实现,Simple Injector 将为您批量注册它们。和RegisterOpenGeneric您指定一个开放通用抽象,并告诉 Simple Injector 当请求该抽象的封闭通用版本时要使用哪个实现。最后一行搜索您的应用程序以查找所有控制器类型并将它们注册到系统中。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在简单注入器中解析具有自定义参数的类 的相关文章

  • 在 LINQ 查询中返回不带时间的日期

    我正在编写一个查询 我想计算按日期联系我们的呼叫中心的次数 看起来很简单 但由于联系日期字段是日期时间字段 我得到了时间 因此当我按联系日期 时间 分组时 每个联系日期实例的计数为 1 所以 我想只按日期分组 而不按时间分组 下面是我用来查
  • WPF PRISM 事件订阅不会消亡

    我有一个 PRISM RegionManager 其中有几个区域 顶部的功能区区域和其下方用于我的视图的主要内容区域 相当基本 该应用程序以主要内容区域中的 主页 视图开始 当我单击功能区上的按钮时 我将第二个视图注入内容区域并在区域管理器
  • 如何在我的应用程序中使用 Windows Key

    Like Windows Key E Opens a new Explorer Window And Windows Key R Displays the Run command 如何在应用程序的 KeyDown 事件中使用 Windows
  • 为什么 POSIX 允许在只读模式下超出现有文件结尾 (fseek) 进行搜索

    为什么寻找文件结尾很有用 为什么 POSIX 让我们像示例中那样在以只读方式打开的文件中进行查找 c http en cppreference com w c io fseek http en cppreference com w c io
  • 将字符串从非托管代码传递到托管

    我在将字符串从非托管代码传递到托管代码时遇到问题 在我的非托管类中 非托管类 cpp 我有一个来自托管代码的函数指针 TESTCALLBACK FUNCTION testCbFunc TESTCALLBACK FUNCTION 接受一个字符
  • 写入和读取文本文件 - C# Windows 通用平台应用程序 Windows 10

    有用 但在显示任何内容之前 您必须在文本框中输入内容 我想那是因为我使用了 TextChanged 事件处理程序 如果我希望它在没有用户交互的情况下显示文本文件的内容 我应该使用哪个事件处理程序 因此 我想在按下按钮时将一些数据写入 C W
  • 当 Cortex-M3 出现硬故障时如何保留堆栈跟踪?

    使用以下设置 基于 Cortex M3 的 C gcc arm 交叉工具链 https launchpad net gcc arm embedded 使用 C 和 C FreeRtos 7 5 3 日食月神 Segger Jlink 与 J
  • 为什么模板不能位于外部“C”块内?

    这是一个后续问题一个答案 https stackoverflow com questions 4866433 is it possible to typedef a pointer to extern c function type wit
  • 使用向量的 merge_sort 在少于 9 个输入的情况下效果很好

    不知何故 我使用向量实现了合并排序 问题是 它可以在少于 9 个输入的情况下正常工作 但在有 9 个或更多输入的情况下 它会执行一些我不明白的操作 如下所示 Input 5 4 3 2 1 6 5 4 3 2 1 9 8 7 6 5 4 3
  • 使用安全函数在 C 中将字符串添加到字符串

    我想将文件名复制到字符串并附加 cpt 但我无法使用安全函数 strcat s 来做到这一点 错误 字符串不是空终止的 我确实设置了 0 如何使用安全函数修复此问题 size strlen locatie size nieuw char m
  • Windows 10 中 Qt 桌面应用程序的缩放不当

    我正在为 Windows 10 编写一个简单的 Qt Widgets Gui 应用程序 我使用的是 Qt 5 6 0 beta 版本 我遇到的问题是它根本无法缩放到我的 Surfacebook 的屏幕上 这有点难以判断 因为 SO 缩放了图
  • 在 URL 中发送之前对特殊字符进行百分比编码

    我需要传递特殊字符 如 等 Facebook Twitter 和此类社交网站的 URL 为此 我将这些字符替换为 URL 转义码 return valToEncode Replace 21 Replace 23 Replace 24 Rep
  • 已过时 - OpenCV 的错误模式

    我正在使用 OpenCV 1 进行一些图像处理 并且对 cvSetErrMode 函数 它是 CxCore 的一部分 感到困惑 OpenCV 具有三种错误模式 叶 调用错误处理程序后 程序终止 Parent 程序没有终止 但错误处理程序被调
  • GDK3/GTK3窗口更新的精确定时

    我有一个使用 GTK 用 C 语言编写的应用程序 尽管该语言对于这个问题可能并不重要 这个应用程序有全屏gtk window与单个gtk drawing area 对于绘图区域 我已经通过注册了一个刻度回调gtk widget add ti
  • 在Linux中使用C/C++获取机器序列号和CPU ID

    在Linux系统中如何获取机器序列号和CPU ID 示例代码受到高度赞赏 Here http lxr linux no linux v2 6 39 arch x86 include asm processor h L173Linux 内核似
  • 在 ASP.NET 中将事件冒泡为父级

    我已经说过 ASP NET 中的层次结构 page user control 1 user control 2 control 3 我想要做的是 当控件 3 它可以是任何类型的控件 我一般都想这样做 让用户用它做一些触发回发的事情时 它会向
  • 窗体最大化时自动缩放子控件

    有没有办法在最大化屏幕或更改分辨率时使 Windows 窗体上的所有内容自动缩放 我发现手动缩放它是正确的 但是当切换分辨率时我每次都必须更改它 this AutoScaleDimensions new System Drawing Siz
  • 如何在 C# 中播放在线资源中的 .mp3 文件?

    我的问题与此非常相似question https stackoverflow com questions 7556672 mp3 play from stream on c sharp 我有音乐网址 网址如http site com aud
  • 更改显示的 DPI 缩放大小使 Qt 应用程序的字体大小渲染得更大

    我使用 Qt 创建了一些 GUI 应用程序 我的 GUI 应用程序包含按钮和单选按钮等控件 当我运行应用程序时 按钮内的按钮和字体看起来正常 当我将显示器的 DPI 缩放大小从 100 更改为 150 或 200 时 无论分辨率如何 控件的
  • 如何连接字符串和常量字符?

    我需要将 hello world 放入c中 我怎样才能做到这一点 string a hello const char b world const char C string a hello const char b world a b co

随机推荐

  • 如何使用 FLinq 在 F# 中进行外连接?

    问题几乎说明了一切 我有一个如下形式的大 flinq 查询 for alias1 in table1 do for alias2 in table2 do if alias1 Id alias2 foreignId 使用这种形式 如何在这两
  • 是否可以在 Visual Studio 中重命名项目,使其文件夹名称也重命名?

    假设我们正在开发一个名为 MyProject 的项目 我希望能够将其名称更改为 MyProject2 并将其文件夹名称也重命名为 MyProject2 这可以从 Visual Studio 中实现吗 如果不是 如何让这种情况发生在 外部 呢
  • preg_match 所有以@开头的单词?

    我对正则表达式不太确定 所以我不得不问你 如何用 PHP 判断字符串中是否包含以 开头的单词 例如我有一个像 This is for codeworxx 这样的字符串 我很抱歉 但我没有任何起点 希望你能帮忙 谢谢 萨沙 好的 谢谢你的结果
  • 如何从 BottomNavigationBar 中删除图标?

    我只需要 BottomNavigationBarItem 中的标签 但我找不到删除它们的方法 您可以隐藏标签showSelectedLabels and showUnselectedLabels设置为 false 但图标没有等效项 构造函数
  • 如何使用 Python 3 正确显示倒计时日期

    我正在尝试获取将显示的倒计时 基本上就像一个世界末日时钟哈哈 有人可以帮忙吗 import os import sys import time import datetime def timer endTime datetime datet
  • 无法从 JSON 请求获取数据,尽管我知道它已返回

    我试图获取从 getJSON 返回的数据 但我无法让它工作 我已经在 search twitter API 上尝试了相同的代码 效果很好 但它不适用于其他网站 我知道数据已返回 因为我在使用检查器时可以找到它 我通过检查器找到的值是 id
  • Java RMI - 客户端超时

    我正在使用 Java RMI 构建分布式系统 它必须支持服务器丢失 如果我的客户端使用 RMI 连接到服务器 如果该服务器出现故障 例如电缆问题 我的客户端应该会收到异常 以便它可以连接到其他服务器 但是当服务器出现故障时 我的客户端什么也
  • 更改 Windows Phone 系统托盘颜色

    有没有办法将 Windows Phone 上的系统托盘颜色从黑色更改为白色 我的应用程序有白色背景 所以我希望系统托盘也是白色的 您可以在页面 XAML 中执行此操作
  • C++ Streambuf 方法可以抛出异常吗?

    我正在尝试找到一种方法来获取读取或写入流的字符数 即使存在错误并且读 写结束时间较短 该方法也是可靠的 我正在做这样的事情 return stream rdbuf gt sputn buffer buffer size 但如果streamb
  • 创建IOT设备边缘Python Sdk

    如何创建物联网边缘设备 实际上我使用azure sdk服务物联网并创建了普通设备 但我不知道如何使用azure服务客户端sdk创建物联网边缘设备 这与创建普通设备的方式类似 唯一的区别是您需要添加一个capabilities财产 devic
  • 如何检测当前的 JSF 版本?

    我正在开发 jsf webapp 现在我需要知道我正在使用什么 JSF 版本 我在哪里可以查到这个 提前致谢 您的意思是 以编程方式 你可以从Package getImplementationVersion http docs oracle
  • 批量插入不适用于 NULL 数据

    当我从 CSV 文件将批量数据插入到表中时 它不起作用 显示错误 第 2 行第 9 列的批量加载数据转换错误 类型不匹配或指定代码页的字符无效 csv 文件中的第 9 列值为空 我该如何处理这个问题 根据这些信息 我认为目标表的特定字段被定
  • 抛出 Java 异常时是否会生成堆栈跟踪?

    这是假设我们不调用 printstacktrace 方法 只是抛出和捕获 我们正在考虑这样做是为了解决一些性能瓶颈 不 堆栈跟踪是在构造异常对象时生成的 而不是在抛出异常对象时生成的 Throwable 构造函数调用 fillInStack
  • 如何在 DropDownList 中保留空格 - ASP.net MVC Razor 视图

    我在视图中通过以下方式绑定我的模型 问题是我的项目文本是格式化文本 单词之间有空格 如下所示 123 First 234 00 123 AnotherItem 234 00 123 Second 234 00 我想保留此项目文本中的空格 即
  • PHP:是否可以从文件内容(字符串)创建 SplFileObject 对象?

    例如 contents file get contents image png 是否可以从 contents 创建 SplFileObject 对象 Thanks php 有一些特殊的流包装器 http www php net manual
  • 矩阵到数组 C#

    这将是转换方阵的最有效方法 例如 1 2 3 4 5 6 7 8 9 into 1 2 3 4 5 6 7 8 9 in c 我在做 int array2D new int 1 2 3 4 5 6 7 8 9 int array1D new
  • iOS 13 检查 CLLocationManager 的临时授权状态

    根据 WWDC 视频 https developer apple com videos play wwdc2019 705 https developer apple com videos play wwdc2019 705 当你要求 Al
  • Erlang:如何将原子转换为字符串?

    我想从原子转换为字符串 Input hello world Output hello world 我该如何实现这一目标 Use atom to list http erlang org doc man erlang html atom to
  • 使用 SERVER_NAME 时出现 Flask 404

    在我的 Flask 配置中 我将 SERVER NAME 设置为 app example com 之类的域 我这样做是因为我需要使用url for with external网址 如果未设置 SERVER NAME Flask 会认为服务器
  • 在简单注入器中解析具有自定义参数的类

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