使用GetRequiredService获取ViewModel时如何传入自定义参数并保留依赖注入
当前使用 Microsoft.Extensions.DependencyInjection 5.0.1 版本,核心需求为:从DI容器解析带有自动注入依赖的ViewModel实例,同时支持传入自定义构造参数。
具体场景:
ThemeManagementPage构造时会接收到外部传入的int类型参数param1ThemeManagementViewModel构造时需要自动从DI容器注入IResourceService依赖,同时接收param1参数- 现有直接通过
Startup.ServiceProvider.GetRequiredService<ThemeManagementViewModel>()获取实例的方式,无法传入自定义的param1参数。
页面代码
public class ThemeManagementPage : HeadingView { private readonly ThemeManagementViewModel _vm; public ThemeManagementPage(int param1) { // 此处需要将param1传递给ViewModel,同时让DI自动注入IResourceService依赖 BindingContext = _vm = Startup.ServiceProvider.GetRequiredService<ThemeManagementViewModel>(); } } public partial class ThemeManagementViewModel : BaseViewModel { private readonly IResourceService _resourceService; public ThemeManagementViewModel(IResourceService resourceService, int param1) { _resourceService = resourceService; var x = param1; } }
DI注册代码
public partial class ResourceService : IResourceService { private IDatabaseService _databaseService; public ResourceService(IDatabaseService databaseService) { _databaseService = databaseService; } } public interface IResourceService { void SetResourceColors(); } public static class Startup { public static IServiceProvider ServiceProvider { get; set; } public static IServiceProvider Init() { var serviceProvider = new ServiceCollection().ConfigureServices() .BuildServiceProvider(); ServiceProvider = serviceProvider; return serviceProvider; } } public static class DependencyInjectionContainer { public static IServiceCollection ConfigureServices(this IServiceCollection services) { services.AddSingleton<IDatabaseService, DatabaseService>(); services.AddSingleton<IResourceService, ResourceService>(); services.AddTransient<ThemeManagementViewModel>(); return services; } }
方案1:使用内置 ActivatorUtilities 解析(最小改动,无注册逻辑调整)
DI框架从3.0版本开始就内置了 ActivatorUtilities 工具,专门解决混合参数的实例创建场景:构造函数中的参数,能从DI容器解析的自动按生命周期取出,剩余匹配不上的参数用手动传入的实参匹配,完全不需要修改现有的DI注册逻辑。该方法随 Microsoft.Extensions.DependencyInjection 5.0 版本内置,不需要额外安装NuGet包。
修改 ThemeManagementPage 的构造函数逻辑,将原来直接获取服务的代码替换为 ActivatorUtilities.CreateInstance 调用:
public class ThemeManagementPage : HeadingView { private readonly ThemeManagementViewModel _vm; public ThemeManagementPage(int param1) { // 自动从DI容器解析IResourceService依赖,手动传入param1作为自定义构造参数 BindingContext = _vm = ActivatorUtilities.CreateInstance<ThemeManagementViewModel>( Startup.ServiceProvider, param1 ); } }
方案优势:
- 原有
services.AddTransient<ThemeManagementViewModel>();注册逻辑完全保留,不需要调整 - 所有已注册的依赖(包括
IResourceService本身依赖的IDatabaseService)都会按原有生命周期正确解析,不会出现依赖脱节的问题 - 后续如果
ThemeManagementViewModel新增了需要DI自动注入的构造参数,不需要修改任何调用逻辑,ActivatorUtilities会自动从容器解析新增的依赖。
方案2:工厂委托注册(适合多参数的复杂场景)
如果后续有大量ViewModel需要传递自定义构造参数,可以将ViewModel的注册逻辑替换为工厂委托形式,在注册时预绑定需要自动注入的依赖,返回一个可以接收自定义参数的委托。
修改DI注册逻辑:
public static IServiceCollection ConfigureServices(this IServiceCollection services) { services.AddSingleton<IDatabaseService, DatabaseService>(); services.AddSingleton<IResourceService, ResourceService>(); // 注册工厂委托:自动获取需要注入的IResourceService,返回接收param1的构造委托 services.AddTransient<Func<int, ThemeManagementViewModel>>(serviceProvider => { var resourceService = serviceProvider.GetRequiredService<IResourceService>(); return (param1) => new ThemeManagementViewModel(resourceService, param1); }); return services; }
在页面中获取实例时,先拿到工厂委托,再传入自定义参数:
public ThemeManagementPage(int param1) { var vmFactory = Startup.ServiceProvider.GetRequiredService<Func<int, ThemeManagementViewModel>>(); BindingContext = _vm = vmFactory(param1); }
方案注意点:
这种方式需要手动维护ViewModel构造函数的参数映射,后续ViewModel新增需要注入的依赖时,需要同步修改工厂委托的逻辑,容易出现遗漏。更适合参数结构稳定、有统一管理需求的场景。
不要使用硬编码的手动构造方式,比如直接写 new ThemeManagementViewModel(serviceProvider.GetRequiredService<IResourceService>(), param1) 来创建实例,这种写法在ViewModel后续新增构造依赖时,需要同步修改所有手动创建的位置,维护成本极高。
内容的提问来源于stack exchange,提问作者Richard A

