带简易喷油器
如果您碰巧使用Simple Injector执行 DI 任务,容器可以帮助解决这个问题。(如果您不使用 Simple Injector,请参阅下面的“使用其他 DI 框架”)
该功能在 Simple Injector 文档的Advanced Scenarios: Mixing collections of open-generic and non-generic components下进行了描述。
您需要对您的服务接口和实现稍作调整。
interface IEntityService<T>
{
void DoSomething(T entity);
}
class BaseEntityService<T> : IEntityService<T> where T : BaseEntity
{
public void DoSomething(T entity) => throw new NotImplementedException();
}
class ChildBEntityService<T> : IEntityService<T> where T : ChildBEntity
{
public void DoSomething(T entity) => throw new NotImplementedException();
}
这些服务现在是通用的,具有描述它们能够处理的最不具体的实体类型的类型约束。作为奖励,DoSomething
现在遵守里氏替换原则。由于服务实现提供了类型约束,IEntityService
接口不再需要一个。
将所有服务注册为一个开放泛型集合。Simple Injector 理解泛型类型约束。解析时,容器本质上会将集合过滤到仅满足类型约束的那些服务。
这是一个工作示例,以xUnit测试的形式呈现。
[Theory]
[InlineData(typeof(GrandChildAEntity), new[] { typeof(GrandChildAEntityService<GrandChildAEntity>), typeof(BaseEntityService<GrandChildAEntity>) })]
[InlineData(typeof(BaseEntity), new[] { typeof(BaseEntityService<BaseEntity>) })]
[InlineData(typeof(ChildBEntity), new[] { typeof(ChildBEntityService<ChildBEntity>), typeof(BaseEntityService<ChildBEntity>) })]
[InlineData(typeof(ChildAEntity), new[] { typeof(BaseEntityService<ChildAEntity>) })]
public void Test1(Type entityType, Type[] expectedServiceTypes)
{
var container = new Container();
// Services will be resolved in the order they were registered
container.Collection.Register(typeof(IEntityService<>), new[] {
typeof(ChildBEntityService<>),
typeof(GrandChildAEntityService<>),
typeof(BaseEntityService<>),
});
container.Verify();
var serviceType = typeof(IEntityService<>).MakeGenericType(entityType);
Assert.Equal(
expectedServiceTypes,
container.GetAllInstances(serviceType).Select(s => s.GetType())
);
}
与您的示例类似,您可以添加ChildAEntityService<T> : IEntityService<T> where T : ChildAEntity
并且UnusualEntityService<T> : IEntityService<T> where T : IUnusualEntity
一切正常...
[Theory]
[InlineData(typeof(GrandChildAEntity), new[] { typeof(UnusualEntityService<GrandChildAEntity>), typeof(ChildAEntityService<GrandChildAEntity>), typeof(GrandChildAEntityService<GrandChildAEntity>), typeof(BaseEntityService<GrandChildAEntity>) })]
[InlineData(typeof(BaseEntity), new[] { typeof(BaseEntityService<BaseEntity>) })]
[InlineData(typeof(ChildBEntity), new[] { typeof(UnusualEntityService<ChildBEntity>), typeof(ChildBEntityService<ChildBEntity>), typeof(BaseEntityService<ChildBEntity>) })]
[InlineData(typeof(ChildAEntity), new[] { typeof(ChildAEntityService<ChildAEntity>), typeof(BaseEntityService<ChildAEntity>) })]
public void Test2(Type entityType, Type[] expectedServiceTypes)
{
var container = new Container();
// Services will be resolved in the order they were registered
container.Collection.Register(typeof(IEntityService<>), new[] {
typeof(UnusualEntityService<>),
typeof(ChildAEntityService<>),
typeof(ChildBEntityService<>),
typeof(GrandChildAEntityService<>),
typeof(BaseEntityService<>),
});
container.Verify();
var serviceType = typeof(IEntityService<>).MakeGenericType(entityType);
Assert.Equal(
expectedServiceTypes,
container.GetAllInstances(serviceType).Select(s => s.GetType())
);
}
正如我之前提到的,这个例子是特定于 Simple Injector 的。并非所有容器都能够如此优雅地处理通用注册。例如,微软的 DI 容器类似的注册失败:
[Fact]
public void Test3()
{
var services = new ServiceCollection()
.AddTransient(typeof(IEntityService<>), typeof(BaseEntityService<>))
.AddTransient(typeof(IEntityService<>), typeof(GrandChildAEntityService<>))
.AddTransient(typeof(IEntityService<>), typeof(ChildBEntityService<>))
.BuildServiceProvider();
// Exception message: System.ArgumentException : GenericArguments[0], 'GrandChildBEntity', on 'GrandChildAEntityService`1[T]' violates the constraint of type 'T'.
Assert.Throws<ArgumentException>(
() => services.GetServices(typeof(IEntityService<ChildBEntity>))
);
}
与其他 DI 框架
我设计了一个可以与任何 DI 容器一起使用的替代解决方案。
这一次,我们从接口中删除了泛型类型定义。相反,该CanHandle()
方法将让调用者知道一个实例是否可以处理给定的实体。
interface IEntityService
{
// Indicates whether or not the instance is able to handle the entity.
bool CanHandle(object entity);
void DoSomething(object entity);
}
抽象基类可以处理大部分类型检查/转换样板:
abstract class GenericEntityService<T> : IEntityService
{
// Indicates that the service can handle an entity of typeof(T),
// or of a type that inherits from typeof(T).
public bool CanHandle(object entity)
=> entity != null && typeof(T).IsAssignableFrom(entity.GetType());
public void DoSomething(object entity)
{
// This could also throw an ArgumentException, although that
// would violate the Liskov Substitution Principle
if (!CanHandle(entity)) return;
DoSomethingImpl((T)entity);
}
// This is the method that will do the actual processing
protected abstract void DoSomethingImpl(T entity);
}
这意味着实际的服务实现可以非常简单,例如:
class BaseEntityService : GenericEntityService<BaseEntity>
{
protected override void DoSomethingImpl(BaseEntity entity) => throw new NotImplementedException();
}
class ChildBEntityService : GenericEntityService<ChildBEntity>
{
protected override void DoSomethingImpl(ChildBEntity entity) => throw new NotImplementedException();
}
要将它们从 DI 容器中取出,您需要一个友好的工厂:
class EntityServiceFactory
{
readonly IServiceProvider serviceProvider;
public EntityServiceFactory(IServiceProvider serviceProvider)
=> this.serviceProvider = serviceProvider;
public IEnumerable<IEntityService> GetServices(BaseEntity entity)
=> serviceProvider
.GetServices<IEntityService>()
.Where(s => s.CanHandle(entity));
}
最后,为了证明一切正常:
[Theory]
[InlineData(typeof(GrandChildAEntity), new[] { typeof(UnusualEntityService), typeof(ChildAEntityService), typeof(GrandChildAEntityService), typeof(BaseEntityService) })]
[InlineData(typeof(BaseEntity), new[] { typeof(BaseEntityService) })]
[InlineData(typeof(ChildBEntity), new[] { typeof(UnusualEntityService), typeof(ChildBEntityService), typeof(BaseEntityService) })]
[InlineData(typeof(ChildAEntity), new[] { typeof(ChildAEntityService), typeof(BaseEntityService) })]
public void Test4(Type entityType, Type[] expectedServiceTypes)
{
// Services appear to be resolved in reverse order of registration, but
// I'm not sure if this behavior is guaranteed.
var serviceProvider = new ServiceCollection()
.AddTransient<IEntityService, UnusualEntityService>()
.AddTransient<IEntityService, ChildAEntityService>()
.AddTransient<IEntityService, ChildBEntityService>()
.AddTransient<IEntityService, GrandChildAEntityService>()
.AddTransient<IEntityService, BaseEntityService>()
.AddTransient<EntityServiceFactory>() // this should have an interface, but I omitted it to keep the example concise
.BuildServiceProvider();
// Don't get hung up on this line--it's part of the test, not the solution.
BaseEntity entity = (dynamic)Activator.CreateInstance(entityType);
var entityServices = serviceProvider
.GetService<EntityServiceFactory>()
.GetServices(entity);
Assert.Equal(
expectedServiceTypes,
entityServices.Select(s => s.GetType())
);
}
由于涉及到强制转换,我认为这不像 Simple Injector 实现那样优雅。不过,它仍然相当不错,而且这种模式有一些先例。它与 MVC Core 的Policy-Based Authorization的实现非常相似;具体来说AuthorizationHandler
。