如何在责任链中注入下一个处理程序的依赖?

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:

上找到

https://github.com/alex-valchuk/dot-net-expressions/blob/master/NetExpressions/ConsoleApp1/ConsoleApp1/ChainBuilder/ComponentChainBuilder.cs