【问题标题】:Unity Container - wrap all resolved implementations of parent interfaceUnity Container - 包装父接口的所有已解析实现
【发布时间】:2015-09-13 22:14:25
【问题描述】:

除了解析接口之外,我希望能够使用 Unity Container 来包装所有实现公共父接口的实例。例如,假设您为命令定义了公共接口以及处理它们的类的实现:

public interface ICommand {}

public interface ICommandHandler<T> where T : ICommand
{
    void Execute(T command);
}

然后你有一些实现这些接口的类:

public class MoveCommand : ICommand { /* properties */ }

public class MoveHandler : ICommandHandler<MoveCommand>
{
    public void Execute(MoveCommand command) { /* do stuff */ }
}

public class CreateCommand : ICommand { /* properties */ }

public class CreateHandler : ICommandHandler<CreateCommand>
{
    public void Execute(CreateCommand command) { /* do other stuff */ }
}

这些命令随后会在 Unity Container 中注册:

container.RegisterType<ICommandHandler<MoveCommand>, MoveHandler>(new ContainerControlledLifetimeManager());
container.RegisterType<ICommandHandler<CreateCommand>, CreateHandler>(new ContainerControlledLifetimeManager());

现在,假设您有一些用于实现横切关注点的接口:

public interface ILogger<T> : ICommandHandler<T> where T : ICommand { }

public class Logger<T> : ILogger<T> where T : ICommand
{
    private ICommandHandler<T> handler;

    public Logger(ICommandHandler<T> handler)
    {
        this.handler = handler;
    }

    public void Execute(T command)
    {
        // Log stuff
        handler.Execute(command);
    }        
}

像这样在 Unity 中注册:

container.RegisterType(typeof(ILogger<>), typeof(Logger<>), new ContainerControlledLifetimeManager());

我希望 Unity 在解决它时能够将每个 ICommandHandler 包装在 ILogger 中。一种方法是修改每个 ICommandHandler 类型的 RegisterType 调用。但是,本着“不要重复自己”的精神,我真的希望能够指定一次 all ICommandHandler 类型应该另外包装在适当类型的 ILogger 中。可能会注册大量 ICommandHandler 类型,以及用于错误处理、身份验证等内容的附加包装器,因此重复和疏忽的机会会很大。有没有办法一次将包装器应用于所有这些?

编辑:这是我正在寻找的语法,改编自接受的答案和接受的答案中的第一个链接:

container.RegisterType<ICommandHandler<MoveCommand>, MoveHandler>("InnerCommand", new ContainerControlledLifetimeManager());
container.RegisterType<ICommandHandler<CreateCommand>, CreateHandler>("InnerCommand", new ContainerControlledLifetimeManager());

container.RegisterType(typeof(ICommandHandler<>),
    typeof(Logger<>),
    InjectionConstructor(new ResolvedParameter(typeof(ICommandHandler<>), "InnerCommand")));

【问题讨论】:

  • 听起来你想做面向方面的编程。看看 PostSharp postsharp.net

标签: c# .net dependency-injection unity-container


【解决方案1】:

您可以使用 Unity 拦截行为。您需要执行以下操作:

1) 获取 Unity.Interception NuGet 包。

2) 像这样指示统一容器使用拦截扩展:

container.AddNewExtension<Interception>();

3) 像这样创建你的拦截行为:

public class LoggingBehavior : IInterceptionBehavior
{
    public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
    {
        var next_bahavior = getNext();

        //Here do your logging before executing the method

        var method_return = next_bahavior.Invoke(input, getNext);

        //Here do your logging after executing the method

        return method_return;
    }

    public IEnumerable<Type> GetRequiredInterfaces()
    {
        yield break;
    }

    public bool WillExecute
    {
        get { return true; }
    }
}

并在其中放入实际执行日志记录所需的代码。请注意,我的示例行为没有构造函数。如果您需要在其中注入某些内容,则需要将其(依赖项)注册到容器中。或者您可以自己手动创建行为并将其注册为统一容器的实例。

请注意,您可以使用“输入”变量来获取方法调用参数。你也可以使用method_return变量来获取返回值和抛出的异常(如果有的话)。

4) 当你注册你的类型时,指示 unity 使用我们刚刚定义的拦截行为:

container.RegisterType<ICommandHandler<MoveCommand>, MoveHandler>(new ContainerControlledLifetimeManager(), new Interceptor<InterfaceInterceptor>(), new InterceptionBehavior<LoggingBehavior>());
container.RegisterType<ICommandHandler<CreateCommand>, CreateHandler>(new ContainerControlledLifetimeManager(), new Interceptor<InterfaceInterceptor>(), new InterceptionBehavior<LoggingBehavior>());

现在您不需要您描述的 ILogger 和 Logger 类型。

【讨论】:

  • Unity 拦截是处理这个问题的好方法。它还允许您拦截任何/所有类,而不仅仅是实现特定接口的类。
  • 我的问题的要点是能够将日志记录行为一次应用到 ICommandHandler,而不是为每个关闭的 ICommandHandler 类型应用一次。我看不出这个答案在这方面有何帮助。
  • 您已经注册了每个关闭的 ICommandHandler 实现,对吧?在这种情况下,为它们中的每一个指定拦截器有什么问题?
  • 从我的角度来看,将日志实现从一个类更改为另一个应该涉及对一行代码的更改。使用编辑中描述的方法,更改日志实现很简单,并且在进行更改时不可能丢失任何处理程序。
  • 在这里您也可以轻松更改记录器的实现,与记录相关的所有内容都在LoggingBehavior 类中完成。您还可以在类中注入一些ILogger,并拥有ILogger 的多个实现。请注意,我的解决方案更通用,因为它不仅仅针对 ICommandHandler&lt;&gt;。它可用于为任何类/接口创建日志记录。话虽如此,我真的不喜欢使用基于 DI 容器的拦截。我只是提出这个建议,因为您已经在使用 DI 容器。
【解决方案2】:

你的Logger&lt;T&gt; 看起来像是一个装饰器,但它是一个奇怪的构造,我不建议让它从ICommandHandler&lt;T&gt; 派生。没有理由这样做,它会使您的设计复杂化。我建议如下:

public interface ILogger {
    void Log(LogEntry entry);
}

public class FileLogger : ILogger { ... }

现在您可以简单地将ILogger 注入需要记录的类中。如果您想将日志记录应用到系统中的所有命令处理程序,您可以为此定义一个装饰器:

public class LoggingCommandHandlerDecorator<T> 
    : ICommandHandler<T> where T : ICommand
{
    private ILogger logger;
    private ICommandHandler<T> decoratee;

    public LoggingCommandHandlerDecorator(ILogger logger, ICommandHandler<T> decoratee)
    {
        this.logger = logger;
        this.decoratee = decoratee;
    }

    public void Execute(T command)
    {
        // Log stuff
        this.logger.Log("Executing " + typeof(T).Name + " " +
            JsonConvert.SerializeObject(command));

        decoratee.Execute(command);
    }        
}

注意这个装饰器依赖于ILogger。它允许将日志记录应用到命令处理程序。

Stackoverflow 上还有其他答案展示了如何注册通用装饰器,例如 this onethis one

我建议不要使用拦截,因为使用装饰器会带来更清洁和更可维护的设计。

【讨论】:

    猜你喜欢
    • 2011-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-29
    • 2019-07-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多