8.8 KiB
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
- Built-in DI: MAUI sử dụng
Microsoft.Extensions.DependencyInjection - Service Lifetimes: Singleton, Scoped, Transient
- 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
IHttpClientFactorycho HttpClient - Platform-specific code dùng Partial Classes