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 implementing a Model-View-ViewModel (MVVM) pattern in Uno Platform projects is similar to any other XAML Page based toolchain such as WPF and Xamarin.Forms for example. You unlock the power of MVVM once you implement a Dependency Injection solution as it simplifies all of your ViewModel dependencies. Microsoft has a great community library for Dependency Injection called Microsoft.Extensions.DependencyInjection.
In this article we will go over how to configure Microsoft.Extensions.DependencyInjection to work with Uno Platform in a MVVM architecture. This is going to be a basic introduction to get you started and there are many enhancements you can add on afterwards or use 3rd party libraries to simplify this.
Let’s get started!
Architecture
Before we start looking at any code let’s understand why we want to use Dependency Injection and Model-View-ViewModel (MVVM) as these are 2 very different patterns that are often used together to solve different problems.
- Dependency Injection
- Model-View-ViewModel (MVVM)
Dependency Injection
Dependency Injection is a type of Inversion of Control from the SOLID principles of object oriented programming. It allows your system to automatically inject instaniated objects into a constructor of a class while it has no knowledge of the instaniated objects.
- S - Single Responsibility Principle
- O - Open Closed Principle
- L - Liskov Substitution Principle
- I - Interface Segregation Principles
- D - Dependency Inversion Principle
If you are unfamiliar with the SOLID principles, I have provided short descriptions and some examples to help. Dependency Injection is an implementation of the Dependency Inversion Principle.
Single Responsibility Principle
The Single Responsibility principle states that your class should have only 1 job and have no other responsibilities. This means that many of your classes should be broken into smaller classes to manage your business rules and operations as your project grows.
Open Closed Principle
Open Closed Principle states that a class should be open for extension but closed for modification. A C# example of this would be creating the ability to override a method in a child class where the parent class marks that method as virtual.
Parent Class
1
2
3
4
5
6
7
public class Parent
{
public virtual void Hello()
{
Console.WriteLine("Hello From the Parent");
}
}
Child Class
1
2
3
4
5
6
7
public class Child : Parent
{
public override void Hello()
{
Console.WriteLine("Hello from the child");
}
}
Liskov Substitution Principle
The Liskov Substitution Principle states that objects can be replaced with instances of their sub-types without having to re-compile the application. Following up with our example from above with Child and Parent in the Open Closed Principle, I could use Child interchangable with Parent. If I have a method that uses a parameter of Parent that method could also accept Child and work the same way.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Program
{
public static void Main(string args[])
{
var child = new Child();
var parent = new Parent();
MyMethod(child);
MyMethod(parent);
}
public static void MyMethod(Parent p)
{
p.Hello();
}
}
Interface Segregation Principle
The Interface Segregation Principle states that no client should depend on methods that it does not use. This means it is best to have many small interfaces that handle each specific use case building a contract for your class. It is considered a violation of this principle to have generic interfaces that are used in a wide array of objects, this type of implementation makes your code-base brittle and difficult to change. Interfaces should make it easier for you to change your code-base.
Dependency Inversion Principle
The Dependency Inversion Principle states that one should depend on abstractions and not concretions. In other words your class should use an interface wherever possible, this allows the implementation of that interface or contract to change without your class needing to change.
This is the fundamental principle for Dependency Injection.
Model View ViewModel (MVVM)
Mode View ViewModel architecture is a popular system design pattern that is used in many UI based applications. In the microsoft stack it is commonly found in:
- Windows Presentation Framework (WPF)
- Universal Windows Platform (UWP)
- Xamarin.Forms
- Uno Platform
- etc.
The goal of MVVM architecture is de-couple your business rules from the View or Page. This produces a clean implementation of the Single Responsibility Pattern and allows the developers to have all of their UI logic in one spot and all of their business rules in another.
View
The UI code and in the case of Uno Platform this will be your XAML page and code behind. If your page is called MainPage you will have 2 files that represent your View
- MainPage.xaml
- MainPage.xaml.cs
In these files you should never be invoking database request, calling backend services or other business related APIs. Your view only controls how things are rendered on the screen. We will talk about data binding a little bit later, which connects the View to the ViewModel.
ViewModel
The ViewModel handles all of your business rules generates the data that will ultimately be displayed on the screen. The Model can be used in the ViewModel to make it easier for data binding to the View, but isn’t always necessary. Anything that you want displayed on the screen or connected to the View will need to be public in this class.
In my experience the ViewModel is best used as an orchestrator, I try not to let my ViewModels call directly into a database or service. I create other services and classes to handle these rules and let my ViewModel orchestrate those calls. It is very easy for a ViewModel to get unruly with too many dependencies and violating the Single Responsbility Principle from above.
Model
The Model is a simple class that contains properties and it typically doesn’t contain any business rules. This class is used to transform any complex types from a database entity for service layer to an easy to use class for the presentation layer of your application. The Model can be referenced by the ViewModel to render data on the View with ease.
Data Binding
MVVM applications use a concept called Data Binding to connect the View to the ViewModel. This allows the View code to bind directly to a property on the ViewModel and render that on the screen without any knowledge of how that object got populated. There are several different techniques of Data Binding, Let’s go over the most basic example
Let’s create a simple View/ViewModel relationship where we display a message in a TextBlock.
ViewModel
1
2
3
4
public class MyViewModel
{
public string Message { get => "Hello from the ViewModel!"; }
}
View
1
<TextBlock Text="{Binding Message}" />
In the View code we add the special syntax called a markup extension as the property value for Text. The curly braces tell the system we are about to use a markup extension and the keyword Binding tells the system to look on the DataContext property for the Message.
Architecture Conclusion
If you were unfamiliar with MVVM or Dependency Injection, hopefully you have a better understanding before diving into the techniques. These are complicated design patterns that we as an industry expect everyone to know. There are many articles available out there that will help with your understanding.
Now we can start going through a working Uno Platform application on how to properly configure MVVM and Dependency Injection.
New Project
Let’s start by creating a new Uno Platform project, if you haven’t downloaded the Visual Studio extensions I strongly recommend it!
Open up Visual Studio and go to File->New and search for uno platform. You want to select Cross-Platform App (Uno Platform). Follow the prompts and it will create you a new project. I have named my project: Sample.MvvmAndDependencyInjection.
MVVM
Let’s get started with configuring our poject so it can support a MVVM architecture.
- Find Shared Code
- Create folders for Model, Views and ViewModels
- Move classes into new folders
- Updated startup sequence
Find Shared Code
The Shared code in Uno Platform projects is easy to find, it typically has the keyword shared in it. In my example my shared code project name is Sample.MvvmAndDependencyInjection.Shared. Your shared project might have a different name.
Create new Folders
At the top level in the shared project create the following new folders
- Models
- Views
- ViewModels
Move Classes
To get started we need to move the MainPage into the Views folder. Once you have moved the class you will need to update the namespace, there will be 2 spots that the namespace needs to be updated in: MainPage.xaml and MainPage.xaml.cs. Since most Pages in Uno Platform leverage the concept of Partial Classes both spots need to be updated because together they make the 1 object of MainPage.
MainPage.xaml
1
2
3
4
5
6
7
8
9
10
11
12
<Page
x:Class="Sample.MvvmAndDependencyInjection.MainPage.Views"
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="Hello, world!" Margin="20" FontSize="30" />
</Grid>
</Page>
Update the x:Class property to include .Views at the end.
MainPage.xaml.cs
1
2
3
4
5
6
7
8
9
10
namespace Sample.MvvmAndDependencyInjection.Views
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
}
}
Easier to find in C# code, update the namespace to include .Views at the end.
Update Startup Sequence
Now that the MainPage has been moved you will need to update the startup sequence in the App.xaml.cs to reference the new location of our Views. At the top of the file add a new using statement that references your Views namespace.
1
using Sample.MvvmAndDependencyInjection.Views;
Add MainViewModel
At this point your application should build and deploy without issue. Everything has been setup and you are ready to start creating ViewModels. Let’s add a MainViewModel that updates the message on the MainPage. This will be done in 3 steps
- Create ViewModel and Message Property
- Instantiate DataContext in code behind
- Add Data Binding in View
MainViewModel
The MainViewModel is going to be a simple class that just stores a message a public property.
1
2
3
4
public class MainViewModel
{
public string Message { get => "Hello from our MainViewModel"; }
}
Instantiate DataContext
All Pages have a property called DataContext this is the property that connects your View to your ViewModel. Without it the View would not understand where to get the properties that we plan to bind.
In the constructor just instantiate our ViewModel and set it equal to the DataContext
1
2
3
4
5
6
7
8
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
Add Data Binding in View
Now that the DataContext is set and we have a MainViewModel the view can add the Data Binding syntax to set the Text property in the TextBlock.
1
2
3
4
5
6
7
8
9
10
11
12
<Page
x:Class="Sample.MvvmAndDependencyInjection.MainPage.Views"
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>
MVVM Conclusion
That’s all there is to a basic MVVM implementation in an Uno Platform project! This is a basic introduction to MVVM and there are many other topics that are useful in a MVVM project. If you want further reading topcis on next steps for MVVM here is a short list
- INotifyPropertyChanged - for data binding updates
- ViewModel Locator - auto mapping of the ViewModel to the View and removes the need to manually set the DataContext
Dependency Injection
Now that you have a working basic MVVM implementation, it is time to start working with a Dependency Injection implementation. There are many libraries and frameworks that can solve this, but we are going to use the community library provided by Microsoft. Microsoft.Extensions.DependencyInjection is becoming a standard library across many open source and closed source in the .NET ecosystem, it is a great library that has many of the popular Dependency Injections features working right out of the box.
Let’s get started
- Add NuGet Reference for Microsoft.Extensions.DependencyInjection
- Create container
- Create MessageService
- Register MessageService to Container
- Inject MessageService into MainViewModel
Add NuGet References
Start off by adding the NuGet packages to all the projects. Right click on the solution level in the solution explorer and select Manage NuGet. Once you have the search box available go ahead and search for Microsoft.Extensions.DependencyInjection.
Go ahead and add the latest version to all of your projects.
Create Container
The Container is the object that maintains all of the dependencies that can be resolved anywhere in the application. This may include instances and singletons that stay in memory for the life of the application. As well as implementation mappings that are instantiated at time of injection.
In the App.xaml.cs you will need to create new code to create your Container and register dependencies. Right now, we are just going to create the container and stub out the registration process.
Add the following code after the constructor:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public sealed partial class App : Application
{
public App()
{
InitializeComponent();
Container = ConfigureDependencyInjection();
}
public IServiceProvider Container { get; }
IServiceProvider ConfigureDependencyInjection()
{
throw new NotImplementedException();
}
}
Already in the Core Library of .NET there is a class called IServiceProvider this is the .NET version of the Container which is used in the community library. This interface provides great interop across the .NET Ecosystem. For our implmentation in the Uno Platform it doesn’t really matter too much.
Now let’s go ahead and create an instance of our container
1
2
3
4
5
6
7
8
9
10
IServiceProvider ConfigureDependencyInjection()
{
// Create new service collection which generates the IServiceProvider
var serviceCollection = new ServiceCollection();
// TODO - Register dependencies
// Build the IServiceProvider and return it
return serviceCollection.BuildServiceProvider();
}
Once we create our dependencies we will come back to the registration process. At this point our Container is being created correctly at application startup.
Create MessageService
In this example we are going to create a simple interface (contract) - implementation relationship with a Message Service. The purpose of this class is to retrieve a message for our MainViewModel when requested.
Typically I put Services like this in their own Services or Managers folder, you could even start creating a new project to further de-couple these objects from your shared UI code.
Let’s start by defining the IMessageService interface (contract)
1
2
3
4
public interface IMessageService
{
string GetMessage();
}
Once the interface (contract) is defined we can implement that code in the concrete MessageService.
1
2
3
4
5
6
7
public class MessageService : IMessageService
{
public string GetMessage()
{
return "Hello from Message Service & Dependency Injection";
}
}
Register MessageService to Container
Now that we have defined our MessageService it is time to register it with the Container. Let’s pick up right where we left off in the App.xaml.cs container creation code. Find our TODO comment and replace it with a registration statement that defines a mapping between the IMessageService and the MessageService.
1
serviceCollection.AddTransient<IMessageService, MessageService>();
When registering services to the Container there is a concept called lifecycle management which tells the Container how a dependency that object should be registered. This allows you to register instances and singletons that remain in memory for the lifecycle of the Container. It also allows you to register dependencies that are instantiated on an as needed basis.
Putting it all together, our final code will look like this:
1
2
3
4
5
6
7
8
9
10
11
IServiceProvider ConfigureDependencyInjection()
{
// Create new service collection which generates the IServiceProvider
var serviceCollection = new ServiceCollection();
// Register the MessageService with the container
serviceCollection.AddTransient<IMessageService, MessageService>();
// Build the IServiceProvider and return it
return serviceCollection.BuildServiceProvider();
}
Inject MessageService into MainViewModel
We can now inject the the MessageService into the MainViewModel and update the Message property. Let’s break this down into 2 parts:
- Constructor Injection
- Use MessageService
Constructor Injection
Now we can supply a constructor parameter to the MainViewModel of IMessageService this will be resolved in the next step. For now let’s just assume it is getting injected and we should save the isntance into our ViewModel.
I prefer to create the protected properties for my dependencies as it provides great extension points for other objects that inherit from it. Let’s define how this works in code:
1
2
3
4
5
6
7
8
9
10
11
public class MainViewModel
{
protected IMessageService MessageService { get; }
public MainViewModel(IMessageService messageService)
{
MessageService = MessageService;
}
// omitted code
}
MessageService Usage
Further down in our code let’s update the usage of the Message property to return data from our MessageService instead. This will allow the MainViewModel to render whatever data is provided by the service, de-coupling the MainViewModel from any service layer rules.
1
public string Message { get => MessageService.GetMessage(); }
Our final completed class will look like this:
1
2
3
4
5
6
7
8
9
10
11
public class MainViewModel
{
protected IMessageService MessageService { get; }
public MainViewModel(IMessageService messageService)
{
MessageService = MessageService;
}
public string Message { get => MessageService.GetMessage(); }
}
Resolve the MainViewModel
There is one final step you need to complete before building your solution, the MainViewModel needs to be resolved correctly and inject the dependencies. There is a great utility function called the ActivatorUtils which uses the IServiceProvider to instaniate the object and inject parameters into the constructor.
Go into your code behind for MainPage.xaml.cs and add the following statement where you instantiate the DataContext.Your completed MainPage.xaml.cs should look like this.
1
2
3
4
5
6
7
8
9
10
11
12
13
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
// Get a local instance of the container
var container = ((App)App.Current).Container;
// Request an instance of the ViewModel and set it to the DataContext
DataContext = ActivatorUtilities.GetServiceOrCreateInstance(container, typeof(MainViewModel));
}
}
Completed App
At this point you should be able to build and run your app. The message printed on the screen should be the string that is hard-coded in the MessageService. Here is a screenshot of what the final solution should look like on UWP.
Since Uno Platform is cross platform, this code will work across the different platforms
- Android
- iOS
- WASM
Conclusion
There are a lot of topics going on in this article, creating both a simple MVVM application that leverages advanced Dependency Injection techniques. If you got lost along the way or find it easier to learn from a code sample, head over to the github link below and download all the code from this article.
-Happy Coding