Monday, 10 June 2013

Changing the Default Conventions used in Prism for the Windows Runtime

Prism for the Windows Runtime specifies a ViewModelLocator object, that can be used to manage the instantiation of view models and their associations to views. This approach has the advantage that there’s a single class that’s responsible for view model instantiation.

The ViewModelLocator class uses an attached property, AutoWireViewModel, to associate view models with views. In the view’s XAML this attached property is set to true to indicate that the view model should be automatically connected to the view. When the property is set to true the AutoWireViewModelChanged event handler (in the ViewModelLocator object) runs which first attempts to resolve the view model from any mappings that have been registered by the Register method of the ViewModelLocator object (for more info see my previous blog post). If the view model cannot be resolved using this approach, the method falls back to using a convention-based approach to resolve the correct view model type. This convention assumes that:

  1. View models are in the same assembly as the view types.
  2. Views are in a .Views child namespace.
  3. View names end with “Page”.
  4. View models are in a .ViewModels child namespace.
  5. View model names correspond with view names and end with “ViewModel”.

Using this convention, a view named MainPage must be located in a .Views child namespace, with its view model being named MainPageViewModel, which resides in a .ViewModels child namespace in the same assembly.

However, Prism doesn’t force you to use this convention. If you wish to change it, you can. For instance, you may choose to have your view models in a separate assembly.

Overriding Prism’s Default Conventions

In order to demonstrate how to override Prism’s default conventions I’ve modified my sample PhotoViewer app so that both views and view models reside in the same Pages folder and .Pages child namespace.

pages

Prism’s default convention specifies that Views can be located in a .Views child namespace. In order to override this convention the FrameNavigationService object must be configured to look for views in a location other than the Views folder. This can be achieved by overriding the GetPageType method (from the MvvmAppBase class) in the App class, and by adding code to define the page location and naming convention.

protected override Type GetPageType(string pageToken)
{
    var assemblyQualifiedAppType = this.GetType().GetTypeInfo().AssemblyQualifiedName;
    var pageNameWithParameter = assemblyQualifiedAppType.Replace(this.GetType().FullName, this.GetType().Namespace + ".Pages.{0}Page");
    var viewFullName = string.Format(CultureInfo.InvariantCulture, pageNameWithParameter, pageToken);
    var viewType = Type.GetType(viewFullName);
    return viewType;
}

This method simply redefines the convention for locating and naming views by specifying that they can be located in a .Pages child namespace, and that view names end with “Page”. If the app is run at this point the MainPage will be displayed, but it will be devoid of photo thumbnails because the MainPageViewModel will not be supplying any data to the view as it can’t be located.

To solve this problem the ViewModelLocator class must be configured to look for view models in a location other than the ViewModels folder in the same assembly. This can be achieved by overriding the OnInitialize method (from the MvvmAppBase class) in the App class, and invoking the static ViewModelLocator.SetDefaultViewTypeToViewModelTypeResolver method, passing in a delegate that specifies a view type and returns a corresponding view model type.

protected override void OnInitialize(IActivatedEventArgs args)
{
    _container.RegisterInstance<INavigationService>(NavigationService);
    _container.RegisterType<IRepository, FileSystemRepository>(new ContainerControlledLifetimeManager());
 
    ViewModelLocator.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
        {
            var viewModelTypeName = string.Format(CultureInfo.InvariantCulture, "PhotoViewer.Pages.{0}ViewModel, PhotoViewer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=495efabf0725380b", viewType.Name);
            var viewModelType = Type.GetType(viewModelTypeName);
            return viewModelType;
        });
}

As previously explained, the method first registers the NavigationService instance and FileSystemRepository type with the Unity container, so that the view model classes can take dependencies on them. The delegate passed into the SetDefaultViewTypeToViewModelTypeResolver method then simply redefines the convention for locating view models by specifying that they can be located in the PhotoViewer.Pages namespace, and that view model names end with “ViewModel”.

While the convention I’ve adopted here is not recommended, it does demonstrate how to override the default convention specified by Prism. For small apps the default convention will most likely suffice. However, for larger apps you may wish to move your view models into a separate assembly, such as a PCL.

Summary

You do not have to use the default conventions that are defined by Prism to locate and name views and views models. To change the convention for naming and locating views you can override the GetPageType method in your App class. To change the convention for naming, locating, and associating view models with views you should invoke the static ViewModelLocator.SetDefaultViewTypeToViewModelTypeResolver method, and pass in a delegate that specifies a view type and returns a corresponding view model type.

The sample app can be downloaded here.

No comments:

Post a comment