Unity Container - 包装父接口的所有解析实现

Unity Container - wrap all resolved implementations of parent interface

除了解析接口之外,我希望能够使用 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.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 调用。但是,本着 "Don't Repeat Yourself" 的精神,我真的很想能够指定一次 all ICommandHandler 类型应该另外包装在适当类型的 ILogger 中。可能有大量的 ICommandHandler 类型被注册,以及用于错误处理、身份验证等的额外包装器,因此重复和疏忽的机会会很大。有没有办法一次将包装器应用于所有这些?

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

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")));

您可以使用 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; }
    }
}

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

请注意,您可以使用 "input" 变量来获取方法调用参数。您还可以使用 method_return 变量来获取 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 类型。

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

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。它允许将日志记录应用于命令处理程序。

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

我建议不要使用拦截,因为使用装饰器会使设计更简洁、更易于维护。