具有自定义属性的 Autofac 拦截

Autofac Interception with custom attributes

我一直在寻找 AOP 日志记录的特定解决方案。我需要一个可以做这样的事情的拦截:

[MyCustomLogging("someParameter")]

事实是,我在其他 DI 框架中看到了使这成为可能的示例。但是我的项目已经在使用 Autofac 进行 DI,我不知道与 Unity 混合使用是否是个好主意(例如)。在 Autofac.extras.dynamiclibrary2 中 class InterceptAttribute 是密封的。

有人知道这个问题吗?

Ps.: 我会满意的:

[Intercept(typeof(MyLoggingClass), "anotherParameter"]

尽管使用属性通过元数据来丰富类型 feed 与要使用的数据的横切关注点不错,但是使用属性来标记 [=52= 运行 的 es 或方法通常是一些横切关注点。

使用您显示的属性标记代码有一些严重的缺点:

  1. 它使您的代码依赖于使用的拦截库,使代码更难更改,也更难替换外部库。应用程序核心与外部库的依赖关系数量应保持在绝对最低限度。如果你的代码中到处都是对依赖注入库的依赖,那就太讽刺了;用于最小化外部依赖性和增加松散耦合的工具。
  2. 要将横切关注点应用到 class 个广泛的 运行ge(这是您通常想要做的),您将必须通过完整的代码库来添加或从方法中删除属性。这是耗时且容易出错的。但更糟糕的是,要确保方面 运行 以特定顺序排列,对于属性来说很难。某些框架允许您指定属性的顺序(使用某种 Order 属性),但更改顺序意味着通过代码进行全面更改以更改属性的 Order .忘记一个会导致错误。这违反了 Open/closed principle.
  3. 由于属性引用方面 class(在您的示例中 typeof(MyLoggingClass)),这使得您的代码仍然静态依赖于横切关注点。将 class 替换为另一个将再次导致您对代码库进行全面更改,并且保持硬依赖性使得重用代码或在 运行 时间或部署时决定方面是否更加困难应不应该应用。在许多情况下,您的代码不能依赖于方面,因为代码位于基础库中,而方面特定于应用程序框架。例如,您可能在 Web 应用程序和 Windows 服务中具有与 运行 相同的业务逻辑。当 运行 在 Web 应用程序中时,您想以不同的方式登录。换句话说,你违反了Dependency inversion principle

因此,我认为以这种方式应用属性是一种不好的做法。 与其使用这样的属性,不如使用拦截器或装饰器来应用横切关注点 t运行。装饰器是我的首选方法,因为它们的使用更干净、更简单,因此更易于维护。无需依赖任何外部库即可编写装饰器,因此可以将它们放置在应用程序中的任何合适位置。然而,装饰器的缺点是编写和应用它们非常麻烦,以防您的设计不是 SOLID, DRY and you're not following the Reused abstraction principle.

但是如果你使用right application design using SOLID and message based patterns,你会发现应用像日志这样的横切关注点只是写一个非常简单的装饰器的问题,比如:

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

    public void Handle(T command) {
        this.logger.Log("Handling {0}. Data: {1}", typeof(T).Name,
            JsonConvert.SerializeObject(command));
        this.decoratee.Handle(command);
    }
}

如果没有适当的设计,您仍然可以使用拦截(没有属性),因为拦截允许您 'decorate' 任何在代码中似乎没有关系(不共享公共接口)的类型。定义要拦截和不拦截的类型可能很麻烦,但您通常仍然可以在应用程序的一个地方定义它,因此不必对整个代码库进行彻底的更改。

侧节点。正如我所说,使用属性来描述pure metadata is fine and preferable。例如,一些代码只允许 运行 具有特定权限的用户使用。您可以按如下方式标记该代码:

[Permission(Permissions.Crm.ManageCompanies)]
public class BlockCompany : ICommand {
    public Guid CompanyId;
}

此属性不描述 运行 的方面,也不引用外部库中的任何类型(PermissionAttribute 是您可以(并且应该)自己定义的东西),或任何AOP 特定类型。它仅使用元数据丰富了代码。

最后,您显然想要应用一些横切关注点来检查当前用户是否具有正确的权限,但该属性不会强制您进入特定方向。使用上面的属性,我可以想象装饰器看起来如下:

public class PermissionCommandHandlerDecorator<T> : ICommandHandler<T>
{
    private static readonly Guid requiredPermissionId =
        typeof(T).GetCustomAttribute<PermissionAttribute>().PermissionId;

    private readonly IUserPermissionChecker checker;
    private readonly ICommandHandler<T> decoratee;

    public PermissionCommandHandlerDecorator(IUserPermissionChecker checker,
        ICommandHandler<T> decoratee) {
        this.checker = checker;
        this.decoratee = decoratee;
    }

    public void Handle(T command) {
        this.checker.CheckPermission(requiredPermissionId);
        this.decoratee.Handle(command);
    }
}