AvaloniaUI: Dependency Injection
Tags: csharp, dotnet
Published: 2020-09-30 15:57:14

Hello, in this post I gonna explain how to use dependency injection in AvaloniaUI application and why is it so important. As always I will use my app [Camelot](https://github.com/IngvarX/Camelot/) as working example of used approach.

# What is dependency injection (DI) and why is it needed

Consider following example:

```csharp
private readonly TerminalService _terminalService;
private readonly DirectoryService _directoryService;

public TopOperationsViewModel()
{
    _terminalService = new TerminalService();
    _directoryService = new DirectoryService();
}
```
What's wrong with this code? It's a constructor of typical view model that depends on few services with business logic inside. In this snippet I created them in a constructor explicitly. That's bad approach because of following reasons:
1) What if we want to use another implementation of `TerminalService` for example that has same API but another implementation (lets call it `AnotherTerminalService`)? We will have to loop over all our files and change usages of `TerminalService` to `AnotherTerminalService`! A lot of work
2) What if we have to use services that depends on other services and so on? In this case in each view model we will have something like this mess:

```csharp
public TopOperationsViewModel()
{
    _terminalService = new TerminalService(new PathService(), new DriveService());
    _directoryService = new DirectoryService();
}
```

And if we have some runtime dependencies it's even worse:

```csharp
public TopOperationsViewModel()
{
    _terminalService = new TerminalService(new PathService(), RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? new LinuxSpecificService() : new WindowsSpecificService());
    _directoryService = new DirectoryService();
}
```
Absolutely unreadable code :smiley:

3) What if used service has some methods/properties that shouldn't be available in our view model? We can do something like this:

```csharp 
private readonly ITerminalService _terminalService; // note interface here

public TopOperationsViewModel()
{
    _terminalService = new TerminalService(); // saving by interface
    _directoryService = new DirectoryService();
}
```

In this case nobody could prevent programmer from modifying this code to use implementation directly based on knowledge what type is used.

Dependency injection provides flexible approach that solves all mentioned problems. In this approach you **INJECT** your dependencies into constructor of your type. Usually additional framework is used to help with this. Your code starts to look like this:

```csharp
private readonly ITerminalService _terminalService;
private readonly IDirectoryService _directoryService;

public TopOperationsViewModel(
    ITerminalService terminalService,
    IDirectoryService directoryService) // note that interface is used
{
    _terminalService = terminalService;
    _directoryService = directoryService;
}
```

In example above dependencies are passed by interface as constructor parameters. DI framework takes responsibility for passing these parameters in correct order. Interfaces hide details of implementation and allow you to change injected classes w/o modifying dependent code. Your view model starts to **depend on abstractions** instead of implementations.

# Using DI in Avalonia app

As I mentioned before under hood Avalonia uses [ReactiveUI](https://www.reactiveui.net/) framework. It has support for DI using library called [Splat](https://github.com/reactiveui/splat). I will show how to use it in your project.

[ReactiveUI Splat doc](https://www.reactiveui.net/docs/handbook/dependency-inversion/)

### Adding Splat

Splat is a dependency of `Avalonia.ReactiveUI`, but you can install it into your root project explicitly using Nuget package manager.

After installing you can access `Splat.Locator` instance and use it for registering/resolving your services.

### Service locator

Internally Splat uses service locator pattern for resolving dependencies. Service locator is actually anti-pattern. It provides static accessible everywhere services resolver (locator). `Splat.Locator.Current` is an example of such locator. It could be used like this:

```csharp
public override void OnFrameworkInitializationCompleted()
{
    if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
    {
        desktop.MainWindow = new MainWindow
        {
            DataContext = Locator.Current.GetRequiredService<MainWindowViewModel>()
        };
    }

    base.OnFrameworkInitializationCompleted();
}
```

Why is it anti-pattern? It used as a way to get rid of dependencies in your code but instead of that it adds one dependency - service locator itself! I recommend to avoid using this pattern if possible. Sometimes it could be useful, but you don't need in most cases.

### Registering dependencies

I moved dependencies registration into separate static class `Bootstrapper` which is called from program entry point:

```csharp
Bootstrapper.Register(Locator.CurrentMutable, Locator.Current); // note that I'm passing Splat service locators as parameters
```

`Locator.CurrentMutable` allows you to register new services while `Locator.Current` allows to resolve registered services.

`Bootstrapper` itself looks like this:

```csharp
public static class Bootstrapper
{
    public static void Register(IMutableDependencyResolver services, IReadonlyDependencyResolver resolver)
    {
        services.Register<IPlatformService>(() => new PlatformService());  // Call services.Register<T> and pass it lambda that creates instance of your service
    }
}
```

What if you want to create service with constructor parameters? You can achieve this using following code:

```csharp
services.RegisterLazySingleton<IUnitOfWorkFactory>(() => new LiteDbUnitOfWorkFactory(
    resolver.GetService<DatabaseConfiguration>()
));
```
Look how `IReadonlyDependencyResolver` instance is used for creating service instance:
```charp
resolver.GetService<DatabaseConfiguration>() // call resolver.GetService<T>() to get instance of type T
```

`IMutableDependencyResolver` has 3 methods for registering your service:
1) `RegisterConstant` allows you to inject some constant value of specific type. I use it for injecting configuration:

```csharp
var filePanelConfiguration = new FilePanelConfiguration();
configuration.GetSection("FilePanel").Bind(filePanelConfiguration);
services.RegisterConstant(filePanelConfiguration);
```
2) `Register`. Service registered with this name will be created each time requested:

```csharp
services.Register(() => new GeneralSettingsViewModel( 
    resolver.GetService<LanguageSettingsViewModel>()
));
```
3) `RegisterLazySingleton`. Once created, service instance will be reused in future so it acts like a singleton.

```csharp
services.RegisterLazySingleton<IBitmapFactory>(() => new BitmapFactory());
```

### Registering dependencies properly

What if Splat doesn't find implementation for your service? Modern frameworks mostly throw exception in this case but Splat injects `null` instead. Why is it not good? You want to see error on startup but you will have runtime error (NRE somewhere in your code) that much harder to detect. How to avoid this? I recommend to use extension methods that will throw an error if resolved type instance is `null`:

```csharp
public static TService GetRequiredService<TService>(this IReadonlyDependencyResolver resolver)
{
    var service = resolver.GetService<TService>();
    if (service is null) // Splat is not able to resolve type for us
    {
        throw new InvalidOperationException($"Failed to resolve object of type {typeof(TService)}"); // throw error with detailed description
    }

    return service; // return instance if not null
}
```

Usage of this method is similar:

```csharp
services.Register<ITopOperationsViewModel>(() => new TopOperationsViewModel(
    resolver.GetRequiredService<ITerminalService>(),
    resolver.GetRequiredService<IDirectoryService>(),
    resolver.GetRequiredService<IFilesOperationsMediator>()
));
```

[Example of code](https://github.com/IngvarX/Camelot/blob/master/src/Camelot/DependencyInjection/ReadonlyDependencyResolverExtensions.cs)

With this approach you won't have unexpected `NullReferenceException` in your code.

### Examples of using DI

Usage in view model:

```csharp
public TopOperationsViewModel(
    ITerminalService terminalService,
    IDirectoryService directoryService,
    IFilesOperationsMediator filesOperationsMediator)
{
    _terminalService = terminalService;
    _directoryService = directoryService;
    _filesOperationsMediator = filesOperationsMediator;

    SearchCommand = ReactiveCommand.Create(Search);
    OpenTerminalCommand = ReactiveCommand.Create(OpenTerminal);
}
```

Registration of this type:

```csharp
services.Register<ITopOperationsViewModel>(() => new TopOperationsViewModel(
    resolver.GetRequiredService<ITerminalService>(),
    resolver.GetRequiredService<IDirectoryService>(),
    resolver.GetRequiredService<IFilesOperationsMediator>()
));
```

All registrations code is available [here](https://github.com/IngvarX/Camelot/blob/master/src/Camelot/DependencyInjection/Bootstrapper.cs)

### DI testing

Developers often forget to register implementations of used interfaces. How to automate check that everything is registered properly? For this purpose solution is to write tests. You can do following:
1) Call registrations code and try to resolve specific service/view model
2) Call registrations code and try to create instances of all registered types. 

In my project I used combined approach. I checked that all registrations are correct (using wrapper for `IMutableDependencyResolver`) but also I have dynamically created from code instances of dialogs so I checked them separately. Full tests code is available [here](https://github.com/IngvarX/Camelot/blob/master/tests/Camelot.Tests/DependencyInjectionTests.cs).

# Conclusion

Hope that now you know how to use DI in your Avalonia app and why is it needed. Feel free to contact me via comments if you have any questions. Thanks for reading!