问题
I am really just starting out with MVVM, IoC and Dependency Injection and I've hit a stumbling block that I don't know how to solve but I do understand why it's happening.
I am using Castle Windsor for DI and IoC functionality and MVVM Light as my MVVM framework in a WPF application. Using this tutorial I have managed to get Castle Windsor to create a MainPageViewModel
that has a IGroupRepository
injected into the constructor. I have registered a mock implementation of this in Castle Windsor.
The following is the only other code in the MainPageViewModel
class besides the constructor.
public ObservableCollection<GroupViewModel> Groups
{
get
{
var groupVms = new ObservableCollection<GroupViewModel>();
IEnumerable<Group> groups = _repository.GetAllGroups();
foreach (Group g in groups)
{
var vm = new GroupViewModel(g);
groupVms.Add(vm);
}
return groupVms;
}
}
The intention is to create a view model for each of the groups in the repository. However, doing this causes Castle Windsor to give the following exception:
Can't create component 'Planner.ViewModel.GroupViewModel' as it has dependencies to be satisfied. 'Planner.ViewModel.GroupViewModel' is waiting for the following dependencies:
- Service 'Planner.Models.Group' which was not registered.
I understand this exception - Castle Windsor is responsible for constructing my view models but it has no way of handling my entity.
I have done plenty of Googling but have found very few answers or suggestions to this problem which makes me think that what I am doing is wrong. This Stack Overflow question has two answers which suggest that having an entity on the view model is ok but I'm beginning to wonder if that's true. Other questions, such as this one suggest that the entity should be nowhere near the view model.
What is the correct way to resolve this problem?
Update: As requested, this is the stacktrace for the exception:
at Castle.MicroKernel.Handlers.DefaultHandler.AssertNotWaitingForDependency()
at Castle.MicroKernel.Handlers.DefaultHandler.ResolveCore(CreationContext context, Boolean requiresDecommission, Boolean instanceRequired, Burden& burden)
at Castle.MicroKernel.Handlers.DefaultHandler.Resolve(CreationContext context, Boolean instanceRequired)
at Castle.MicroKernel.Handlers.AbstractHandler.Resolve(CreationContext context)
at Castle.MicroKernel.DefaultKernel.ResolveComponent(IHandler handler, Type service, IDictionary additionalArguments, IReleasePolicy policy)
at Castle.MicroKernel.DefaultKernel.Castle.MicroKernel.IKernelInternal.Resolve(Type service, IDictionary arguments, IReleasePolicy policy)
at Castle.MicroKernel.DefaultKernel.Resolve(Type service, IDictionary arguments)
at Castle.Windsor.WindsorContainer.Resolve(Type service)
at Planner.ViewModel.ViewModelResolver.Resolve(String viewModelName) in D:\Planner\Planner\Planner\ViewModel\ViewModelResolver.cs:line 27
at Planner.ViewModel.ViewModelLocator.get_Item(String viewModelName) in D:\Planner\Planner\Planner\ViewModel\ViewModelLocator.cs:line 21
I thought that this was the correct behaviour because of the following code which (I believe) intercepts any calls to the constructor of a view model and injects them as appropriate.
public class ViewModelResolver : IViewModelResolver
{
private IWindsorContainer _container;
public object Resolve(string viewModelName)
{
if (_container == null)
{
_container = new WindsorContainer();
_container.Install(new WindsorViewsInstaller());
_container.Install(new WindsorRepositoriesInstaller());
}
var viewModelType =
GetType()
.Assembly
.GetTypes()
.Where(t => t.Name.Equals(viewModelName))
.FirstOrDefault();
return _container.Resolve(viewModelType);
}
}
Update 2: I think this answers Ritch's query:
public class ViewModelLocator : DynamicObject
{
public IViewModelResolver Resolver { get; set; }
public ViewModelLocator()
{
Resolver = new ViewModelResolver();
}
public object this[string viewModelName]
{
get
{
return Resolver.Resolve(viewModelName);
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = this[binder.Name];
return true;
}
}
I think I understand this a little more now. The problem isn't actually with the original code I posted. The problem is occurring actually setting up Windsor isn't it? I'm still not sure how I solve that problem though.
回答1:
The property Groups
on MainPageViewModel
creates a bunch of VMs that aren't in the container, but your stacktrace is the Locator looking to bind/create an instance of the GroupViewModel (Why? I cannot tell at this point with the code you've posted.).
You have a disconnect in how the GroupViewModel
s are created and what the container is doing. You either need to let Windsor create these through a factory interface Factory Interface Documentation or remove them from the container entirely and manage them yourself. Based on gut feeling, I would lean towards the factory interface.
回答2:
Ritch's answer led me in the right direction but I wanted to post a separate answer so that I can show the code that I ended up with in the hope that it will be useful to the next person trying to do this.
As well as Ritch's answer, this Stack Overflow question and this blog post by one of the main contributers to Castle Windsor, Krzysztof Koźmic, all helped me to solve this problem in what I believe is the correct way.
As Ritch said, I shouldn't have been calling the constructor for my view model directly - that is what the container is there for. So the way to do it is to create a class which will help Windsor to create your view model. This is known as a typed factory facility. The good thing about these classes is that you don't actually need to implement them - they are just interfaces. This is the code for the class that will eventually be used to create the view model:
public interface IGroupViewModelFactory
{
GroupViewModel Create(Group group);
}
This is injected into the constructor for the view model that will create the GroupViewModel
which in my case was the MainWindowViewModel
class. Here is the code for that class:
public class MainWindowViewModel : ViewModelBase
{
private readonly IGroupRepository _repository;
private readonly IGroupViewModelFactory _groupViewModelFactory;
public MainWindowViewModel(IGroupRepository repository, IGroupViewModelFactory groupViewModelFactory)
{
_repository = repository;
_groupViewModelFactory = groupViewModelFactory;
}
public ObservableCollection<GroupViewModel> Groups
{
get
{
var groupVms = new ObservableCollection<GroupViewModel>();
IEnumerable<Group> groups = _repository.GetAllGroups();
foreach (Group g in groups)
{
var vm = _groupViewModelFactory.Create(g);
groupVms.Add(vm);
}
return groupVms;
}
}
}
The final step is to register the factory class with Windsor and that is done by this piece of code:
_container.AddFacility<TypedFactoryFacility>();
_container.Register(
Component.For<Group>(),
Component.For<IGroupViewModelFactory>()
.AsFactory());
It's worth noting that the question I linked to earlier did not have the Component.For<Group>(),
line in the above piece of code. Without that I was getting an exception from Windsor but unfortunately I didn't keep the details and I can no longer replicate it so there was probably something else amiss in my application.
Through the magic of Castle Windsor, you can now create view models from entities in a repository!
来源:https://stackoverflow.com/questions/11548135/mvvm-using-entities-in-viewmodels