# 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 ```csharp public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp() .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(client => { client.BaseAddress = new Uri("https://api.example.com/"); client.Timeout = TimeSpan.FromSeconds(30); }); // Singleton: Shared state, expensive to create services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); // Transient: Stateless, lightweight services.AddTransient(); services.AddTransient(); } private static void RegisterViewModels(IServiceCollection services) { // Transient: New instance per navigation services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); } private static void RegisterViews(IServiceCollection services) { // Transient: Match ViewModel lifecycle services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); } } ``` --- ## 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 ```csharp // ✅ Singleton: Giữ state toàn cục public interface IAuthService { User? CurrentUser { get; } bool IsAuthenticated { get; } Task 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 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(); ``` ### Transient ViewModels ```csharp // ✅ Transient: Mỗi lần navigate tạo instance mới services.AddTransient(); // ❌ SAI: Singleton ViewModel sẽ giữ data cũ services.AddSingleton(); // DON'T! ``` --- ## View-ViewModel Injection ### Constructor Injection Pattern ```csharp // 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 ```csharp // AppShell.xaml - ContentTemplate cho lazy loading ``` ```csharp // 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(); } } ``` --- ## Platform-Specific Services ```csharp // 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(); ``` --- ## HttpClient Configuration ```csharp // Named HttpClient với retry policy services.AddHttpClient(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) ```csharp // ❌ SAI: Product từ lần navigate trước vẫn còn services.AddSingleton(); // ✅ ĐÚNG: Fresh instance mỗi lần services.AddTransient(); ``` ### 2. Missing View Registration ```csharp // ❌ SAI: Shell không thể resolve page // Exception: Unable to resolve ProductListPage // ✅ ĐÚNG: Đăng ký cả View và ViewModel services.AddTransient(); services.AddTransient(); ``` ### 3. Circular Dependencies ```csharp // ❌ SAI: ServiceA → ServiceB → ServiceA public class ServiceA { public ServiceA(ServiceB b) { } // ServiceB cần ServiceA } // ✅ ĐÚNG: Dùng Lazy hoặc refactor public class ServiceA { private readonly Lazy _serviceB; public ServiceA(Lazy 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 - [.NET MAUI Dependency Injection](https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/dependency-injection) - [MVVM Rules](./mvvm-rules.md) - [Shell Navigation](./shell-nav.md)