Files
pos-system/microservices/.agent/skills/maui-enterprise-architect/guidelines/di-setup.md
Ho Ngoc Hai 76d75c753b Migrate
2026-05-23 18:37:02 +07:00

8.8 KiB

Dependency Injection Setup / Cấu Hình DI

Hướng dẫn chi tiết về Dependency Injection trong .NET MAUI với MauiProgram.cs.

Core Concepts / Khái Niệm Cốt Lõi

  1. Built-in DI: MAUI sử dụng Microsoft.Extensions.DependencyInjection
  2. Service Lifetimes: Singleton, Scoped, Transient
  3. Constructor Injection: Inject dependencies qua constructor

MauiProgram.cs Structure

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        
        builder
            .UseMauiApp<App>()
            .UseMauiCommunityToolkit()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("Inter-Regular.ttf", "InterRegular");
                fonts.AddFont("Inter-SemiBold.ttf", "InterSemiBold");
                fonts.AddFont("Inter-Bold.ttf", "InterBold");
            });

        // Register services
        RegisterServices(builder.Services);
        
        // Register ViewModels
        RegisterViewModels(builder.Services);
        
        // Register Views
        RegisterViews(builder.Services);

#if DEBUG
        builder.Logging.AddDebug();
#endif

        return builder.Build();
    }
    
    private static void RegisterServices(IServiceCollection services)
    {
        // HTTP Client
        services.AddHttpClient<IApiClient, ApiClient>(client =>
        {
            client.BaseAddress = new Uri("https://api.example.com/");
            client.Timeout = TimeSpan.FromSeconds(30);
        });
        
        // Singleton: Shared state, expensive to create
        services.AddSingleton<ISettingsService, SettingsService>();
        services.AddSingleton<IAuthService, AuthService>();
        services.AddSingleton<INavigationService, NavigationService>();
        services.AddSingleton<IConnectivityService, ConnectivityService>();
        
        // Transient: Stateless, lightweight
        services.AddTransient<IProductService, ProductService>();
        services.AddTransient<IOrderService, OrderService>();
    }
    
    private static void RegisterViewModels(IServiceCollection services)
    {
        // Transient: New instance per navigation
        services.AddTransient<MainViewModel>();
        services.AddTransient<ProductListViewModel>();
        services.AddTransient<ProductDetailViewModel>();
        services.AddTransient<CartViewModel>();
        services.AddTransient<CheckoutViewModel>();
        services.AddTransient<SettingsViewModel>();
    }
    
    private static void RegisterViews(IServiceCollection services)
    {
        // Transient: Match ViewModel lifecycle
        services.AddTransient<MainPage>();
        services.AddTransient<ProductListPage>();
        services.AddTransient<ProductDetailPage>();
        services.AddTransient<CartPage>();
        services.AddTransient<CheckoutPage>();
        services.AddTransient<SettingsPage>();
    }
}

Service Lifetimes

When to Use Each Lifetime

Lifetime When to Use Examples
Singleton Shared state across app AuthService, SettingsService, Cache
Scoped Per-request (rarely used in MAUI) DbContext in Blazor Hybrid
Transient Stateless, new each time ViewModels, Pages, Repositories

Singleton Services

// ✅ Singleton: Giữ state toàn cục
public interface IAuthService
{
    User? CurrentUser { get; }
    bool IsAuthenticated { get; }
    Task<bool> LoginAsync(string email, string password);
    Task LogoutAsync();
}

public class AuthService : IAuthService
{
    private User? _currentUser;
    
    public User? CurrentUser => _currentUser;
    public bool IsAuthenticated => _currentUser != null;
    
    public async Task<bool> LoginAsync(string email, string password)
    {
        _currentUser = await _apiClient.LoginAsync(email, password);
        return _currentUser != null;
    }
    
    public Task LogoutAsync()
    {
        _currentUser = null;
        return Task.CompletedTask;
    }
}

// Registration
services.AddSingleton<IAuthService, AuthService>();

Transient ViewModels

// ✅ Transient: Mỗi lần navigate tạo instance mới
services.AddTransient<ProductDetailViewModel>();

// ❌ SAI: Singleton ViewModel sẽ giữ data cũ
services.AddSingleton<ProductDetailViewModel>(); // DON'T!

View-ViewModel Injection

Constructor Injection Pattern

// View nhận ViewModel qua constructor
public partial class ProductListPage : ContentPage
{
    public ProductListPage(ProductListViewModel viewModel)
    {
        InitializeComponent();
        BindingContext = viewModel;
    }
}

// ViewModel nhận Services qua constructor
public partial class ProductListViewModel : ObservableObject
{
    private readonly IProductService _productService;
    private readonly INavigationService _navigation;
    
    public ProductListViewModel(
        IProductService productService,
        INavigationService navigation)
    {
        _productService = productService;
        _navigation = navigation;
    }
}

Shell Navigation with DI

// AppShell.xaml - ContentTemplate cho lazy loading
<ShellContent Title="Products"
              ContentTemplate="{DataTemplate views:ProductListPage}" />
// AppShell.xaml.cs - Manual resolution nếu cần
public partial class AppShell : Shell
{
    public AppShell(IServiceProvider serviceProvider)
    {
        InitializeComponent();
        
        // Optional: Resolve services if Shell needs them
        var authService = serviceProvider.GetRequiredService<IAuthService>();
    }
}

Platform-Specific Services

// Interface
public interface IDeviceService
{
    string GetDeviceId();
    string GetPlatformName();
}

// Shared partial class
public partial class DeviceService : IDeviceService
{
    public partial string GetDeviceId();
    
    public string GetPlatformName()
    {
        return DeviceInfo.Platform.ToString();
    }
}

// Platforms/Android/Services/DeviceService.cs
public partial class DeviceService
{
    public partial string GetDeviceId()
    {
        return Android.Provider.Settings.Secure.GetString(
            Android.App.Application.Context.ContentResolver,
            Android.Provider.Settings.Secure.AndroidId) ?? "unknown";
    }
}

// Platforms/iOS/Services/DeviceService.cs
public partial class DeviceService
{
    public partial string GetDeviceId()
    {
        return UIKit.UIDevice.CurrentDevice.IdentifierForVendor?.ToString() 
            ?? "unknown";
    }
}

// Registration
services.AddSingleton<IDeviceService, DeviceService>();

HttpClient Configuration

// Named HttpClient với retry policy
services.AddHttpClient<IApiClient, ApiClient>(client =>
{
    client.BaseAddress = new Uri("https://api.example.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
#if ANDROID
    return new Xamarin.Android.Net.AndroidMessageHandler
    {
        ServerCertificateCustomValidationCallback = (_, _, _, _) => true // Dev only!
    };
#else
    return new HttpClientHandler();
#endif
})
.AddTransientHttpErrorPolicy(policy => 
    policy.WaitAndRetryAsync(3, retryAttempt => 
        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))));

Common Mistakes / Lỗi Thường Gặp

1. Singleton ViewModel (State Leaking)

// ❌ SAI: Product từ lần navigate trước vẫn còn
services.AddSingleton<ProductDetailViewModel>();

// ✅ ĐÚNG: Fresh instance mỗi lần
services.AddTransient<ProductDetailViewModel>();

2. Missing View Registration

// ❌ SAI: Shell không thể resolve page
// Exception: Unable to resolve ProductListPage

// ✅ ĐÚNG: Đăng ký cả View và ViewModel
services.AddTransient<ProductListViewModel>();
services.AddTransient<ProductListPage>();

3. Circular Dependencies

// ❌ SAI: ServiceA → ServiceB → ServiceA
public class ServiceA
{
    public ServiceA(ServiceB b) { } // ServiceB cần ServiceA
}

// ✅ ĐÚNG: Dùng Lazy<T> hoặc refactor
public class ServiceA
{
    private readonly Lazy<ServiceB> _serviceB;
    
    public ServiceA(Lazy<ServiceB> serviceB)
    {
        _serviceB = serviceB;
    }
}

Best Practices Checklist

  • Views và ViewModels đều đăng ký là Transient
  • Services giữ state toàn cục đăng ký là Singleton
  • Stateless services đăng ký là Transient
  • Inject interfaces, không inject concrete classes
  • Sử dụng IHttpClientFactory cho HttpClient
  • Platform-specific code dùng Partial Classes

Resources