如何使用构造函数依赖注入将集合中的模型提供给其 ViewModel?
我在 WPF 应用程序中使用构造函数依赖注入,并且不断遇到以下模式,因此想了解其他人对此的意见并了解替代解决方案。
目标是将 ViewModel 的层次结构连接到类似的 Model 层次结构,以便在每个模型中呈现信息的责任在于其自己的 ViewModel 实现。 (该模式也会在其他情况下出现,但 MVVM 应该是一个很好的例子。)
这是一个简化的示例。假设我有一个包含更多模型集合的模型:
public interface IPerson
{
IEnumerable<IAddress> Addresses { get; }
}
public interface IAddress
{
}
我想在 ViewModel 中镜像此层次结构,以便我可以将 ListBox(或其他内容)绑定到 Person ViewModel 中的集合:
public interface IPersonViewModel
{
ObservableCollection<IAddressViewModel> Addresses { get; }
void Initialize();
}
public interface IAddressViewModel
{
}
子 ViewModel 需要呈现来自子模型的信息,因此它是通过构造函数注入的:
public class AddressViewModel : IAddressViewModel
{
private readonly IAddress _address;
public AddressViewModel(IAddress address)
{
_address = address;
}
}
问题是,将子模型提供给相应的子视图模型的最佳方法是什么?
该示例很简单,但在典型的实际情况中,ViewModel 具有更多依赖项 - 每个视图模型都有自己的依赖项(等等)。我正在使用 Unity 1.2(尽管我认为这个问题与其他 IoC 容器相关),并且我正在使用 Caliburn 的视图策略来自动查找并将适当的视图连接到 ViewModel。
这是我当前的解决方案:
父 ViewModel 需要为每个子 Model 创建一个子 ViewModel,因此它在初始化期间使用的构造函数中添加了一个工厂方法:
public class PersonViewModel : IPersonViewModel
{
private readonly Func<IAddress, IAddressViewModel> _addressViewModelFactory;
private readonly IPerson _person;
public PersonViewModel(IPerson person,
Func<IAddress, IAddressViewModel> addressViewModelFactory)
{
_addressViewModelFactory = addressViewModelFactory;
_person = person;
Addresses = new ObservableCollection<IAddressViewModel>();
}
public ObservableCollection<IAddressViewModel> Addresses { get; private set; }
public void Initialize()
{
foreach (IAddress address in _person.Addresses)
Addresses.Add(_addressViewModelFactory(address));
}
}
满足 Func
接口已注册到主UnityContainer
中。工厂方法使用子容器来注册 ViewModel 所需的 IAddress
依赖项,然后解析子 ViewModel:
public class Factory
{
private readonly IUnityContainer _container;
public Factory(IUnityContainer container)
{
_container = container;
}
public void RegisterStuff()
{
_container.RegisterInstance<Func<IAddress, IAddressViewModel>>(CreateAddressViewModel);
}
private IAddressViewModel CreateAddressViewModel(IAddress model)
{
IUnityContainer childContainer = _container.CreateChildContainer();
childContainer.RegisterInstance(model);
return childContainer.Resolve<IAddressViewModel>();
}
}
现在,当 PersonViewModel
初始化时,它会循环遍历模型中的每个 Address
并调用 CreateAddressViewModel()
(通过 Func
参数注入)。 CreateAddressViewModel()
创建一个临时子容器并注册 IAddress
模型,以便当它从子容器解析 IAddressViewModel
时,AddressViewModel
获取通过其构造函数注入的正确实例。
这对我来说似乎是一个很好的解决方案,因为 ViewModel 的依赖关系非常清晰,并且它们很容易测试并且不知道 IoC 容器。另一方面,性能还可以,但不是很好,因为可以创建很多临时子容器。我最终也得到了很多非常相似的工厂方法。
- 这是使用 Unity 将子模型注入子 ViewModel 的最佳方法吗?
- 在其他 IoC 容器(例如 Autofac)中是否有更好(或更快)的方法?
- 鉴于 MEF 不是传统的 IoC 容器但仍用于组合对象,那么如何使用 MEF 解决这个问题呢?
I'm using constructor dependency injection in my WPF application and I keep running into the following pattern, so would like to get other people's opinion on it and hear about alternative solutions.
The goal is to wire up a hierarchy of ViewModels to a similar hierarchy of Models, so that the responsibility for presenting the information in each model lies with its own ViewModel implementation. (The pattern also crops up under other circumstances but MVVM should make for a good example.)
Here's a simplified example. Given that I have a model that has a collection of further models:
public interface IPerson
{
IEnumerable<IAddress> Addresses { get; }
}
public interface IAddress
{
}
I would like to mirror this hierarchy in the ViewModels so that I can bind a ListBox (or whatever) to a collection in the Person ViewModel:
public interface IPersonViewModel
{
ObservableCollection<IAddressViewModel> Addresses { get; }
void Initialize();
}
public interface IAddressViewModel
{
}
The child ViewModel needs to present the information from the child Model, so it's injected via the constructor:
public class AddressViewModel : IAddressViewModel
{
private readonly IAddress _address;
public AddressViewModel(IAddress address)
{
_address = address;
}
}
The question is, what is the best way to supply the child Model to the corresponding child ViewModel?
The example is trivial, but in a typical real case the ViewModels have more dependencies - each of which has its own dependencies (and so on). I'm using Unity 1.2 (although I think the question is relevant across the other IoC containers), and I am using Caliburn's view strategies to automatically find and wire up the appropriate View to a ViewModel.
Here is my current solution:
The parent ViewModel needs to create a child ViewModel for each child Model, so it has a factory method added to its constructor which it uses during initialization:
public class PersonViewModel : IPersonViewModel
{
private readonly Func<IAddress, IAddressViewModel> _addressViewModelFactory;
private readonly IPerson _person;
public PersonViewModel(IPerson person,
Func<IAddress, IAddressViewModel> addressViewModelFactory)
{
_addressViewModelFactory = addressViewModelFactory;
_person = person;
Addresses = new ObservableCollection<IAddressViewModel>();
}
public ObservableCollection<IAddressViewModel> Addresses { get; private set; }
public void Initialize()
{
foreach (IAddress address in _person.Addresses)
Addresses.Add(_addressViewModelFactory(address));
}
}
A factory method that satisfies the Func<IAddress, IAddressViewModel>
interface is registered with the main UnityContainer
. The factory method uses a child container to register the IAddress
dependency that is required by the ViewModel and then resolves the child ViewModel:
public class Factory
{
private readonly IUnityContainer _container;
public Factory(IUnityContainer container)
{
_container = container;
}
public void RegisterStuff()
{
_container.RegisterInstance<Func<IAddress, IAddressViewModel>>(CreateAddressViewModel);
}
private IAddressViewModel CreateAddressViewModel(IAddress model)
{
IUnityContainer childContainer = _container.CreateChildContainer();
childContainer.RegisterInstance(model);
return childContainer.Resolve<IAddressViewModel>();
}
}
Now, when the PersonViewModel
is initialized, it loops through each Address
in the Model and calls CreateAddressViewModel()
(which was injected via the Func<IAddress, IAddressViewModel>
argument). CreateAddressViewModel()
creates a temporary child container and registers the IAddress
model so that when it resolves the IAddressViewModel
from the child container the AddressViewModel
gets the correct instance injected via its constructor.
This seems to be a good solution to me as the dependencies of the ViewModels are very clear and they are easily testable and unaware of the IoC container. On the other hand, performance is OK but not great as a lot of temporary child containers can be created. Also I end up with a lot of very similar factory methods.
- Is this the best way to inject the child Models into the child ViewModels with Unity?
- Is there a better (or faster) way to do it in other IoC containers, e.g. Autofac?
- How would this problem be tackled with MEF, given that it is not a traditional IoC container but is still used to compose objects?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
WPF 应用程序框架 (WAF) 的 ViewModel 示例应用程序展示了如何将模型和视图模型在一起。该示例使用 MEF 作为依赖注入框架。
The ViewModel sample application of the WPF Application Framework (WAF) shows how you could bring the Model and the ViewModel together. The sample uses MEF as Dependency Injection Framework.
根据容器,您是否可以不在工厂的 CreateAddressViewModel 方法中指定参数(命名或其他)?
根据容器,您的工厂可能必须知道参数的名称(TinyIoC 和 Castle afaik),或者它可能必须位于构造函数依赖项列表中的最后(YMMV 取决于容器),这不是很好,但它可以节省快速连续创建大量子容器的时间,并且接下来的 GC 颠簸,您仍然可以为所有其他依赖项获得 DI。
当然,如果您的虚拟机还具有需要相同 IAddress 的依赖项,那么这种情况就会失败,在这种情况下,除非您希望虚拟机了解容器,否则可能需要使用子容器。
更新:
如果您使用的容器的子容器使用“最后注册获胜”(我认为 Unity 就是这样做的),那么您可以每次将相同的子容器传递到您的工厂中,并让您的工厂只需注册新的 IAddress - 这样您就不会为每次迭代在堆上创建新的 UnityContainer 实例,并且如果您创建大量项目,它应该会减少垃圾收集。
Depending on the container can you not specify a parameter (named or otherwise) in your factory's CreateAddressViewModel method?
Depending on the container your factory may have to know the name of the parameter (TinyIoC and Castle afaik), or it may had to be last in the list of constructor dependencies (YMMV depending on containers), which isn't great, but it saves creating a lot of child containers in quick succession, and the GC thrashing that will follow, and you still get DI for all your other dependencies.
Of course this falls down if your VM also has a dependency that requires the same IAddress, in that case a child container is probably the way to go unless you want the VM to have knowledge of the container.
Update:
If you're using a subcontainer of a container that uses "last register wins" (which I think Unity does), then you could pass the same child container into your Factory each time, and have your factory simply register the new IAddress - that way you wouldn't be creating a new UnityContainer instance on the heap for each iteration and it should cut down on garbage collections if you're creating lots of items.