In previous posts I mentioned how, by using the Binding markup extension, the view automatically resolves its bounded properties from a view model object that is stored in its DataContext property. How the view model ends up being referenced in the view’s DataContext property was left to be some kind of magic. In this post I will talk about the component that is responsible for making this happen – the ViewModelLocator.
Before diving into the details, let’s re-set the context. We have a simple view model with a single property, named FirstName:
And a single page containing a TextBlock control with its Text property bounded to the view model’s FirstName property:
Notice how the AutoWireViewModel attached property is set on the Page control. This property is where the magic happens. The ViewModelLocator is implemented as an attached behavior. All you have to do is set the AutoWireViewModel attached property on the page, and the ViewModelLocator chooses the appropriate view model, creates it, and stores its reference in the page’s DataContext property.
There are two different ways for the ViewModelLocator to choose the right view model for a view: Conventions and Factories.
In this approach, the view model is chosen based on a convention, which is by default, a class in the same assembly and with the same full name as the view, with the ViewModel suffix. For example, for the above view, named KonaSample.MainPage, the KonaSample.MainPageViewModel class, contained in the same assembly, is chosen by default.
The default convention can be overridden with the SetDefaultViewTypeToViewModelTypeResolver method, which receives a delegate that maps between the view type and the matching view model type. For example, to look for the view models in their own namespace, use the following convention:
Once the appropriate view model type is found, a new instance is created by calling the Activator.CreateInstance method. This default instantiation behavior is not enough for view models without a parameter-less constructor, or when the same view model instance should be used every time. It can be overridden with the SetDefaultViewModelFactory method, which receives a delegate that returns a ViewModel instance based on a given view model Type. For example, the following code uses an IoC container to resolve view models:
Using Custom Factories
A different, more verbose approach, than using conventions, is to register a specific view model factory for each view. Factories are in higher priority than conventions. The ViewModelLocator falls back to using conventions only after it fails to resolve a view model with a custom factory. Factories are registered per view type. For example, the following code registers a custom factory for the above view model:
Unlike the SetDefaultViewModelFactory method, which receives a delegate that returns a ViewModel derived class, the Register method receives a delegate that returns a BindableBase derived class, which is less restrictive. I assume that it will change in the official release and both methods will return BindableBase derived objects, as currently, there is no reason to restrict the view models to derive from the ViewModel class.
Another issue, that I assume will be fixed in the official release, is that, currently, there is no Unregister method to remove existing factories.
I prefer to use conventions whenever possible and register a specific factory only in special cases. That way, I don’t need to update the registration code for each new view.