如何在责任链中注入下一个处理程序的依赖?
How to inject the dependency of the next handler in a chain of responsibility?
在我当前的项目中,我使用了很多责任链模式。
但是,我觉得通过依赖注入来配置链有点尴尬。
鉴于此模型:
public interface IChainOfResponsibility
{
IChainOfResponsibility Next { get; }
void Handle(Foo foo);
}
public class HandlerOne : IChainOfResponsibility
{
private DbContext _dbContext;
public HandlerOne(IChainOfResponsibility next, DbContext dbContext)
{
Next = next;
_dbContext = dbContext;
}
public IChainOfResponsibility Next { get; }
public void Handle(Foo foo) { /*...*/}
}
public class HandlerTwo : IChainOfResponsibility
{
private DbContext _dbContext;
public HandlerTwo(IChainOfResponsibility next, DbContext dbContext)
{
Next = next;
_dbContext = dbContext;
}
public IChainOfResponsibility Next { get; }
public void Handle(Foo foo) { /*...*/}
}
我的启动变成:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IChainOfResponsibility>(x =>
new HandlerOne(x.GetRequiredService<HandlerTwo>(), x.GetRequiredService<DbContext>())
);
services.AddTransient(x =>
new HandlerTwo(null, x.GetRequiredService<DbContext>())
);
}
如何更干净地配置我的责任链?
我找到了一个简单的解决方案,因为我找不到任何能满足我要求的东西。它工作正常,因为它使用 IServiceProvider.GetRequiredService
来解析链中所有处理程序的所有构造函数依赖性。
我的启动 class 变成:
public void ConfigureServices(IServiceCollection services)
{
services.Chain<IChainOfResponsibility>()
.Add<HandlerOne>()
.Add<HandlerTwo>()
.Configure();
}
我正在做的是使用 Expression 动态生成问题中的 lambda。然后将其编译并注册到 IServiceCollection.AddTransient
.
因为它生成编译代码,在运行时间内它应该运行和问题注册一样快。
这是神奇的代码:
public static class ChainConfigurator
{
public static IChainConfigurator<T> Chain<T>(this IServiceCollection services) where T : class
{
return new ChainConfiguratorImpl<T>(services);
}
public interface IChainConfigurator<T>
{
IChainConfigurator<T> Add<TImplementation>() where TImplementation : T;
void Configure();
}
private class ChainConfiguratorImpl<T> : IChainConfigurator<T> where T : class
{
private readonly IServiceCollection _services;
private List<Type> _types;
private Type _interfaceType;
public ChainConfiguratorImpl(IServiceCollection services)
{
_services = services;
_types = new List<Type>();
_interfaceType = typeof(T);
}
public IChainConfigurator<T> Add<TImplementation>() where TImplementation : T
{
var type = typeof(TImplementation);
_types.Add(type);
return this;
}
public void Configure()
{
if (_types.Count == 0)
throw new InvalidOperationException($"No implementation defined for {_interfaceType.Name}");
foreach (var type in _types)
{
ConfigureType(type);
}
}
private void ConfigureType(Type currentType)
{
// gets the next type, as that will be injected in the current type
var nextType = _types.SkipWhile(x => x != currentType).SkipWhile(x => x == currentType).FirstOrDefault();
// Makes a parameter expression, that is the IServiceProvider x
var parameter = Expression.Parameter(typeof(IServiceProvider), "x");
// get constructor with highest number of parameters. Ideally, there should be only 1 constructor, but better be safe.
var ctor = currentType.GetConstructors().OrderByDescending(x => x.GetParameters().Count()).First();
// for each parameter in the constructor
var ctorParameters = ctor.GetParameters().Select(p =>
{
// check if it implements the interface. That's how we find which parameter to inject the next handler.
if (_interfaceType.IsAssignableFrom(p.ParameterType))
{
if (nextType is null)
{
// if there's no next type, current type is the last in the chain, so it just receives null
return Expression.Constant(null, _interfaceType);
}
else
{
// if there is, then we call IServiceProvider.GetRequiredService to resolve next type for us
return Expression.Call(typeof(ServiceProviderServiceExtensions), "GetRequiredService", new Type[] { nextType }, parameter);
}
}
// this is a parameter we don't care about, so we just ask GetRequiredService to resolve it for us
return (Expression)Expression.Call(typeof(ServiceProviderServiceExtensions), "GetRequiredService", new Type[] { p.ParameterType }, parameter);
});
// cool, we have all of our constructors parameters set, so we build a "new" expression to invoke it.
var body = Expression.New(ctor, ctorParameters.ToArray());
// if current type is the first in our list, then we register it by the interface, otherwise by the concrete type
var first = _types[0] == currentType;
var resolveType = first ? _interfaceType : currentType;
var expressionType = Expression.GetFuncType(typeof(IServiceProvider), resolveType);
// finally, we can build our expression
var expression = Expression.Lambda(expressionType, body, parameter);
// compile it
var compiledExpression = (Func<IServiceProvider, object>)expression.Compile();
// and register it in the services collection as transient
_services.AddTransient(resolveType, compiledExpression );
}
}
}
PS.: 我正在回答我自己的问题以供将来参考(我自己和希望其他人),但我希望对此有一些反馈。
适用于最简单的依赖链情况的快速解决方案。
public static IServiceCollection AddChained<TService>(this IServiceCollection services, params Type[] implementationTypes)
{
if (implementationTypes.Length == 0)
{
throw new ArgumentException("Pass at least one implementation type", nameof(implementationTypes));
}
foreach(Type type in implementationTypes)
{
services.AddScoped(type);
}
int order = 0;
services.AddTransient(typeof(TService), provider =>
{
//starts again
if (order > implementationTypes.Length - 1)
{
order = 0;
}
Type type = implementationTypes[order];
order++;
return provider.GetService(type);
});
return services;
}
然后
services.AddChained<IService>(typeof(SomeTypeWithIService), typeof(SomeType));
重要通知:
需要非常小心地使用此解决方案,因为它在多线程场景中可能无法始终如一地工作。 order
变量在这里不是线程安全的。因此,它不能保证它总是 return 我们服务链中的第一个实施。
例如,当我们调用 services.GetService<IService>()
时,我们希望一直接收到 SomeTypeWithIService
的实例,因为这是链中的第一个实现。但是如果我们在多个线程中执行相同的调用,我们有时会收到 SomeType
而不是因为 order
不是线程安全的。
我通过引入 ChainLink(current, next) 的概念发展了你的想法。
public class ItemDecoratorChainLink : IItemDecorator
{
private readonly IItemDecorator[] _decorators;
public ItemDecoratorChainLink(
IItemDecorator current,
IItemDecorator next)
{
if (current == null)
{
throw new ArgumentNullException(nameof(current));
}
_decorators = next != null
? new[] { current, next }
: new[] { current };
}
public bool CanHandle(Item item) =>
_decorators.Any(d => d.CanHandle(item));
public void Decorate(Item item)
{
var decorators = _decorators.Where(d => d.CanHandle(item)).ToArray();
foreach (var decorator in decorators)
{
decorator.Decorate(item);
}
}
}
因此,您不需要在 link 中保留对“下一个”link 的引用,但会增加 chainLink 的负担。你的 links,其中,变得更干净,从重复中解脱出来,并且可以关心单一的责任。
下面是链构建器的代码:
public class ComponentChainBuilder<TInterface> : IChainBuilder<TInterface>
where TInterface : class
{
private static readonly Type InterfaceType = typeof(TInterface);
private readonly List<Type> _chain = new List<Type>();
private readonly IServiceCollection _container;
private readonly ConstructorInfo _chainLinkCtor;
private readonly string _currentImplementationArgName;
private readonly string _nextImplementationArgName;
public ComponentChainBuilder(
IServiceCollection container,
Type chainLinkType,
string currentImplementationArgName,
string nextImplementationArgName)
{
_container = container;//.GuardNotNull(nameof(container));
_chainLinkCtor = chainLinkType.GetConstructors().First();//.GuardNotNull(nameof(chainLinkType));
_currentImplementationArgName = currentImplementationArgName;//.GuardNeitherNullNorWhitespace(nameof(currentImplementationArgName));
_nextImplementationArgName = nextImplementationArgName;//.GuardNeitherNullNorWhitespace(nameof(nextImplementationArgName));
}
/// <inheritdoc />
public IChainBuilder<TInterface> Link(Type implementationType)
{
_chain.Add(implementationType);
return this;
}
/// <inheritdoc />
public IChainBuilder<TInterface> Link<TImplementationType>()
where TImplementationType : class, TInterface
=> Link(typeof(TImplementationType));
public IServiceCollection Build(ServiceLifetime serviceLifetime = ServiceLifetime.Transient)
{
if (_chain.Count == 0)
{
throw new InvalidOperationException("At least one link must be registered.");
}
var serviceProviderParameter = Expression.Parameter(typeof(IServiceProvider), "x");
Expression chainLink = null;
for (var i = _chain.Count - 1; i > 0; i--)
{
var currentLink = CreateLinkExpression(_chain[i - 1], serviceProviderParameter);
var nextLink = chainLink ?? CreateLinkExpression(_chain[i], serviceProviderParameter);
chainLink = CreateChainLinkExpression(currentLink, nextLink, serviceProviderParameter);
}
if (chainLink == null)
{
// only one type is defined so we use it to register dependency
_container.Add(new ServiceDescriptor(InterfaceType, _chain[0], serviceLifetime));
}
else
{
// chain is built so we use it to register dependency
var expressionType = Expression.GetFuncType(typeof(IServiceProvider), InterfaceType);
var createChainLinkLambda = Expression.Lambda(expressionType, chainLink, serviceProviderParameter);
var createChainLinkFunction = (Func<IServiceProvider, object>)createChainLinkLambda.Compile();
_container.Add(new ServiceDescriptor(InterfaceType, createChainLinkFunction, serviceLifetime));
}
return _container;
}
private NewExpression CreateLinkExpression(Type linkType, ParameterExpression serviceProviderParameter)
{
var linkCtor = linkType.GetConstructors().First();
var linkCtorParameters = linkCtor.GetParameters()
.Select(p => GetServiceProviderDependenciesExpression(p, serviceProviderParameter))
.ToArray();
return Expression.New(linkCtor, linkCtorParameters);
}
private Expression CreateChainLinkExpression(
Expression currentLink,
Expression nextLink,
ParameterExpression serviceProviderParameter)
{
var chainLinkCtorParameters = _chainLinkCtor.GetParameters().Select(p =>
{
if (p.Name == _currentImplementationArgName)
{
return currentLink;
}
if (p.Name == _nextImplementationArgName)
{
return nextLink;
}
return GetServiceProviderDependenciesExpression(p, serviceProviderParameter);
}).ToArray();
return Expression.New(_chainLinkCtor, chainLinkCtorParameters);
}
private static Expression GetServiceProviderDependenciesExpression(ParameterInfo parameter, ParameterExpression serviceProviderParameter)
{
// this is a parameter we don't care about, so we just ask GetRequiredService to resolve it for us
return Expression.Call(
typeof(ServiceProviderServiceExtensions),
nameof(ServiceProviderServiceExtensions.GetRequiredService),
new[] { parameter.ParameterType },
serviceProviderParameter);
}
}
及其扩展名:
public static IChainBuilder<TInterface> Chain<TInterface, TChainLink>(
this IServiceCollection container,
string currentImplementationArgumentName = "current",
string nextImplementationArgumentName = "next")
where TInterface : class
where TChainLink : TInterface
=> new ComponentChainBuilder<TInterface>(
container,
typeof(TChainLink),
currentImplementationArgumentName,
nextImplementationArgumentName);
构建链的代码如下所示:
serviceProvider.Chain<IItemDecorator, ItemDecoratorChainLink>()
.Link<ChannelItemDecorator>()
.Link<CompetitionItemDecorator>()
.Link<ProgramItemDecorator>()
.Build(ServiceLifetime.Singleton);
此方法的完整示例可以在我的 GitHub:
上找到
在我当前的项目中,我使用了很多责任链模式。
但是,我觉得通过依赖注入来配置链有点尴尬。
鉴于此模型:
public interface IChainOfResponsibility
{
IChainOfResponsibility Next { get; }
void Handle(Foo foo);
}
public class HandlerOne : IChainOfResponsibility
{
private DbContext _dbContext;
public HandlerOne(IChainOfResponsibility next, DbContext dbContext)
{
Next = next;
_dbContext = dbContext;
}
public IChainOfResponsibility Next { get; }
public void Handle(Foo foo) { /*...*/}
}
public class HandlerTwo : IChainOfResponsibility
{
private DbContext _dbContext;
public HandlerTwo(IChainOfResponsibility next, DbContext dbContext)
{
Next = next;
_dbContext = dbContext;
}
public IChainOfResponsibility Next { get; }
public void Handle(Foo foo) { /*...*/}
}
我的启动变成:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IChainOfResponsibility>(x =>
new HandlerOne(x.GetRequiredService<HandlerTwo>(), x.GetRequiredService<DbContext>())
);
services.AddTransient(x =>
new HandlerTwo(null, x.GetRequiredService<DbContext>())
);
}
如何更干净地配置我的责任链?
我找到了一个简单的解决方案,因为我找不到任何能满足我要求的东西。它工作正常,因为它使用 IServiceProvider.GetRequiredService
来解析链中所有处理程序的所有构造函数依赖性。
我的启动 class 变成:
public void ConfigureServices(IServiceCollection services)
{
services.Chain<IChainOfResponsibility>()
.Add<HandlerOne>()
.Add<HandlerTwo>()
.Configure();
}
我正在做的是使用 Expression 动态生成问题中的 lambda。然后将其编译并注册到 IServiceCollection.AddTransient
.
因为它生成编译代码,在运行时间内它应该运行和问题注册一样快。
这是神奇的代码:
public static class ChainConfigurator
{
public static IChainConfigurator<T> Chain<T>(this IServiceCollection services) where T : class
{
return new ChainConfiguratorImpl<T>(services);
}
public interface IChainConfigurator<T>
{
IChainConfigurator<T> Add<TImplementation>() where TImplementation : T;
void Configure();
}
private class ChainConfiguratorImpl<T> : IChainConfigurator<T> where T : class
{
private readonly IServiceCollection _services;
private List<Type> _types;
private Type _interfaceType;
public ChainConfiguratorImpl(IServiceCollection services)
{
_services = services;
_types = new List<Type>();
_interfaceType = typeof(T);
}
public IChainConfigurator<T> Add<TImplementation>() where TImplementation : T
{
var type = typeof(TImplementation);
_types.Add(type);
return this;
}
public void Configure()
{
if (_types.Count == 0)
throw new InvalidOperationException($"No implementation defined for {_interfaceType.Name}");
foreach (var type in _types)
{
ConfigureType(type);
}
}
private void ConfigureType(Type currentType)
{
// gets the next type, as that will be injected in the current type
var nextType = _types.SkipWhile(x => x != currentType).SkipWhile(x => x == currentType).FirstOrDefault();
// Makes a parameter expression, that is the IServiceProvider x
var parameter = Expression.Parameter(typeof(IServiceProvider), "x");
// get constructor with highest number of parameters. Ideally, there should be only 1 constructor, but better be safe.
var ctor = currentType.GetConstructors().OrderByDescending(x => x.GetParameters().Count()).First();
// for each parameter in the constructor
var ctorParameters = ctor.GetParameters().Select(p =>
{
// check if it implements the interface. That's how we find which parameter to inject the next handler.
if (_interfaceType.IsAssignableFrom(p.ParameterType))
{
if (nextType is null)
{
// if there's no next type, current type is the last in the chain, so it just receives null
return Expression.Constant(null, _interfaceType);
}
else
{
// if there is, then we call IServiceProvider.GetRequiredService to resolve next type for us
return Expression.Call(typeof(ServiceProviderServiceExtensions), "GetRequiredService", new Type[] { nextType }, parameter);
}
}
// this is a parameter we don't care about, so we just ask GetRequiredService to resolve it for us
return (Expression)Expression.Call(typeof(ServiceProviderServiceExtensions), "GetRequiredService", new Type[] { p.ParameterType }, parameter);
});
// cool, we have all of our constructors parameters set, so we build a "new" expression to invoke it.
var body = Expression.New(ctor, ctorParameters.ToArray());
// if current type is the first in our list, then we register it by the interface, otherwise by the concrete type
var first = _types[0] == currentType;
var resolveType = first ? _interfaceType : currentType;
var expressionType = Expression.GetFuncType(typeof(IServiceProvider), resolveType);
// finally, we can build our expression
var expression = Expression.Lambda(expressionType, body, parameter);
// compile it
var compiledExpression = (Func<IServiceProvider, object>)expression.Compile();
// and register it in the services collection as transient
_services.AddTransient(resolveType, compiledExpression );
}
}
}
PS.: 我正在回答我自己的问题以供将来参考(我自己和希望其他人),但我希望对此有一些反馈。
适用于最简单的依赖链情况的快速解决方案。
public static IServiceCollection AddChained<TService>(this IServiceCollection services, params Type[] implementationTypes)
{
if (implementationTypes.Length == 0)
{
throw new ArgumentException("Pass at least one implementation type", nameof(implementationTypes));
}
foreach(Type type in implementationTypes)
{
services.AddScoped(type);
}
int order = 0;
services.AddTransient(typeof(TService), provider =>
{
//starts again
if (order > implementationTypes.Length - 1)
{
order = 0;
}
Type type = implementationTypes[order];
order++;
return provider.GetService(type);
});
return services;
}
然后
services.AddChained<IService>(typeof(SomeTypeWithIService), typeof(SomeType));
重要通知:
需要非常小心地使用此解决方案,因为它在多线程场景中可能无法始终如一地工作。 order
变量在这里不是线程安全的。因此,它不能保证它总是 return 我们服务链中的第一个实施。
例如,当我们调用 services.GetService<IService>()
时,我们希望一直接收到 SomeTypeWithIService
的实例,因为这是链中的第一个实现。但是如果我们在多个线程中执行相同的调用,我们有时会收到 SomeType
而不是因为 order
不是线程安全的。
我通过引入 ChainLink(current, next) 的概念发展了你的想法。
public class ItemDecoratorChainLink : IItemDecorator
{
private readonly IItemDecorator[] _decorators;
public ItemDecoratorChainLink(
IItemDecorator current,
IItemDecorator next)
{
if (current == null)
{
throw new ArgumentNullException(nameof(current));
}
_decorators = next != null
? new[] { current, next }
: new[] { current };
}
public bool CanHandle(Item item) =>
_decorators.Any(d => d.CanHandle(item));
public void Decorate(Item item)
{
var decorators = _decorators.Where(d => d.CanHandle(item)).ToArray();
foreach (var decorator in decorators)
{
decorator.Decorate(item);
}
}
}
因此,您不需要在 link 中保留对“下一个”link 的引用,但会增加 chainLink 的负担。你的 links,其中,变得更干净,从重复中解脱出来,并且可以关心单一的责任。
下面是链构建器的代码:
public class ComponentChainBuilder<TInterface> : IChainBuilder<TInterface>
where TInterface : class
{
private static readonly Type InterfaceType = typeof(TInterface);
private readonly List<Type> _chain = new List<Type>();
private readonly IServiceCollection _container;
private readonly ConstructorInfo _chainLinkCtor;
private readonly string _currentImplementationArgName;
private readonly string _nextImplementationArgName;
public ComponentChainBuilder(
IServiceCollection container,
Type chainLinkType,
string currentImplementationArgName,
string nextImplementationArgName)
{
_container = container;//.GuardNotNull(nameof(container));
_chainLinkCtor = chainLinkType.GetConstructors().First();//.GuardNotNull(nameof(chainLinkType));
_currentImplementationArgName = currentImplementationArgName;//.GuardNeitherNullNorWhitespace(nameof(currentImplementationArgName));
_nextImplementationArgName = nextImplementationArgName;//.GuardNeitherNullNorWhitespace(nameof(nextImplementationArgName));
}
/// <inheritdoc />
public IChainBuilder<TInterface> Link(Type implementationType)
{
_chain.Add(implementationType);
return this;
}
/// <inheritdoc />
public IChainBuilder<TInterface> Link<TImplementationType>()
where TImplementationType : class, TInterface
=> Link(typeof(TImplementationType));
public IServiceCollection Build(ServiceLifetime serviceLifetime = ServiceLifetime.Transient)
{
if (_chain.Count == 0)
{
throw new InvalidOperationException("At least one link must be registered.");
}
var serviceProviderParameter = Expression.Parameter(typeof(IServiceProvider), "x");
Expression chainLink = null;
for (var i = _chain.Count - 1; i > 0; i--)
{
var currentLink = CreateLinkExpression(_chain[i - 1], serviceProviderParameter);
var nextLink = chainLink ?? CreateLinkExpression(_chain[i], serviceProviderParameter);
chainLink = CreateChainLinkExpression(currentLink, nextLink, serviceProviderParameter);
}
if (chainLink == null)
{
// only one type is defined so we use it to register dependency
_container.Add(new ServiceDescriptor(InterfaceType, _chain[0], serviceLifetime));
}
else
{
// chain is built so we use it to register dependency
var expressionType = Expression.GetFuncType(typeof(IServiceProvider), InterfaceType);
var createChainLinkLambda = Expression.Lambda(expressionType, chainLink, serviceProviderParameter);
var createChainLinkFunction = (Func<IServiceProvider, object>)createChainLinkLambda.Compile();
_container.Add(new ServiceDescriptor(InterfaceType, createChainLinkFunction, serviceLifetime));
}
return _container;
}
private NewExpression CreateLinkExpression(Type linkType, ParameterExpression serviceProviderParameter)
{
var linkCtor = linkType.GetConstructors().First();
var linkCtorParameters = linkCtor.GetParameters()
.Select(p => GetServiceProviderDependenciesExpression(p, serviceProviderParameter))
.ToArray();
return Expression.New(linkCtor, linkCtorParameters);
}
private Expression CreateChainLinkExpression(
Expression currentLink,
Expression nextLink,
ParameterExpression serviceProviderParameter)
{
var chainLinkCtorParameters = _chainLinkCtor.GetParameters().Select(p =>
{
if (p.Name == _currentImplementationArgName)
{
return currentLink;
}
if (p.Name == _nextImplementationArgName)
{
return nextLink;
}
return GetServiceProviderDependenciesExpression(p, serviceProviderParameter);
}).ToArray();
return Expression.New(_chainLinkCtor, chainLinkCtorParameters);
}
private static Expression GetServiceProviderDependenciesExpression(ParameterInfo parameter, ParameterExpression serviceProviderParameter)
{
// this is a parameter we don't care about, so we just ask GetRequiredService to resolve it for us
return Expression.Call(
typeof(ServiceProviderServiceExtensions),
nameof(ServiceProviderServiceExtensions.GetRequiredService),
new[] { parameter.ParameterType },
serviceProviderParameter);
}
}
及其扩展名:
public static IChainBuilder<TInterface> Chain<TInterface, TChainLink>(
this IServiceCollection container,
string currentImplementationArgumentName = "current",
string nextImplementationArgumentName = "next")
where TInterface : class
where TChainLink : TInterface
=> new ComponentChainBuilder<TInterface>(
container,
typeof(TChainLink),
currentImplementationArgumentName,
nextImplementationArgumentName);
构建链的代码如下所示:
serviceProvider.Chain<IItemDecorator, ItemDecoratorChainLink>()
.Link<ChannelItemDecorator>()
.Link<CompetitionItemDecorator>()
.Link<ProgramItemDecorator>()
.Build(ServiceLifetime.Singleton);
此方法的完整示例可以在我的 GitHub:
上找到