【问题标题】:How to configure dependency injection container with Func<T, Result>?如何使用 Func<T, Result> 配置依赖注入容器?
【发布时间】:2022-01-02 12:03:18
【问题描述】:

BusinessAction 用于表示用户可以执行的操作。每个操作都与特定实体相关,例如,如果该实体是 Order,则业务操作可能是 CancelOrder、IssueRefund 等。

public abstract class BusinessAction<T>
{
    public Guid Id { get; init; }
    public Func<T, bool> IsEnabledFor { get; init; }
}

public class CancelOrderAction : BusinessAction<Order>
{
    public CancelOrderAction ()
    {
        Id = Guid.Parse("0e07d05c-6298-4c56-87d7-d2ca339fee1e");
        IsEnabledFor = o => o.Status == OrderStatus.Active;
    }
}

然后我需要对与特定类型相关的所有操作进行分组。

public interface IActionRegistry
{
    Task<IEnumerable<Guid>> GetEnabledActionIdsForAsync(Guid entityId);
}

public class ActionRegistry<T> : IActionRegistry
    where T : BaseEntity
{
    private readonly IEnumerable<BusinessAction<T>> _actions;
    private readonly IRepository<T> _repository;

    public ActionRegistry(IEnumerable<BusinessAction<T>> actions, IRepository<T> repository)
    {
        _actions = actions;
        _repository = repository;
    }

    public async Task<IEnumerable<Guid>> GetEnabledActionIdsForAsync(Guid entityId)
    {
        var entity = await _repository.FindByIdAsync(entityId);

        return entity == null
            ? Enumerable.Empty<Guid>()
            : _actions.Where(a => a.IsEnabledFor(entity)).Select(a => a.Id);
    }
}

最后,有一个 API 端点接收实体类型(一些后来映射到真实 .NET 类型的枚举)和实体 ID。 API 端点负责返回为实体的当前状态启用的操作 ID。

public class RequestHandler : IRequestHandler<Request, IEnumerable<Guid>>>
{
    private readonly Func<Type, IActionRegistry> _registryFactory;

    public RequestHandler(Func<Type, IActionRegistry> registryFactory)
    {
        _registryFactory = registryFactory;
    }

    public async Task<IEnumerable<Guid>> Handle(Request request, CancellationToken cancellationToken)
    {
        var type = request.EntityType.GetDotnetType();
        var actionRegistry = _registryFactory(type);
        var enabledActions = await actionRegistry.GetEnabledActionIdsForAsync(request.EntityId);

        return enabledActions;
    }
}

问题是:如何在 ASP.NET 中配置依赖注入容器(使用默认选项或 Autofac),以便解析 Func

ActionRegistry&lt;T&gt;中的参数我想我可以做到:

builder.RegisterAssemblyTypes().AsClosedTypesOf(typeof(BusinessAction<>));

builder.RegisterGeneric(typeof(Repository<>))
       .As(typeof(IRepository<>))
       .InstancePerLifetimeScope();

但是,我如何配置Func&lt;Type, IActionRegistry&gt;,以便能够自动连接OrderActionRegistry&lt;Order&gt; 的请求?有没有办法做到这一点,或者我需要通过编写一些基于类型的 switch 语句来手动配置工厂(以及看起来如何)?

有没有更好的方法来实现我在这里需要的东西?最终目标是,一旦我有了运行时类型,我就可以获得与该类型相关的业务操作列表以及存储库(以便我可以从数据库中获取实体)。

【问题讨论】:

    标签: c# .net dependency-injection inversion-of-control autofac


    【解决方案1】:

    您尝试做的事情是可能的,但这不是一件常见的事情,也不是您可以开箱即用的魔法。您必须编写代码来实现它。

    在我开始之前...从未来的角度来看,如果您的重现要小得多,您可能会更快地获得帮助并更多地关注您的问题。整个BusinessAction&lt;T&gt; 并不是真正需要的;不需要RequestHandler...老实说,您只需要重现您正在做的事情就是:

    public interface IActionRegistry
    {
    }
    
    public class ActionRegistry<T> : IActionRegistry
    {
    }
    

    如果其他内容与问题相关,请务必将其包含在内...但在这种情况下,它不是,因此在此处添加它只会使问题更难阅读和回答。我知道我个人有时会跳过有很多额外内容的问题,因为一天只有这么多小时,你知道吗?

    无论如何,这就是你的做法,以工作示例形式:

    var builder = new ContainerBuilder();
    
    // Register the action registry generic but not AS the interface.
    // You can't register an open generic as a non-generic interface.
    builder.RegisterGeneric(typeof(ActionRegistry<>));
    
    // Manually build the factory method. Going from reflection
    // System.Type to a generic ActionRegistry<Type> is not common and
    // not directly supported.
    builder.Register((context, parameters) => {
        // Capture the lifetime scope or you'll get an exception about
        // the resolve operation already being over.
        var scope = context.Resolve<ILifetimeScope>();
    
        // Here's the factory method. You can add whatever additional
        // enhancements you need, like better error handling.
        return (Type type) => {
            var closedGeneric = typeof(ActionRegistry<>).MakeGenericType(type);
            return scope.Resolve(closedGeneric) as IActionRegistry;
        };
    });
    
    var container = builder.Build();
    
    // Now you can resolve it and use it.
    var factory = container.Resolve<Func<Type, IActionRegistry>>();
    var instance = factory(typeof(DivideByZeroException));
    Assert.Equal("ActionRegistry`1", instance.GetType().Name);
    Assert.Equal("DivideByZeroException", instance.GetType().GenericTypeArguments[0].Name);
    

    【讨论】:

    • 谢谢你,特拉维斯。您对我的问题是正确的,我应该将其最小化并保持简单,但出于某种原因,我认为显示 ActionRegistry 的构造函数很重要。无论如何,下次我会记住这一点。
    猜你喜欢
    • 1970-01-01
    • 2018-12-30
    • 1970-01-01
    • 2018-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-30
    相关资源
    最近更新 更多