【问题标题】:c# Simple Injector inject decorator in a class only from one clientc# Simple Injector 仅从一个客户端在一个类中注入装饰器
【发布时间】:2019-05-28 10:45:35
【问题描述】:

大家好,
我有一个问题,我有一个这样的界面:

public interface ICommand<in TRequest, out TResponse> 
    where TRequest : class
    where TResponse : BaseResponse
{
    TResponse Execute(TRequest request);
}

然后我两个类实现这个接口是这样的:

public class ExternalAddUser : ICommand<ExternalAddUserRequest, ExternalAddUserResponse>
{
    private readonly ICommand<AddUserRequest, AddUserResponse> _command;

    public ExternalAddUser(ICommand<AddUserRequest, AddUserResponse> command)
    {
        _command = command;
    }

    public ExternalAddUserResponse Execute(ExternalAddUserRequest request)
    {
        var response = _command.Execute(Mapper.Map<AddUserRequest>(request));

        return Mapper.Map<ExternalAddUserResponse>(response);
    }
}

还有这个:

public class AddUser : ICommand<AddUserRequest, AddUserResponse>
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly IMessageService _messageService;
    private readonly IDefaultSettings _settings;
    private readonly IMessageFactory _messageFactory;

    public AddUser(IUnitOfWork unitOfWork, IMessageService messageService, IDefaultSettings settings, IMessageFactory messageFactory)
    {
        _unitOfWork = unitOfWork;
        _messageService = messageService;
        _settings = settings;
        _messageFactory = messageFactory;
    }

    public AddUserResponse Execute(AddUserRequest request)
    {
        // My implementation here
    }
}

Interface IMessageFactory 是一个“工厂/模板”模式,它创建一个只有以下属性的 IMessage 接口:Body、Subject、Language。 我已经用这样的简单注射器注册了我的课程:

container.Register(typeof(ICommand<,>), businessLayerAssembly);   
container.Register<IDefaultSettings, DefaultSettings>(Lifestyle.Singleton);
container.Register<ISecuritySettings, SecuritySettings>(Lifestyle.Singleton);
container.RegisterConditional<IMessageFactory, ActivationMessageFactory>
            (c => c.Consumer.ImplementationType == typeof(AddUser) 
                  || c.Consumer.ImplementationType == typeof(SendActivationEmail));
container.RegisterConditional<IMessageFactory, RecoveryMessageFactory>
            (c => !c.Handled);

现在我有另一个类是 ActivationMessageFactory 的装饰器,如下所示:

public class ActivationMessageWithoutLinkFactory : IMessageFactory 
{
    private readonly IMessageFactory _messageFactory;

    public ActivationMessageWithoutLinkFactory(IMessageFactory messageFactory)
    {
         _messageFactory = messageFactory;
    }

    public IMessage CreateMessage(MessageData messageData)
    {
        // Implementation
    }
}

我的问题是:
当从 ExternalAddUser 类调用此类时,是否可以在 AddUser 类中注入 ActivationMessageWithoutLinkFactory 装饰器? 气味代码示例:

public class ExternalAddUser : ICommand<ExternalAddUserRequest, ExternalAddUserResponse>
{
    public ExternalAddUserResponse Execute(ExternalAddUserRequest request)
    {

        ICommand<AddUserRequest, AddUserResponse> command = new AddUser(new SqlUnitOfWork(), new EmailService(), 
            new DefaultSettings(), new ActivationMessageWithoutLinkFactory(new ActivationMessageFactory()));
    }
}

这是我要构建的对象图:

// AddUser injected into ExternalAddUser
new ExternalAddUser(
    new AddUser(
        new UnitOfWork(),
        new MessageService(),
        new DefaultSettings(),
        new ActivationMessageWithoutLinkFactory(
            new ActivationMessageFactory())))

// AddUser injected into anything else
new AnythingElse(
    new AddUser(
        new UnitOfWork(),
        new MessageService(),
        new DefaultSettings(),
        new ActivationMessageFactory())) // no decorator

谢谢你的回答,希望我清楚。

【问题讨论】:

  • 我们可以尝试基于消费者的基于条件的注入吗? simpleinjector.readthedocs.io/en/latest/…
  • 类似container.RegisterConditional&lt;IMessageFactory , ActivationMessageWithoutLinkFactory &gt;( c =&gt; c.Consumer.ImplementationType == typeof(ExternalAddUser));
  • 不,我已经尝试过了,但是这个解决方案不起作用。
  • 所以你嵌套了ICommand 接口?这似乎是你问题的来源。看看this
  • 我的问题是如何注册这个:ICommand &lt;AddUserRequest, AddUserResponse&gt; command = new AddUser (new SqlUnitOfWork (), new EmailService (), new DefaultSettings (), new ActivationMessageWithoutLinkFactory (new ActivationMessageFactory ()); 即,如何在AddUser 类中注入装饰器ActivationMessageWithoutLinkFactory 只有当它被特定类ExternalAddUser 调用时。我希望我更清楚了。谢谢@Ric.Net

标签: c# .net design-patterns dependency-injection simple-injector


【解决方案1】:

您要实现的是基于消费者的消费者应用装饰器。这在 Simple Injector 中不容易实现。相反,您可以尝试以下方法:与其使装饰器成为条件,不如将其注入可以在请求开始时设置的上下文数据。这样装饰器可以决定是否执行其逻辑,或者它应该简单地将调用转发给它的被装饰者。

更新:

您可以定义以下抽象:

public class ICommandContext
{
    Type RootRequest { get; }
}

此抽象允许您检查当前正在运行的根请求的类型。你可以在你的装饰器中使用这个抽象:

public class ActivationMessageWithoutLinkFactory : IMessageFactory 
{
    private readonly ICommandContext _context;
    private readonly IMessageFactory _messageFactory;

    public ActivationMessageWithoutLinkFactory(
        ICommandContext context,
        IMessageFactory messageFactory)
    {
        _context = context;
        _messageFactory = messageFactory;
    }

    public IMessage CreateMessage(MessageData messageData)
    {
        if (_context.RootRequest == typeof(ExternalAddUser))
        {
            // Begin decorated stuff
            var message = _messageFactory.CreateMessage(messageData);
            // End decorated stuff

            return message;
        }
        else
        {
            return _messageFactory.CreateMessage(messageData);
        }
    }
}

在您的 Composition Root 中,您现在可以创建一个 ICommandContext 实现和一个可以管理此上下文的 ICommand&lt;,&gt; 装饰器:

public class CommandContext : ICommandContext
{
    public Stack<Type> Requests = new Stack<Type>();

    public Type RootRequest => Requests.First();
}

public class ContextCommandDecorator<TRequest, TResponse> : ICommand<TRequest, TResponse>
{
    private readonly CommandContext _context;
    private readonly ICommand<TRequest, TResponse> _decoratee;
    public ContextCommandDecorator(
        CommandContext context,
        ICommand<TRequest, TResponse> decoratee)
    {
        _context = context;
        _decoratee = decoratee;
    }

    public TResponse Execute(TRequest request)
    {
        _context.Push(typeof(TRequest));

        try
        {
            return _decoratee.Execute(request);
        }
        finally
        {
            _context.Pop();
        }
    }
}

最后,您可以将以下三个注册添加到您的应用程序中:

container.Register<ICommandContext, CommandContext>(Lifestyle.Scoped);
container.Register<CommandContext>(Lifestyle.Scoped);

container.RegisterDecorator(typeof(ICommand<,>), typeof(ContextCommandDecorator<,>));

【讨论】:

  • 感谢您的回复,但我该怎么做才能“使用可以在请求开始时设置的上下文数据注入它。”?你有例子吗?
  • 请注意,我觉得 Ric 的评估是正确的。当您不嵌套命令并仔细查看您的设计时,您可能不会遇到这类问题,尽管这不是我可以证明的。
  • 嗨史蒂文,我试过你的解决方案,它工作得很好。仅为了代码的正确性,RootRequest 必须与 Last () 方法一起使用,否则将使用插入堆栈的最后一个类型,Pop 和 Push 应该用于只读属性 Requests 并且装饰器中的控件应该总是根据请求的类型来完成。我试图纠正它,但它被拒绝了。非常感谢。 @史蒂文
猜你喜欢
  • 1970-01-01
  • 2019-05-01
  • 1970-01-01
  • 2015-04-02
  • 2016-06-07
  • 2014-04-20
  • 2017-09-09
  • 2019-12-07
  • 1970-01-01
相关资源
最近更新 更多