如何使用 Func<T, Result> 配置依赖注入容器?
How to configure dependency injection container with Func<T, Result>?
BusinessAction 用于表示可以由用户执行的操作。每个操作都与特定实体相关,例如,如果该实体是订单,则业务操作可以是取消订单、问题退款等。
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 端点负责为实体的当前状态启用的 return 操作 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<T>
中的参数我想我可以做到:
builder.RegisterAssemblyTypes().AsClosedTypesOf(typeof(BusinessAction<>));
builder.RegisterGeneric(typeof(Repository<>))
.As(typeof(IRepository<>))
.InstancePerLifetimeScope();
但是,我如何配置 Func<Type, IActionRegistry>
以便我能够自动连接 Order
与 ActionRegistry<Order>
的请求?有没有办法做到这一点,或者我需要通过编写一些基于类型的 switch 语句(以及它看起来如何)来手动配置工厂?
有没有更好的方法来实现我在这里需要的东西?最终目标是一旦我有了运行时类型,我就可以获得与该类型相关的业务操作列表以及存储库(以便我可以从数据库中获取实体)。
您尝试做的事情是可能的,但这不是一件常见的事情,也不是您可以开箱即用的魔法。您必须编写代码来实现它。
在我开始之前......从未来的角度来看,如果你的重现更小[=24=,你可能会更快地得到帮助,更多人关注你的问题]。整个 BusinessAction<T>
并不是真正需要的; 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);
BusinessAction 用于表示可以由用户执行的操作。每个操作都与特定实体相关,例如,如果该实体是订单,则业务操作可以是取消订单、问题退款等。
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 端点负责为实体的当前状态启用的 return 操作 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<T>
中的参数我想我可以做到:
builder.RegisterAssemblyTypes().AsClosedTypesOf(typeof(BusinessAction<>));
builder.RegisterGeneric(typeof(Repository<>))
.As(typeof(IRepository<>))
.InstancePerLifetimeScope();
但是,我如何配置 Func<Type, IActionRegistry>
以便我能够自动连接 Order
与 ActionRegistry<Order>
的请求?有没有办法做到这一点,或者我需要通过编写一些基于类型的 switch 语句(以及它看起来如何)来手动配置工厂?
有没有更好的方法来实现我在这里需要的东西?最终目标是一旦我有了运行时类型,我就可以获得与该类型相关的业务操作列表以及存储库(以便我可以从数据库中获取实体)。
您尝试做的事情是可能的,但这不是一件常见的事情,也不是您可以开箱即用的魔法。您必须编写代码来实现它。
在我开始之前......从未来的角度来看,如果你的重现更小[=24=,你可能会更快地得到帮助,更多人关注你的问题]。整个 BusinessAction<T>
并不是真正需要的; 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);