具有自定义属性的Autofac拦截

时间:2015-04-26 16:06:08

标签: c# aop autofac custom-attributes

我一直在寻找针对AOP日志记录的特定解决方案。我需要拦截才能做到这样的事情:

[MyCustomLogging("someParameter")]

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

任何人都有这个问题的想法?

Ps。:我对此感到满意:

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

1 个答案:

答案 0 :(得分:11)

尽管使用属性来丰富带有元数据的类型以及 feed 横切关注的数据使用并不是很糟糕,但使用属性来标记类或方法运行通常是一些跨领域的关注。

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

  1. 它使您的代码依赖于使用的拦截库,使代码更难更改并使更换外部库变得更加困难。应用程序核心与外部库的依赖关系应保持绝对最小值。如果您的代码充斥着对依赖注入库的依赖,那将是具有讽刺意味的;用于允许最小化外部依赖性并增加松散耦合的工具。
  2. 要将横切关注点应用于各种类(这是您通常要做的事情),您必须通过完整的代码库来添加或删除方法中的属性。这是耗时且容易出错的。但更糟糕的是,确保方面以特定顺序运行对于属性来说很难。某些框架允许您指定属性的顺序(使用某种Order属性),但更改顺序意味着通过代码进行彻底更改以更改属性的Order。忘记一个会导致错误。这违反了Open/closed principle
  3. 由于该属性引用了方面类(在您的示例中为typeof(MyLoggingClass)),这使得您的代码仍然静态地依赖于交叉关注点。用另一个替换类将再次导致您对代码库进行彻底更改,并且保持硬依赖性使得重用代码或在运行时或部署时决定是否应该应用方面更加困难。在许多情况下,您不能从代码到方面具有此依赖性,因为代码存在于基础库中,而方面特定于应用程序框架。例如,您可能具有在Web应用程序和Windows服务中运行的相同业务逻辑。在Web应用程序中运行时,您希望以不同的方式登录。换句话说,您违反了Dependency inversion principle
  4. 因此,我认为应用属性这种不良做法。 不使用这样的属性,而是使用拦截或装饰器透明地应用横切关注点。装饰器是我的首选方法,因为它们的使用更清洁,更简单,因此更易于维护。装饰器可以在不必依赖任何外部库的情况下编写,因此可以将它们放在应用程序的任何合适位置。然而,装饰者的缺点是,如果您的设计不是SOLIDDRY并且您没有遵循{{3},那么编写和应用它们非常麻烦}}

    但是如果您使用Reused abstraction principle使用SOLID和right application design,您会发现应用横切关注点(例如日志记录)只需要编写一个非常简单的装饰器,例如:

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

    如果没有适当的设计,你仍然可以使用拦截(没有属性),因为拦截允许你装饰&#39;任何似乎在代码中没有关系的类型(共享没有通用接口)。定义要拦截哪些类型以及哪些类型不麻烦,但您通常仍然可以在应用程序的一个位置定义它,因此无需在整个代码库中进行彻底更改。

    侧节点。正如我所说,使用属性来描述message based patterns很好,pure metadata。例如,获取一些仅允许具有特定权限的用户运行的代码。您可以按如下方式标记该代码:

    [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);
        }
    }