.NET MAUI Documentation

Dependency Injection in .NET MAUI with MVVM

Dependency Injection (DI) is a fundamental design pattern that promotes loose coupling and makes applications more maintainable, testable, and extensible. In .NET MAUI, when following the Model-View-ViewModel (MVVM) pattern, DI plays a crucial role in managing dependencies between your ViewModels, services, and other components.

Why Use Dependency Injection in .NET MAUI?

Built-in DI in .NET MAUI

.NET MAUI applications are built on top of the .NET generic host, which includes a built-in, first-party dependency injection container. This makes it straightforward to register and resolve services.

Key Concepts of DI

Registering Services

Services are typically registered in the MauiProgram.cs file within the CreateMauiApp() method. You'll use the builder.Services collection to register your types.

Service Lifetimes

Here's an example of registering services with different lifetimes:


using Microsoft.Extensions.DependencyInjection;
using YourApp.Services;
using YourApp.ViewModels;

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

        // Registering a Singleton service
        builder.Services.AddSingleton<IMessageService, MessageService>();

        // Registering a Scoped service (example for a specific scope if needed)
        // In MAUI, 'Scoped' is less common than Singleton/Transient for most services
        // but can be useful for specific scenarios.
        // builder.Services.AddScoped<ISomeScopedService, SomeScopedService>();

        // Registering a Transient service
        builder.Services.AddTransient<IDataFetcher, DataFetcher>();

        // Registering ViewModels
        builder.Services.AddTransient<MainViewModel>();
        builder.Services.AddTransient<DetailsViewModel>();

        return builder.Build();
    }
}
        

Resolving Dependencies

You can resolve dependencies in several ways:

1. Constructor Injection (Recommended for ViewModels and Services)

This is the most common and recommended approach. The DI container automatically provides the required dependencies when an instance of the class is created.


using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using YourApp.Services;
using System.Windows.Input;

public partial class MainViewModel : ObservableObject
{
    private readonly IMessageService _messageService;
    private readonly IDataFetcher _dataFetcher;

    [ObservableProperty]
    private string _message;

    public ICommand ShowMessageCommand { get; }

    public MainViewModel(IMessageService messageService, IDataFetcher dataFetcher)
    {
        _messageService = messageService;
        _dataFetcher = dataFetcher;
        ShowMessageCommand = new AsyncRelayCommand(async () => await ExecuteShowMessageCommand());
    }

    private async Task ExecuteShowMessageCommand()
    {
        _message = await _messageService.GetWelcomeMessageAsync();
        // You could also use _dataFetcher here to get data
    }
}
        

2. Resolving from a Service Provider

While constructor injection is preferred, you might need to resolve services directly from the IServiceProvider in certain situations, such as within static methods or where constructor injection isn't feasible.


// Accessing the service provider from the App instance
var serviceProvider = Application.Current.Handler.MauiContext.Services;

// Resolving a service
var messageService = serviceProvider.GetService<IMessageService>();
var welcomeMessage = await messageService.GetWelcomeMessageAsync();
        

Example Service Implementations


// Interface for a message service
public interface IMessageService
{
    Task<string> GetWelcomeMessageAsync();
}

// Concrete implementation of the message service
public class MessageService : IMessageService
{
    public Task<string> GetWelcomeMessageAsync()
    {
        return Task.FromResult("Hello from .NET MAUI MVVM Dependency Injection!");
    }
}

// Interface for data fetching
public interface IDataFetcher
{
    Task<IEnumerable<string>> FetchItemsAsync();
}

// Concrete implementation for data fetching
public class DataFetcher : IDataFetcher
{
    public Task<IEnumerable<string>> FetchItemsAsync()
    {
        // Simulate fetching data
        return Task.FromResult(new List<string> { "Item 1", "Item 2", "Item 3" });
    }
}
        

MVVM and DI Integration

The MVVM pattern benefits greatly from DI. ViewModels often depend on various services (e.g., data access, API clients, navigation services, message dialog services). By injecting these services into the ViewModel's constructor, you achieve:

When navigating between pages, you'll typically inject the INavigationService or use the built-in INavigation interface to manage page transitions. If you need to pass data or configurations between ViewModels, DI can also be used to manage these shared services or state managers.

Conclusion

Embracing Dependency Injection in your .NET MAUI MVVM applications is a best practice that leads to more robust, testable, and maintainable code. By leveraging the built-in DI container and following established patterns like constructor injection, you can build sophisticated applications with greater ease and confidence.