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.
(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
- Register the
ViewModelLocator.Mvvm
xmlns (xml namespace) in the xaml code. In our sample code the root namespace isViewModelLocator
- 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.
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
- FindViewModel - This method determines where the ViewModel is and returns the type
- 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
.
- Check if the
DependencyObject
is aFrameworkElement
. TheDataContext
only exists on theFrameworkElement
so we need to ensure we have the correct type. - Use the
FineViewModel
method and get the correct ViewModel type. - 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
Happy Coding