.NET 6.0命名服务工厂实现:Blazor同接口多配置注册问题
实现Blazor客户端的命名式服务工厂
核心思路
无需新增接口或实现类,通过注册带配置逻辑的实例创建委托,用字符串名称关联不同配置,工厂根据名称匹配对应的委托,创建并返回已配置好的实例。
步骤1:定义通用工厂接口与实现
先实现通用的命名服务工厂,用于存储不同名称对应的实例创建逻辑:
// 通用工厂接口,支持按名称创建指定类型实例 public interface INamedServiceFactory<TService> { TService Create(string name); } // 通用工厂实现类 public class NamedServiceFactory<TService> : INamedServiceFactory<TService> { private readonly Dictionary<string, Func<IServiceProvider, TService>> _factoryMap = new(); public IServiceProvider? ServiceProvider { get; set; } // 注册名称对应的实例创建逻辑 public void Register(string name, Func<IServiceProvider, TService> factoryFunc) { if (_factoryMap.ContainsKey(name)) throw new InvalidOperationException($"服务名称「{name}」已注册"); _factoryMap.Add(name, factoryFunc); } // 根据名称创建并返回配置好的实例 public TService Create(string name) { if (!_factoryMap.TryGetValue(name, out var factoryFunc) || ServiceProvider == null) throw new KeyNotFoundException($"未找到名称为「{name}」的服务配置"); return factoryFunc(ServiceProvider); } }
步骤2:编写服务注册扩展方法
实现你期望的AddFoodFactory风格扩展方法,本质是将每个名称的配置逻辑注册到通用工厂:
public static class NamedServiceFactoryExtensions { // 通用注册方法:支持接口-实现类绑定,以及实例配置委托 public static IServiceCollection AddNamedServiceFactory<TInterface, TImplementation>( this IServiceCollection services, string name, Action<TImplementation> configureAction) where TImplementation : class, TInterface { // 获取或注册对应的NamedServiceFactory实例(单例) var factory = services.GetOrAddFactory<TInterface>(); // 注册该名称对应的实例创建逻辑:先实例化,再执行配置 factory.Register(name, sp => { // 自动注入实现类的构造函数依赖 var instance = ActivatorUtilities.CreateInstance<TImplementation>(sp); // 执行配置委托,设置差异化属性 configureAction(instance); return instance; }); return services; } // 辅助方法:获取已注册的工厂,不存在则创建并注册为单例 private static NamedServiceFactory<TService> GetOrAddFactory<TService>(this IServiceCollection services) { var existingFactory = services.FirstOrDefault(sd => sd.ServiceType == typeof(INamedServiceFactory<TService>) && sd.ImplementationType == typeof(NamedServiceFactory<TService>)); if (existingFactory != null) { return (NamedServiceFactory<TService>)existingFactory.ImplementationInstance!; } else { var factory = new NamedServiceFactory<TService>(); // 注册工厂为单例 services.AddSingleton<INamedServiceFactory<TService>>(factory); // 注入服务提供者,确保工厂能解析依赖 services.AddSingleton(sp => { factory.ServiceProvider = sp; return factory; }); return factory; } } }
步骤3:适配业务场景(FoodService)
假设你的业务服务定义如下:
public interface IFoodService { string GetRoom(); } public class FoodService : IFoodService { // 可配置的差异化属性 public string StorageRoom { get; set; } = string.Empty; public string GetRoom() { return StorageRoom; } }
注册服务(Program.cs)
完全按照你期望的方式注册:
builder.Services.AddNamedServiceFactory<IFoodService, FoodService>("Citrus", service => { service.StorageRoom = "A1"; }); builder.Services.AddNamedServiceFactory<IFoodService, FoodService>("Starch", service => { service.StorageRoom = "A2"; });
步骤4:在Blazor组件中使用
在组件中注入工厂,按名称创建实例:
@inject INamedServiceFactory<IFoodService> FoodServiceFactory @code { private IFoodService? CitrusService; private IFoodService? StarchService; protected override void OnInitialized() { CitrusService = FoodServiceFactory.Create("Citrus"); StarchService = FoodServiceFactory.Create("Starch"); // 测试输出:分别打印"A1"和"A2" Console.WriteLine(CitrusService.GetRoom()); Console.WriteLine(StarchService.GetRoom()); } }
与参考实现的核心差异
你参考的Tim Corey实现是针对不同实现类的工厂,而本方案是针对同一实现类的不同配置实例:
- 无需为每个配置新增实现类,仅通过
Action委托完成属性配置 - 工厂存储的是实例创建+配置的委托,而非已实例化的对象集合
- 自动支持实现类的构造函数依赖注入(比如FoodService需要其他服务时,ActivatorUtilities会自动解析)
内容的提问来源于stack exchange,提问作者DONAR144-Release
相关产品推荐
相关产品推荐

