I'm Andrew Hoefling, and I work for FileOnQ as a Lead Software Engineer building mobile technologies for Government, Financial and First Responders using Xamarin. 

 

MVVM and Dependency Injection 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 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

public class Parent
{
    public virtual void Hello()
    {
        Console.WriteLine("Hello From the Parent");
    }
}

Child Class

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.

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

public class MyViewModel
{
    public string Message { get => "Hello from the ViewModel!"; }
}

View  

<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. 

  1. Find Shared Code
  2. Create folders for Model, Views and ViewModels
  3. Move classes into new folders
  4. 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

<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

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.

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

  1. Create ViewModel and Message Property
  2. Instantiate DataContext in code behind
  3. Add Data Binding in View

MainViewModel

The MainViewModel is going to be a simple class that just stores a message a public property.

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

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.

<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

  1. Add NuGet Reference for Microsoft.Extensions.DependencyInjection
  2. Create container
  3. Create MessageService
  4. Register MessageService to Container
  5. 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:

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

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)

public interface IMessageService
{
    string GetMessage();
}

Once the interface (contract) is defined we can implement that code in the concrete MessageService.

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

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:

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:

  1. Constructor Injection
  2. 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:

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.

public string Message { get => MessageService.GetMessage(); }

Our final completed class will look like this:

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.

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

 

 


Share

Tags

UWPAndroidiOSDependency InjectionMVVMSOLIDMicrosoft Extensions