You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 17:40:17