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.
.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.
Services are typically registered in the MauiProgram.cs
file within the CreateMauiApp()
method. You'll use the builder.Services
collection to register your types.
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();
}
}
You can resolve dependencies in several ways:
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
}
}
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();
// 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" });
}
}
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.
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.