# Are you using Dependency Injection in your .NET MAUI app yet?

# Introduction

In this article, I will show you how to leverage MAUI's built-in dependency injection capabilities to provide dependencies to your Views and ViewModels.

I absolutely love the *Dependency Inversion Principle (DIP)*, it is by far my favorite principle from *SOLID*, and *.NET MAUI* makes it very easy to apply *Inversion of Control (IoC)* with it, because as a first class-citizen, *Dependency Injection (DI)* already comes built-in with [Shell](https://learn.microsoft.com/dotnet/maui/fundamentals/shell/) apps. Like *ASP.NET Core*, *.NET MAUI* also uses [.NET's Dependency Injection pattern](https://learn.microsoft.com/dotnet/core/extensions/dependency-injection).

SOLID stands for:

* **S**RP = Single Responsibility Principle
    
* **O**CP = Open/Closed Principle
    
* **L**SP = Liskov Substitution Principle
    
* **I**SP = Interface Segregation Principle
    
* **D**IP = Dependency Inversion Principle
    

Uff, a lot of fancy acronyms and buzz words. While DIP, IoC and DI are not all exactly the same, they express and address different aspects of the same idea: Loose coupling. Instead of having hard dependencies in our code, which make it unflexible and difficult to maintain and test, the idea is to invert the control of the dependencies by hiding implementation details and only depending on interfaces or contracts instead.

Specific implementations of those dependencies are then injected. This can be done via constructor arguments, IoC containers, the builder pattern or specific setter methods. In this blog post, we will explore how to leverage Shell's built-in dependency injection, which uses a combination of the first three approaches.

This is entirely optional, of course. If you want, you can also use any other IoC container, there are plenty of great DI frameworks and IoC containers out there for .NET, like [TinyIoC](https://github.com/grumpydev/TinyIoC), [SimpleInjector](https://simpleinjector.org/) and [Prism](https://prismlibrary.com).

As always, you can find the code from this blog post in the [**sample repository**](https://github.com/ewerspej/maui-samples) on GitHub.

> **Note**: This post focusses on dependency injection in .NET MAUI and is not a general discussion or tutorial about what DI is. If you want to learn more about DI patterns (and anti-patterns) and good software design in general, I highly recommend reading "Dependency Injection - Principles, Practices and Patterns" by Steven van Deursen and Mark Seemann.

# Setting

For this example, we'll assume that we have a `MainPage`, a `MainViewModel` and an `IDeviceService` which is consumed by the `MainViewModel`. The `MainViewModel` somehow needs to be passed into the `MainPage` to serve as its `BindingContext` and the `IDeviceService` needs to be passed into the `MainViewModel`.

One of the most common ways to inject dependencies is via *constructor injection*, which we will mainly focus on today. Typically, this would look as follows:

```csharp
public MainPage(MainViewModel viewModel)
{
    InitializeComponent();
    BindingContext = viewModel;
}
```

Here, we have defined the `MainPage` constructor which takes in a `MainViewModel` as a parameter, so our dependency gets injected via the constructor and can then be used in the class.

Similarly, the `MainViewModel` constructor takes in an instance of an `IDeviceService` implementation:

```csharp
private readonly IDeviceService _deviceService;

public MainViewModel(IDeviceService deviceService)
{
    _deviceService = deviceService;
}
```

Without the use of a DI service, we would have to manually set up all the constructor calls and somehow manage which dependency goes where. For example, we would have to do the following in order to instantiate the `MainPage` in the *App.xaml.cs*:

```csharp
public App()
{
    InitializeComponent();
    var deviceService = DeviceService.Instance;
    var viewModel = new MainViewModel(deviceService);
    MainPage = new MainPage(viewModel);
}
```

This works perfectly fine as long as the app is small and doesn't use Shell; but, what if we have many different pages and want to use Shell to build our app hierarchy using routes and `<ShellContent>` objects?

One thing we definitely couldn't do is the following, because in XAML you cannot provide constructor parameters to a `DataTemplate`:

```xml
<Shell
  x:Class="MauiSamples.AppShell"
  xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  xmlns:views="clr-namespace:MauiSamples.Views">

  <TabBar>
    <Tab Title="Home">
      <ShellContent
        Title="Home"
        ContentTemplate="{DataTemplate views:MainPage}"
        Route="MainPage" />
    </Tab>
</Shell>
```

The `MainPage` takes a `MainViewModel` instance as a constructor parameter, but we cannot provide that in XAML.

This is where MAUI's built-in dependency injection comes in. It uses its own IoC container and performs the resolution of the dependencies for us automatically, provided that we register all the required dependencies.

So, let's have a look at how registration works.

# Registration

In MAUI, we use the builder pattern, which is a common approach for app configuration in modern .NET applications and technologies. It makes the configuration of features and dependencies very straightforward and keeps it all in a single place.

The *MauiProgram.cs* is the place where the app is usually configured and built. There, you will find something like this:

```csharp
public static MauiApp CreateMauiApp()
{
    var builder = MauiApp.CreateBuilder();
    builder
        .UseMauiApp<App>()
        .ConfigureFonts(fonts =>
        {
            fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
        })

    return builder.Build();
}
```

The `builder` object provides a `Services` collection where all dependencies get registered. This is how we let MAUI know that we have classes that have dependencies or that are dependencies for other classes.

Registering a new dependency is very easy and just takes a single line of code anywhere before the `return builder.Build()` call:

```csharp
builder.Services.AddSingleton<MainViewModel>();

return builder.Build();
```

The code above is used to register the `MainViewModel` class. However, we also need to register the `MainPage` as well for the automatic resolution to work:

```csharp
builder.Services.AddSingleton<MainViewModel>();
builder.Services.AddSingleton<MainPage>();

return builder.Build();
```

So, how does resolution work?

# Resolution

There are two types of dependency resolution in MAUI apps: *automatic* and *explicit*.

Automatic resolution uses constructor injection without explicitly requesting the dependency from the IoC container, while explicit resolution occurs on demand by explicitly requesting a specific dependency from the IoC container.

## Automatic Resolution with Shell

As we've already seen above, this type of resolution works *\- as the name suggests -* completely automatically by providing the required dependencies to the constructor:

```csharp
private readonly IDeviceService _deviceService;

public MainViewModel(IDeviceService deviceService)
{
    _deviceService = deviceService;
}
```

Shell takes care of this for you as long as you register the dependency's type as well as the type that uses this dependency, e.g. `MainViewModel` and an implementation of `IDeviceService`.

> **Note:** Only Shell apps support automatic constructor injection.

This is pretty cool, because we do not have to pass around our dependencies ourselves, Shell takes care of it and manages the lifetime and handles the injection for us.

## Explicit Resolution

If your class only exposes a parameterless constructor, then Shell cannot inject dependencies automatically for you. Or if you don't use Shell, because you want to use a [FlyoutPage](https://learn.microsoft.com/dotnet/maui/user-interface/pages/flyoutpage) or implement navigation completely manually without using routes, then you also cannot use the automatic resolution mechanism. Therefore, we need an explicit way to resolve dependencies.

> **Note:** I have included the following approaches to show that they are viable options. However, if you need to resolve dependencies manually in many different places or you have complex scenarios with deep dependency trees, you may want to use an independent DI framework or IoC container.

### Accessing the IServiceProvider

Imagine our `MainViewModel` for some reason doesn't provide a constructor with the `IDeviceService` parameter:

```csharp
private readonly IDeviceService _deviceService;

public MainViewModel() { }
```

In that case, we need to resolve the dependency manually by directly accessing the IoC container, e.g. in the constructor. One way to do this is the following, where we access the `IServiceProvider` (i.e. the `Services` object) via the `MauiContext` provided by our current `MainPage`:

```csharp
private readonly IDeviceService _deviceService;

public MainViewModel()
{
    _deviceService = Application.Current.MainPage
        .Handler
        .MauiContext
        .Services  // IServiceProvider
        .GetService<IDeviceService>();
}
```

One downside of this approach is that we have a dependency on the `Application` and the `MainPage` in our `MainViewModel`, which kind of defeats the purpose of using IoC in .NET applications following the MVVM pattern. This also makes automatic testing tricky, because if we want write unit tests for our `MainViewModel`, we shouldn't have any dependencies on the `Application` or the `MainPage`.

Alternatively, we can also pass down the `IServiceProvider` via the constructor *(either through automatic resolution or by manually injecting it in the constructor call)* and then resolve the required dependencies manually:

```csharp
private readonly IDeviceService _deviceService;
private readonly IAudioService _audioService;

public MainViewModel(IServiceProvider provider)
{
    _deviceService = provider.GetService<IDeviceService>();
    _audioService = provider.GetService<IAudioService>();
}
```

This slightly different approach allows us to keep the constructor signature short and allows us to use mocking frameworks with unit tests. This approach also works with Shell's automatic resolution without having to manually register the `IServiceProvider`.

😓 That'll work, but it's not pretty and we have to always pass down the `IServiceProvider`. Isn't there a better way? As you may have guessed: There is. 🤩

### ServiceHelper to the rescue!

If you don't want to or cannot use any constructor injection, then there is a better way to deal with the `IServiceProvider`, which is to create a static `ServiceHelper` class:

```csharp
public static class ServiceHelper
{
    public static IServiceProvider Services { get; private set; }

    public static void Initialize(IServiceProvider serviceProvider) => 
        Services = serviceProvider;

    public static T GetService<T>() => Services.GetService<T>();
}
```

Then, in your *MauiProgram.cs* you just need to call the Initialize method and pass in the `IServiceProvider` instance that the `MauiApp` class exposes once it was built:

```csharp
public static MauiApp CreateMauiApp()
{
    var builder = MauiApp.CreateBuilder();

    // ...

    builder.Services.AddSingleton<IDeviceService>(DeviceService.Instance);
    builder.Services.AddSingleton<MainViewModel>();
    builder.Services.AddSingleton<MainPage>();

    var app = builder.Build();

    //we must initialize our service helper before using it
    ServiceHelper.Initialize(app.Services);

    return app;
}
```

Then, in order to resolve the dependencies, you can access the static helper and call the `GetService<T>()` method:

```csharp
private readonly IDeviceService _deviceService;
private readonly IAudioService _audioService;

public MainViewModel()
{
    _deviceService = ServiceHelper.GetService<IDeviceService>();
    _audioService = ServiceHelper.GetService<IAudioService>();
}
```

The beauty of this approach is that we don't need to pass down the `IServiceProvider` in every single constructor call when we're not using constructor parameters and at the same time we can configure the `ServiceHelper` using a fake or mock implementation of `IServiceProvider` when writing unit tests.

Awesome! 🏆

> **Note**: I am aware that the ServiceHelper is an implementation of the Service Locator pattern, which is often regarded as an anti-pattern. However, in certain scenarios, especially with regards to .NET-baseed UI technologies, the Service Locator pattern is a necessity.

# Lifetime of Dependencies

As you may have noticed before, we have so far registered the dependencies using a method called `AddSingleton<T>()`. This group of methods can be used to register a single instance (hence *singleton*) of a class, which means that every time a dependency gets resolved, the exact same instance will be provided by the IoC container during the entire lifetime of the app.

There are three different lifetime modes for dependencies:

* Singleton
    
* Transient
    
* Scoped
    

The first one, as already mentioned, registers a single instance which will be kept *alive* during the entire lifetime of our app. This is done via the `AddSingleton<T>()` methods.

> **Note:** Here, the term singleton does not mean the Singleton Design Pattern.

The second mode is called *transient* and means that whenever a dependency registered with this mode gets resolved, a fresh instance of the registered type is provided. Transients can be registered using the `AddTransient<T>()` method(s).

Last but not least, there is the *scoped* mode, which is a little tricky to understand. Basically, *scoped* services or dependencies share the same lifetime as the Page or object that resolves them. If a non-singleton Page is closed or an object goes out of scope, so does the *scoped* dependency. This means resolving the same dependency multiple times within the same scope will always yield the same instance, while resolving the same dependency in different scopes will yield different instances. This can be particularly useful when components (e.g. Views) of the same Page share the same dependencies while different Pages should each have their own instance. *Scoped* dependencies can be registered using the `AddScoped<T>()` method(s).

> **Note:** There are multiple methods for each type, because you can register contracts (e.g. interfaces and abstract base classes) together with a specific type as well as instances and factories.

Personally, I do not need the *scoped* mode very often, I prefer the *singleton* and *transient* modes as they are sufficient for the more common scenarios.

# Common Pitfalls and *Gotchas!*

There are a couple of common mistakes and issues that I have noticed some people struggling with over the last months, there have been various Stack Overflow questions related to these problems. To make your life easier, I want to address a couple of them here.

## Always register all dependencies

Make sure to always register ***all*** dependencies *including any classes that require these dependencies,* otherwise you may see strange exceptions and errors.

One common mistake that I see people make very often is that they register their ViewModels, but they don't register the Pages that use those ViewModels in their constructor and then they see the following runtime error:

> System.MissingMethodException: 'No parameterless constructor defined for type 'MauiSamples.Views.MainPage'.'

This can be resolved by registering the `MainPage` along with the `MainViewModel`:

```csharp
builder.Services.AddSingleton<MainViewModel>();
// do NOT forget to also register the MainPage!
builder.Services.AddSingleton<MainPage>();
```

The same applies to any other dependencies that are automatically resolved. If you have a ViewModel that takes a dependency as a constructor parameter, then you need to register the ViewModel and all of its dependencies.

## Automatic constructor injection

Automatic resolution only works in the context of Shell, because Shell takes care of construction for you. If you use constructor injection without Shell by manually creating instances of your classes through explicit constructor calls, then you need to specify the dependencies in the constructor call yourself.

For example, when you use automatic resolution in your MAUI Shell app, but you also write unit tests for your ViewModel, which has no *parameterless* constructor, then you need to provide the dependencies (e.g. by using mocks or fakes) yourself:

```csharp
[Test]
public void SomeMethod_ArgumentsAreValid_ReturnsTrue()
{
    //arrange
    var deviceServiceMock = new Mock<IDeviceService>();
    var vm = new MainViewModel(deviceServiceMock.Object);

    //act
    var result = vm.SomeMethod(1,2,3);
 
    //assert
    Assert.That(result, Is.True);
}
```

The same applies when you manually create Page instances instead of using routes, which then leads to the creation of a [Navigation Stack](https://learn.microsoft.com/dotnet/maui/fundamentals/shell/navigation#perform-navigation), which is not governed by Shell.

## Resolving unknown types

If you attempt to resolve a type that has not been registered, you will see an exception. My recommendation is to not "swallow" this exception by using a *catch-all* clause. Instead, you should let your app crash in this case, because mandatory dependencies are required for the correct functioning of an app and it's usually the developer's fault when the required dependencies don't get registered. *Fail early, fail often* is the premise here.

## Adding dependencies during runtime

Unfortunately, it's not possible to register, replace or update dependencies during the lifetime of a .NET MAUI app, because the [service collection cannot be modified](https://stackoverflow.com/a/72554375/4308455) after the application is built. Bummer. If you have a scenario where you need to modify the dependencies frequently, e.g. based on user settings, etc. then you can either register a service provider that can handle this for you or you can use a third-party DI framework like the ones I've mentioned earlier.

# Conclusions and Next Steps

Dependency injection is a powerful first-class citizen in .NET MAUI and comes directly built-in, which is awesome, because we don't have to use any third-party frameworks or libraries, as long as we do not need to do anything beyond the restrictions and limitations of the built-in dependency injection mechanism.

Today, I have shown you how to leverage Shell's automatic constructor injection as well as ways to manually resolve dependencies. I might write another blog article on dependency injection and unit tests in .NET MAUI in the future. There are viable solutions to this problem, e.g. by mocking the `IServiceProvider` and using it with the static `ServiceHelper` class. Let me know if this is something you would like to read more about.

I hope this is useful for you. If you enjoyed this blog post, then follow me on [**LinkedIn**](https://www.linkedin.com/in/jewerspeters), subscribe to this [**blog**](https://hashnode.com/@ewerspej) and star the [**GitHub** **repository**](https://github.com/ewerspej/maui-samples) for this post so you don't miss out on any future posts and developments.
