[Windows 8(.1)] Introduction to Prism for the Windows Runtime

February 25th, 2014 | Posted by Tom in .NET | Article | Prism | Windows 8 | WinRT | Read: 6,153

Early 2014, the Patterns & Practices teams at Microsoft released a new version of the Prism library, dedicated to the creation of business apps using C# and XAML on Windows 8.1 (a version for Windows 8 already existed since 2013, the team “just” updated the version to add the support of Windows 8.1).

In this article, we’ll provide the basics to use Prism to create a Windows Store application so let’s start !

Architecture

Here is the diagram of a typical Windows Store application that is developed using the Prism library:

 Logical architecture of a Windows Store business app that uses Prism

As you can see, this is a standard architecture: MVVM to separate the concerns of presentation, presentation logic, and model, a data access layer using repositories and some Services proxies to retrieve the data, etc. The good part is that some elements are directly provided by Prism (ViewBase, ViewModelBase, ViewModelLocator, Event Aggregator, etc.) so, you just have to provide the XAML code (View), the logic code (ViewModel) and the business objects (Model).

Creating the application

Creating the application is the same as creating any Windows Store application (File –> New Project) but, once Visual Studio has finished to create the project, you need to add reference to the following Nuget packages:

  • Prism.StoreApps: this is the class library that provides MVVM support with lifecycle management, and core services to a Windows Store app (see here for the list of all the features provided)
  • Prism.PubSubEvents: the Portable Class Library that contains classes that implement event aggregation (again, check here to see the class contains on the library)

image

Once this is done, derive the App class from the MvvmAppBase class, to allow your application to support the MVVM pattern and the core services (navigation, events aggregation, etc.):

sealed partial class App : MvvmAppBase

Don’t forget to change the base class of the file App.xaml too or you’ll get compile errors:

<storeApps:MvvmAppBase
    x:Class="DemoPrism.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DemoPrism"
    xmlns:storeApps="using:Microsoft.Practices.Prism.StoreApps">

</storeApps:MvvmAppBase>

The MvvmAppBase class is an abstract class that define one method that need to be implemented: OnLaunchApplication. This method is used to perform the initial launch of the application so this is where we’ll define the page that’ll be launched on start:

protected override Task OnLaunchApplication(LaunchActivatedEventArgs args)
{
    NavigationService.Navigate("MainPage", null);

    return Task.FromResult<object>(null);
}

This method need to return a Task object so you can use Task.FromResult to return an empty task. To don’t have conflicts with the initial overridden methods, don’t forget to delete the OnLaunched and OnSuspending methods.

The code use the NavigationService property, which is Navigation Service that we can use to perform navigation within the application. Unfortunately, if you try to run this code, you’ll get the following exception:

SNAGHTML2de0d500

Indeed, by default, Prism use a particular convention for the Views: they need to be placed on the “Views” folder. In my case, I used to have the following structure on my applications:

image

So I have a “Views” folder but this folder contains sub-folders containing the big features of the application. So I need to find a way to tell the Prism library to look for the views on that sub-folders. And this can be done by overriding the GetPageType method of the MvvmAppBase class:

protected override Type GetPageType(string pageToken)
{
    var subPageToken = pageToken.Substring(0, pageToken.Length - pageToken.IndexOf("Page", StringComparison.Ordinal));

    var assemblyQualifiedAppType = this.GetType().GetTypeInfo().AssemblyQualifiedName;
    var pageNameWithParameter = assemblyQualifiedAppType.Replace(this.GetType().FullName, this.GetType().Namespace + ".Views.{0}.{1}");
    var viewFullName = string.Format(CultureInfo.InvariantCulture, pageNameWithParameter, subPageToken, pageToken);
    var viewType = Type.GetType(viewFullName);

    return viewType;
}

Now, if I execute the code, the application loads correctly and the page is displayed:

image

Registering services using Dependency Injection

This is not a mandatory steps but Prism has been a long time co-worker of Dependency Injection tools like Unity. So let’s start by adding the Nuget package of your choice (here, I’ll use Unity):

image

Then, declare a Unity container to register and resolve types and instances:

private readonly IUnityContainer _container = new UnityContainer();

You can then override the OnInitialize method of MvvmAppBase to register types and perform any other initialization:

protected override void OnInitialize(IActivatedEventArgs args)
{
    _container.RegisterInstance<INavigationService>(NavigationService);
    _container.RegisterType<IEventAggregator, EventAggregator>(new ContainerControlledLifetimeManager());
}

And, to be able to resolve the types, you just need to override the Resolve method:

protected override object Resolve(Type type)
{
    return _container.Resolve(type);
}

Now, let’s see how we can add the Views and the ViewModels to the application.

Creating a View and its ViewModel

The Views has nothing particular related to the Prism library. You just composed it like you used to do it so this is just XAML content that you defined using Visual Studio or Blend.

To define the ViewModel associated to the view, you can use the ViewModelLocator.AutoWireViewModel property:

xmlns:storeApps="using:Microsoft.Practices.Prism.StoreApps"
storeApps:ViewModelLocator.AutoWireViewModel="true"

Once this property is set to True, the ViewModelLocator will try to instanciate the corresponding ViewModel based on a particular convention: the ViewModels need to be located in the same assembly, in a namespace ended by .ViewModels and the ViewModel name must match the View name and ends with ViewModel. But this is always what you have in your application. Indeed, you can have your ViewModels in a separate assembly or you can have them within the folder containing the views, like I use to have:

image

Fortunately, you can once again change the way Prism is loading your ViewModels, by specifying the ViewModelLocator.SetDefaultViewTypetoViewModelTypeResolver method:

protected override void OnInitialize(IActivatedEventArgs args)
{
    _container.RegisterInstance<INavigationService>(NavigationService);
    _container.RegisterType<IEventAggregator, EventAggregator>(new ContainerControlledLifetimeManager());

    ViewModelLocator.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
    {
        var subPageToken = viewType.Name.Substring(0, viewType.Name.Length - viewType.Name.IndexOf("Page", StringComparison.Ordinal));

        var assemblyQualifiedAppType = this.GetType().GetTypeInfo().AssemblyQualifiedName;
        var pageNameWithParameter = assemblyQualifiedAppType.Replace(this.GetType().FullName, this.GetType().Namespace + ".Views.{0}.{1}ViewModel");

        var viewFullName = string.Format(CultureInfo.InvariantCulture, pageNameWithParameter, subPageToken, viewType.Name);
        var viewModelType = Type.GetType(viewFullName);

        return viewModelType;
    });
}

Now, if you execute your application and put a breakpoint to the ViewModel’s constructor, you’ll see that the execution is stopped correctly (the ViewModel has been correctly instantiated):

image

Also, thanks to the Dependency Injection we put in place with Unity, we can construct the ViewModel and get, in constructor’s parameters, the type we want to retrieve:

public class MainPageViewModel : ViewModel
{
    private readonly INavigationService _navigationService;
    private readonly IEventAggregator _eventAggregator;

    public MainPageViewModel(INavigationService navigationService, IEventAggregator eventAggregator)
    {
        this._navigationService = navigationService;
        this._eventAggregator = eventAggregator;
    }
}

image

Handling navigation between pages

To handle navigation between pages, Prism library offers to developer the interface INavigationAware (already implemented by the base ViewModel class). This interface exposes 2 methods:

  • OnNavigatedFrom: which is called before the navigation occurred. You can use this method to preserve the state of your data during the navigation
  • OnNavigatedTo: which is called after the navigation to a page is performed. This method can be used to retrieve parameter sent from a page to another page.

So this interface is really helpful if you want to execute navigation to another page within a component which has nothing to do with navigation (like a ViewModel for example!). And to change to another page, we’ll use the methods provided by the INavigationService (that is injected to the ViewModel’s constructor thanks to the dependency injection):

image

Saving properties’ values between the application’s states

As you know, a Windows Store application have different state: Not Running, Running, Suspended and Terminated. This is your job to preserve the properties’ value between the different states of your application so when users are going back to the application, they don’t feel there was a kind of pause (or termination) during the application’s execution.

So let’s take a look at the following example:

private string _mySuperString;

public string MySuperString
{
    get
    {
        return this._mySuperString;
    }
    set
    {
        this.SetProperty(ref this._mySuperString, value);
    }
}

And the dedicated XAML interface:

<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
            VerticalAlignment="Center">
    <TextBox HorizontalAlignment="Center"
             VerticalAlignment="Center"
             Text="{Binding MySuperString, Mode=TwoWay}" />
    <Button Content="Click Me!"
            HorizontalAlignment="Center"
            VerticalAlignment="Center" />
</StackPanel>

If I enter some text on the Textbox, the property will be updated in memory, thanks to the databinding feature. Each time the application is suspended and  terminated by the OS (which determined when to kill suspended apps to retrieve some resources), the text entered in the Textbox is deleted from the memory. To stop that, I can simply add the RestoreState attribute to the property:

private string _mySuperString;

[RestorableState]
public string MySuperString
{
    get
    {
        return this._mySuperString;
    }
    set
    {
        this.SetProperty(ref this._mySuperString, value);
    }
}

image

Now, I can execute the application and simulate a termination done by the OS, by using the Suspend and shutdown feature from the Debug Location toolbar:

image

If I execute the application again, without having to add other code, the MySuperString property will be automatically populated and the user interface will be modified:

image

Of course, you can also add more complex objects to the State to retrieve them later. For that, you’ll need to use the ISessionStateService (which exposes a Dictionary object) that manages the automatic load and save during application exit and startup!

First, to get the ISessionStateService object from your ViewModel, you need to get it injected to the constructor:

public MainPageViewModel(INavigationService navigationService, ISessionStateService sessionStateService)
{
    this._navigationService = navigationService;
    this._sessionStateService = sessionStateService;
}

Don’t forget to register the instance during the app’s initialization:

protected override void OnInitialize(IActivatedEventArgs args)
{
    _container.RegisterInstance<INavigationService>(NavigationService);
    _container.RegisterInstance<ISessionStateService>(SessionStateService);
}

Now, you just need to add your object to the session’s state:

public void SaveToSessionState()
    {
        var users = new List<User> { new User { Id = Guid.NewGuid(), Name = "Thomas LEBRUN" } };

        this._sessionStateService.SessionState.Add("DemoKey", users);
    }

public class User
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

And, finally, you can check (when the user navigated to the ViewModel) if the key exists in the dictionary:

public override void OnNavigatedTo(object navigationParameter, NavigationMode navigationMode, Dictionary<string, object> viewModelState)
{
    base.OnNavigatedTo(navigationParameter, navigationMode, viewModelState);

    if (_sessionStateService.SessionState.ContainsKey("DemoKey"))
    {
        var users = _sessionStateService.SessionState["DemoKey"] as List<User>;
        if (users != null)
        {
            //
        }
    }
}

Now, if you execute the code and simulate an termination of the app, you got a correct execution… or not :)

image

You’ll see that an exception is raised:

image

If you take a look at the details of the exception, you’ll see something familiar if you use to work with data serialization:

image

“Type ‘System.Collections.Generic.List`1[[DemoPrism.Views.Main.User, DemoPrism, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]’ with data contract name ‘ArrayOfUser:http://schemas.datacontract.org/2004/07/DemoPrism.Views.Main’ is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types – for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.”

Indeed, to save the session state, the system will serialize the items and here, we try to serialize a type (User) that is not known by the DataContractSerializer. So we need to teach it all about our custom type(s). This is done by overriding the method OnRegisterKnownTypesForSerialization of the MvvmAppBase class:

protected override void OnRegisterKnownTypesForSerialization()
{
    base.OnRegisterKnownTypesForSerialization();

    SessionStateService.RegisterKnownType(typeof(User));
    SessionStateService.RegisterKnownType(typeof(List<User>));
}

And now, the termination of the app works successfully and when the user navigated to the page, we can check/retrieve the results from the session state:

image

Simple but terribly useful isn’t it ?! Now, let’s see how we can communicate from a component to another one!

Communication between components using the Event Aggregator

Sometimes, you might need to communicate some data from a component to another one. This can be from ViewModelA to ViewModelB, ViewModelA to ViewC, ViewB to ViewA, etc. All this elements have one thing in common: they are not loosely coupled so there are no links between them. So how can we communicate from a component to another one when they are not linked ?

The answer is 2 words: Event Aggregator. This is a design pattern, also known as Publish/Subscribe, which is used to send a message (from the publisher) that is catched by all the subscribers (Martin Fowler talked about the pattern on his blog).

So how does it works ? Well, first, you need to get a reference to the Event Aggregator instance. This can be done through the dependency injection of the ViewModel’s constructor:

public MainPageViewModel(INavigationService navigationService, IEventAggregator eventAggregator, ISessionStateService sessionStateService)
{
    this._navigationService = navigationService;
    this._eventAggregator = eventAggregator;
    this._sessionStateService = sessionStateService;
}

Or thanks to Dependency Injection container you choose to use:

var eventAggregator = UnityServiceHelper.UnityContainer.Resolve<IEventAggregator>();

Then, we need to create the event object that will be sent from the publisher to the subscribers. This object is a simple class that inherits from the PubSubEvent<T> class:

public class SessionStateSavedEvent : PubSubEvent<object>
{
}

Now, we just have to publish an instance of this event. To do so, we’ll use the GetEvent method of the EventAggregator object and we’ll call the Publish method:

this._eventAggregator.GetEvent<SessionStateSavedEvent>().Publish(null);

This method takes in parameter the object you want to send to the subscribers (in our case, we don’t want to use any parameters so we send the null value). Finally, we need to add a handler that will be raised when the event will be catched by the subscriber:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    var eventAggregator = UnityServiceHelper.UnityContainer.Resolve<IEventAggregator>();
    eventAggregator.GetEvent<SessionStateSavedEvent>().Subscribe(OnSessionStateSavedEventSubscribe);
}

private void OnSessionStateSavedEventSubscribe(object obj)
{
    //
}

And voilà! Once the publisher send an instance of our event, all the subscribers will be notified and their handlers will be invoked:

image image

Of course, there are plenty other possibilities when using the Event Aggregator like specifying on which thread the handler will be executed or like filtering which handlers need to be executed:

image

image

 

Prism allows a lot of more (validation of user input, performance optimizations, etc.) but I hope this first overview gave you all you need to take a further look at this very cool library !

 

Happy coding!

 

PS: The source code of the article is available here.

You can follow any responses to this entry through the RSS 2.0 You can leave a response, or trackback.

Add Comment Register



Leave a Reply