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.