Home ViewModelLocator for MVVM applications in Uno Platform
Post
Cancel

ViewModelLocator for MVVM applications in Uno Platform

The Uno Platform Getting Started blog series is a blog series that I put together for October of 2020. It contains several articles that will help you get started building scalable enterprise applications in Uno Platform. Be sure to checkout all the blogs in the series by heading to the 1st article - Uno Platform Getting Started Series

When building cross-platform apps using Uno Platform it is a common technique to use the Model-View-ViewModel (MVVM) architecture. This is a way to separate out your User Interface logic from the business rules that power the page. A ViewModel is a special class that provides data binding to the View. Many MVVM frameworks provide a pre-built ViewModelLocator. This article goes over how to build a custom ViewModelLocator for your project.

The ViewModelLocator described here has been adapted from the one used in Prism Library. The structure remains very similar as they have designed a fantastic ViewModelLocator

What is MVVM?

MVVM stands for Model-View-ViewModel an application design pattern made famous by Microsoft. It enables developers to decouple their User Interface logic from the business rules. For example custom animations or rendering items on a screen will be your page. Then API calls to retrieve data and loading that data into collections would be ViewModel code.

/assets/img/2020-10-14/mvvm.png

(Image provided by Microsoft Xamarin Documentation)

If you are not familiar with the pattern, you should check out the Xamarin documentation for MVVM as it thoroughly explains the pattern.

Simple ViewModel Binding

Consider this simple ViewModel that stores a Message to be displayed on the screen for a Hello World app.

1
2
3
4
public class MainViewModel
{
    public string Message => "Hello World from MainViewModel!";
}

Once the ViewModel is created we don’t need to do anything special to it. We simple just instantiate it or new it up in the View’s code behind and assign it to the DataContext.

1
2
3
4
5
6
7
8
public sealed partial class MainPage
{
    public MainPage()
    {
        InitializeComponent();
        DataContext = new MainViewModel();
    }
}

Now you can update the View code to bind to the Message property instead of hard coding a string in the XAML.

1
2
3
4
5
6
7
8
9
10
11
12
13
<Page
    x:Class="ViewModelLocator.Views.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="{Binding Message}" Margin="20" FontSize="30" />
    </Grid>

</Page>

The View Model Locator

The View Model Locator is a technique in MVVM applications to automatically assign a ViewModel to a View’s DataContext with no additional code required in the View or code behind. This technique becomes increasingly useful when you want to use Dependency Injection with your MVVM application. This will automatically resolve any dependencies when the ViewModel is instantiated.

How Does This Work?

We are not adding a BasePage or re-writing the core code to solve this problem. We are going to use a concept known as attached properties. An attached property allows you to execute logic on public members of an object that does not exist on the class. This technique can be applied in the constructor or in the xaml. If you have been working in Uno Platform, UWP or Xamarin.Forms apps you most likely have used an attached property and didn’t even know it. For example defining a Grid and defining the row or column via Grid.Row="0" is using an attached property.

1
2
3
4
5
6
7
8
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <TextBlock Grid.Row="0" Text="This is using an attached property!" />

</Grid>

If you are interested in learning more about attached properties you should read the Microsoft UWP Documentations on this technique

ViewModelLocator Usage

We are going to do things a little backwards from most of my articles. It will be helpful to see the final API before we start.

The goal is to add 2 lines of code to our XAML to register the ViewModelLocator. Let’s plan to define the ViewModelLocator in the Mvvm namespace. We will need to add the following lines to our view

  1. Register the ViewModelLocator.Mvvm xmlns (xml namespace) in the xaml code. In our sample code the root namespace is ViewModelLocator
  2. Using the new xmlns reference access the ViewModelLocator to turn it on
1
2
xmlns:mvvm="using:ViewModelLocator.Mvvm"
mvvm:ViewModelLocator.AutoWireViewModel="True"

That should be all the code needed to automatically bind a ViewModel to a View. Now as you create more and more View/ViewModel relationships it will automatically instantiate it and assign it to the View. Here is our updated entire view code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<Page
    x:Class="ViewModelLocator.Views.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:mvvm="using:ViewModelLocator.Mvvm"
    mvvm:ViewModelLocator.AutoWireViewModel="True"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="{Binding Message}" Margin="20" FontSize="30" />
    </Grid>

</Page>

Create the ViewModelLocator

Now that we have our API usage defined we can create the ViewModelLocator which will implement an attached property to assign the correct ViewModel to the DataContext. Let’s start by creating a new folder in your shared code called Mvvm, then create the ViewModelLocator class.

/assets/img/2020-10-14/ViewModelLocator-SolutionExplorer.jfif

Start by creating the basic attached property which includes the accesser, setter and property changed methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static class ViewModelLocator
{
    public static DependencyProperty AutoWireViewModelProperty = DependencyProperty.RegisterAttached("AutoWireViewModel", typeof(bool),
        typeof(ViewModelLocator), new PropertyMetadata(false, AutoWireViewModelChanged));

    public static bool GetAutoWireViewModel(UIElement element)
    {
        return (bool)element.GetValue(AutoWireViewModelProperty);
    }

    public static void SetAutoWireViewModel(UIElement element, bool value)
    {
        element.SetValue(AutoWireViewModelProperty, value);
    }

    private static void AutoWireViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // TODO - Implement binding
    }
}

This is the most basic code you will need to implement an attached property. When the AutoWireViewModelChanged is invoked it will pass in the page as the parameter. Any object in Uno Platform that inherits from DependencyObject which is the base object passed. This enables more advanced scenarios, but we are going to focus on a simple page binding.

To complete this we need to implement 2 steps

  1. FindViewModel - This method determines where the ViewModel is and returns the type
  2. Instantiation - Once the ViewModel is found it needs to be set to the DataContext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static Type FindViewModel(Type viewType)
{
    string viewName = string.Empty;

    if (viewType.FullName.EndsWith("Page"))
    {
        viewName = viewType.FullName
            .Replace("Page", string.Empty)
            .Replace("Views", "ViewModels");
    }

    var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
    var viewModelName = string.Format(CultureInfo.InvariantCulture, "{0}ViewModel, {1}", viewName, viewAssemblyName);

    return Type.GetType(viewModelName);
}

Our logic here looks at the current page or DependencyObject and attempts to finding a matching ViewModel based on the name. For example our code looks at the MainPage and understands our convention maps MainViewModel. Both of these classes contain the keyword Main which generates the mapping. The method then uses reflection to get the object type and returns it. Your FindViewModel logic may differ if you have different namespaces where your ViewModels exist.

Once you have defined your FindViewModel method we can implement the Bind method. This method will take the returned type and instantiate it which will be applied to the DataContext.

  1. Check if the DependencyObject is a FrameworkElement. The DataContext only exists on the FrameworkElement so we need to ensure we have the correct type.
  2. Use the FineViewModel method and get the correct ViewModel type.
  3. Create a new instance of the view model and store it to the DataContext.
1
2
3
4
5
6
7
8
private static void Bind(DependencyObject view)
{
    if (view is FrameworkElement frameworkElement)
    {
        var viewModelType = FindViewModel(frameworkElement.GetType());
        frameworkElement.DataContext = Activator.CreateInstance(viewModelType);
    }
}

This example uses the Activator to create a new instance of the View Model and is a basic example of a View Model Locator. To expand upon this, I would update the Activator invocation use the Dependency Injection container to resolve the View Model. This would ensure all the dependies get injected without any additional work.

If you are using the Microsoft.Extensions.DependencyInjection library you can swap out the Activator invocation for the ActivatorUtilities. We aren’t going to cover dependency injection in this article, but if you want to futher expand on this technique you may find this snippet useful

1
frameworkElement.DataContext = ActivatorUtilities.GetServiceOrCreateInstance(((App)App.Current).Container, viewModelType);

Once you have created the Bind method you can update the AutoWireViewModelChanged implementation to use the Bind method. As a safety check I always ensure that it is a new variable before applying the binding, you don’t want to constantly be re-wiring a View Model.

1
2
3
4
5
private static void AutoWireViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if ((bool)e.NewValue)
        Bind(d);
}

You have now built your ViewModelLocator to ensure you code is correct I have the full code put together in one snippet here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public static class ViewModelLocator
{
    public static DependencyProperty AutoWireViewModelProperty = DependencyProperty.RegisterAttached("AutoWireViewModel", typeof(bool),
        typeof(ViewModelLocator), new PropertyMetadata(false, AutoWireViewModelChanged));

    public static bool GetAutoWireViewModel(UIElement element)
    {
        return (bool)element.GetValue(AutoWireViewModelProperty);
    }

    public static void SetAutoWireViewModel(UIElement element, bool value)
    {
        element.SetValue(AutoWireViewModelProperty, value);
    }

    private static void AutoWireViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if ((bool)e.NewValue)
            Bind(d);
    }

    private static void Bind(DependencyObject view)
    {
        if (view is FrameworkElement frameworkElement)
        {
            var viewModelType = FindViewModel(frameworkElement.GetType());
            frameworkElement.DataContext = Activator.CreateInstance(viewModelType);
        }
    }

    private static Type FindViewModel(Type viewType)
    {
        string viewName = string.Empty;

        if (viewType.FullName.EndsWith("Page"))
        {
            viewName = viewType.FullName
                .Replace("Page", string.Empty)
                .Replace("Views", "ViewModels");
        }

        var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
        var viewModelName = string.Format(CultureInfo.InvariantCulture, "{0}ViewModel, {1}", viewName, viewAssemblyName);

        return Type.GetType(viewModelName);
    }
}

At this point you can run your application and your View Model will automatically be wired up to the View. Any new View/ViewModel paris you create will be wired up just by adding the 2 xaml statements mentioned earlier.

Conclusion

That is all you need to setup a View Model Locator in your MVVM application. The purpose of the View Model Locator is to automatically connect your View to the ViewModel. An advancement on the techniques here would be using Dependency Injection in your View Model Locator to automatically inject dependencies into your View Model.

If you had any trouble getting this setup, be sure to check out my code sample

This post is licensed under CC BY 4.0 by the author.