为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C#
浮云间 2022-11-22 16:34:04

在我当前的项目中,我使用了很多责任链模式。


但是,我发现通过依赖注入来配置链有点尴尬。


给定这个模型:


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>())

    );

}

如何更干净地配置我的责任链?


查看完整描述

3 回答

?
慕虎7371278

TA贡献0条经验 获得超4个赞

我破解了一个简单的解决方案,因为我找不到任何能满足我要求的东西。它工作正常,因为它用于IServiceProvider.GetRequiredService解决链中所有处理程序的所有构造函数依赖性。


我的启动类变为:


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.:我正在回答我自己的问题以供将来参考(我自己和希望其他人),但我希望得到一些反馈。


查看完整回答
反对 回复 2022-11-22
?
侃侃尔雅

TA贡献1531条经验 获得超14个赞

适用于最简单的依赖链情况的快速解决方案。


    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变量在这里不是线程安全的。因此,它不能保证它总是会为我们的服务返回链中的第一个实现。


例如,当我们调用时,services.GetService<IService>()我们希望SomeTypeWithIService始终收到实例,因为这是链中的第一个实现。但是如果我们在多个线程中执行相同的调用,我们有时会收到SomeType,因为order它不是线程安全的。


查看完整回答
反对 回复 2022-11-22
?
慕工程0101907

TA贡献1604条经验 获得超1个赞

我通过引入 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);

        }

    }

}

因此,您不需要在链接内保留对“下一个”链接的引用,但会增加 chainLink 的负担。你的链接,其中,变得更干净,从重复中解脱出来,并且可以关心单一的责任。


以下是链构建器的代码:


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


查看完整回答
反对 回复 2022-11-22
  • 3 回答
  • 0 关注
  • 6 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信