.NET Core中基于特性与命名服务的程序集扫描实现问题
解决方案与路径建议
方案一:用Autofac实现命名服务注册
1. 定义特性与处理器接口
先明确自定义特性和基础处理器接口:
[AttributeUsage(AttributeTargets.Class, Inherited = false)] public class AutoWireQueryNameAttribute : Attribute { public string QueryName { get; } public AutoWireQueryNameAttribute(string queryName) => QueryName = queryName; } public interface IRequestHandler<TRequest, TResponse> { Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken); }
2. 配置Autofac扫描注册
替换原生DI为Autofac,扫描带特性的处理器并按QueryName注册为命名服务:
var builder = WebApplication.CreateBuilder(args); // 切换为Autofac容器 builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder => { // 扫描目标程序集(替换为你的处理器所在程序集标记类) containerBuilder.RegisterAssemblyTypes(typeof(YourHandlerAssemblyMarker).Assembly) .Where(t => t.GetCustomAttribute<AutoWireQueryNameAttribute>() != null) .AsImplementedInterfaces() .Named<IRequestHandler>(t => t.GetCustomAttribute<AutoWireQueryNameAttribute>()!.QueryName); }); // 其他服务注册逻辑... var app = builder.Build(); // 后续应用配置...
3. 运行时解析处理器
注入Autofac的IComponentContext实现调度:
public class QueryDispatcher { private readonly IComponentContext _componentContext; public QueryDispatcher(IComponentContext componentContext) => _componentContext = componentContext; public async Task<TResponse> Dispatch<TRequest, TResponse>(string queryName, TRequest request, CancellationToken cancellationToken) { var handler = _componentContext.ResolveNamed<IRequestHandler<TRequest, TResponse>>(queryName); return await handler.Handle(request, cancellationToken); } } // 注册调度器 builder.Services.AddTransient<QueryDispatcher>();
方案二:原生DI结合自定义扫描逻辑
不想引入第三方容器的话,自己实现扫描和字典缓存:
1. 扫描处理器并注册
var builder = WebApplication.CreateBuilder(args); // 扫描目标程序集获取带特性的处理器类型 var handlerTypes = typeof(YourHandlerAssemblyMarker).Assembly.GetTypes() .Where(t => t.GetCustomAttribute<AutoWireQueryNameAttribute>() != null && !t.IsAbstract) .ToList(); // 构建QueryName到处理器类型的映射字典 var handlerMap = new Dictionary<string, Type>(); foreach (var handlerType in handlerTypes) { var attr = handlerType.GetCustomAttribute<AutoWireQueryNameAttribute>()!; handlerMap[attr.QueryName] = handlerType; // 先把处理器注册为瞬态服务 builder.Services.AddTransient(handlerType); } // 将映射字典注册为单例 builder.Services.AddSingleton(handlerMap);
2. 实现自定义调度器
通过IServiceProvider和字典解析处理器:
public class QueryDispatcher { private readonly IServiceProvider _serviceProvider; private readonly Dictionary<string, Type> _handlerMap; public QueryDispatcher(IServiceProvider serviceProvider, Dictionary<string, Type> handlerMap) { _serviceProvider = serviceProvider; _handlerMap = handlerMap; } public async Task<TResponse> Dispatch<TRequest, TResponse>(string queryName, TRequest request, CancellationToken cancellationToken) { if (!_handlerMap.TryGetValue(queryName, out var handlerType)) throw new KeyNotFoundException($"No handler found for query name: {queryName}"); var handler = (IRequestHandler<TRequest, TResponse>)_serviceProvider.GetService(handlerType)!; if (handler == null) throw new InvalidOperationException($"Failed to resolve handler type: {handlerType.FullName}"); return await handler.Handle(request, cancellationToken); } } builder.Services.AddTransient<QueryDispatcher>();
方案三:用LightInject实现命名注册
LightInject同样支持命名服务,配置方式如下:
1. 切换容器并注册
var builder = WebApplication.CreateBuilder(args); // 替换为LightInject容器 var container = new ServiceContainer(); builder.Host.UseServiceProviderFactory(new LightInjectServiceProviderFactory(container)); // 扫描并注册命名服务 container.RegisterAssembly(typeof(YourHandlerAssemblyMarker).Assembly, (serviceType, implementingType) => { var attr = implementingType.GetCustomAttribute<AutoWireQueryNameAttribute>(); if (attr != null && serviceType.IsAssignableTo(typeof(IRequestHandler))) { return new[] { new ServiceRegistration { ServiceType = serviceType, ImplementingType = implementingType, Name = attr.QueryName } }; } return null; }); // 其他服务注册... var app = builder.Build();
2. 解析命名服务
注入IServiceFactory实现调度:
public class QueryDispatcher { private readonly IServiceFactory _serviceFactory; public QueryDispatcher(IServiceFactory serviceFactory) => _serviceFactory = serviceFactory; public async Task<TResponse> Dispatch<TRequest, TResponse>(string queryName, TRequest request, CancellationToken cancellationToken) { var handler = _serviceFactory.GetInstance<IRequestHandler<TRequest, TResponse>>(queryName); return await handler.Handle(request, cancellationToken); } } builder.Services.AddTransient<QueryDispatcher>();
额外提示
- 可以定义非泛型的
IRequestHandler基础接口,方便扫描和统一处理,减少泛型约束带来的限制。 - 扫描时添加重复
QueryName的校验逻辑,避免运行时冲突。 - 若DTO完全通用,可考虑用
object作为请求响应类型,但需额外添加类型安全检查。
内容的提问来源于stack exchange,提问作者Prev-I
相关产品推荐
相关产品推荐

