.NET MAUI内置DI容器如何实现分模块延迟注册服务与ViewModel
.NET MAUI内置DI容器运行时按需批量注册服务实现方案
.NET MAUI内置依赖注入基于Microsoft.Extensions.DependencyInjection实现,原生默认行为是IServiceCollection构建为IServiceProvider后不支持直接修改注册项,针对模块化分阶段加载场景,可以通过自定义模块注册管理器的方式实现运行时追加注册,不需要引入第三方DI容器。
分步实现逻辑
1. 启动阶段仅注册启动模块依赖
在MauiProgram.cs中初始化时,只加载启动页必需的服务,不注册其他业务模块内容:
public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); }); // 仅注册启动模块相关视图、视图模型、服务 builder.Services.AddTransient<SplashPage>(); builder.Services.AddTransient<SplashViewModel>(); builder.Services.AddSingleton<ISplashService, SplashService>(); var app = builder.Build(); // 初始化全局模块注册器 var registrar = new ModuleRegistrar(builder.Services, app.Services); // 注入注册器实例供全局调用 builder.Services.AddSingleton<IModuleRegistrar>(registrar); return app; }
2. 定义模块注册抽象与核心管理器
首先定义模块注册通用接口,所有业务模块都实现该接口完成自身注册逻辑:
public interface IServiceModule { bool IsRegistered { get; set; } void Register(IServiceCollection services); } public interface IModuleRegistrar { void RegisterModule(IServiceModule module); T GetService<T>(); object GetService(Type serviceType); }
实现核心注册管理器,处理追加注册、容器重建、单例迁移逻辑,避免重复注册、单例重复初始化问题:
public class ModuleRegistrar : IModuleRegistrar { private IServiceCollection _serviceCollection; private IServiceProvider _serviceProvider; private readonly object _lockObj = new(); public ModuleRegistrar(IServiceCollection services, IServiceProvider initialProvider) { _serviceCollection = services; _serviceProvider = initialProvider; } public void RegisterModule(IServiceModule module) { lock (_lockObj) { // 已注册模块直接跳过,避免重复执行 if (module.IsRegistered) return; // 调用模块自身注册逻辑,向服务集合追加依赖 module.Register(_serviceCollection); // 迁移旧容器中已生成的单例实例,避免单例重复创建、状态丢失 var existingSingletons = _serviceProvider.GetServices<object>() .Where(s => !s.GetType().IsAssignableTo(typeof(IDisposable))) .ToList(); foreach (var singleton in existingSingletons) { var desc = _serviceCollection.FirstOrDefault(d => d.ServiceType == singleton.GetType() && d.Lifetime == ServiceLifetime.Singleton); if (desc != null) { _serviceCollection.Remove(desc); _serviceCollection.AddSingleton(desc.ServiceType, singleton); } } // 重建服务提供器 _serviceProvider = _serviceCollection.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = Debugger.IsAttached, ValidateOnBuild = Debugger.IsAttached }); module.IsRegistered = true; } } public T GetService<T>() => (T)GetService(typeof(T)); public object GetService(Type serviceType) { lock (_lockObj) { return _serviceProvider.GetService(serviceType); } } }
3. 按模块编写注册逻辑
每个独立业务模块单独实现IServiceModule接口,支持手动注册或程序集反射批量注册,以聊天模块为例:
public class ChatModule : IServiceModule { public bool IsRegistered { get; set; } public void Register(IServiceCollection services) { // 手动注册核心依赖 services.AddSingleton<IChatService, ChatService>(); services.AddTransient<ChatMessageRepository>(); // 反射批量注册当前模块程序集下的所有视图、视图模型 var assembly = typeof(ChatModule).Assembly; // 注册所有Page页面 foreach (var pageType in assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(Page)) && !t.IsAbstract)) { services.AddTransient(pageType); // Shell场景下同步注册路由 Routing.RegisterRoute(pageType.Name, pageType); } // 注册所有ViewModel foreach (var vmType in assembly.GetTypes().Where(t => t.Name.EndsWith("ViewModel") && !t.IsAbstract)) { services.AddTransient(vmType); } } }
4. 业务触发时执行按需注册
在用户触发对应功能入口时,先调用模块注册逻辑,再执行导航或业务操作,注册过程仅在第一次触发时执行:
public class SplashViewModel { private readonly IModuleRegistrar _moduleRegistrar; public SplashViewModel(IModuleRegistrar moduleRegistrar) { _moduleRegistrar = moduleRegistrar; EnterChatCommand = new Command(OnEnterChat); } public ICommand EnterChatCommand { get; } private async void OnEnterChat() { // 首次进入聊天模块时才执行注册,后续进入直接跳过 _moduleRegistrar.RegisterModule(new ChatModule()); // 注册完成后解析页面实例导航 var chatPage = _moduleRegistrar.GetService<ChatPage>(); await Application.Current.MainPage.Navigation.PushAsync(chatPage); } }
注意事项
- 注册逻辑加了线程锁,避免多线程场景下容器状态不一致
- 单例迁移逻辑保证启动阶段初始化的全局服务不会因为容器重建丢失状态
- 单个模块反射扫描+注册的耗时通常在10ms以内,用户无感知,完全避免启动阶段全量注册的性能损耗
- 每个模块仅在首次触发时执行一次注册,整个应用生命周期内不会重复执行
- 如果使用Shell导航,在模块注册逻辑中同步注册路由即可,原有导航逻辑不需要修改
内容的提问来源于stack exchange,提问作者Pravin Patil
相关产品推荐
相关产品推荐

