具有自定义属性的Autofac拦截

Raf*_*nda 4 c# aop custom-attributes autofac

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

[MyCustomLogging("someParameter")]
Run Code Online (Sandbox Code Playgroud)

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

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

Ps.:我对此感到满意:

[Intercept(typeof(MyLoggingClass), "anotherParameter"]
Run Code Online (Sandbox Code Playgroud)

Ste*_*ven 11

虽然使用属性的丰富类型的元数据来养活数据使用横切关注点也不错,使用属性标记类或方法来运行一些横切关注点通常是.

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

  1. 它使您的代码依赖于使用的拦截库,使代码更难更改并使更换外部库变得更加困难.应用程序核心与外部库的依赖关系应保持绝对最小值.如果您的代码充斥着对依赖注入库的依赖,那将是具有讽刺意味的; 用于允许最小化外部依赖性和增加松散耦合的工具.
  2. 要将横切关注点应用于各种类(这是您通常想要做的事情),您必须通过完整的代码库来添加或删除方法中的属性.这是耗时且容易出错的.但更糟糕的是,确保方面以特定顺序运行对于属性来说很难.某些框架允许您指定属性的顺序(使用某种Order属性),但更改顺序意味着通过代码进行彻底更改以更改Order属性.忘记一个会导致错误.这违反了开放/封闭原则.
  3. 由于该属性引用了方面类(在您的示例中typeof(MyLoggingClass)),因此您的代码仍然静态地依赖于交叉关注点.用另一个替换类将再次导致您对代码库进行彻底更改,并且保持硬依赖性使得重用代码或在运行时或部署时决定是否应该应用方面更加困难.在许多情况下,您不能从代码到方面具有此依赖性,因为代码存在于基础库中,而方面特定于应用程序框架.例如,您可能具有在Web应用程序和Windows服务中运行的相同业务逻辑.在Web应用程序中运行时,您希望以不同的方式登录.换句话说,您违反了依赖性反转原则.

因此,我认为应用属性这种不好的做法.不使用这样的属性,而是使用拦截或装饰器透明地应用横切关注点.装饰器是我的首选方法,因为它们的使用更清洁,更简单,因此更易于维护.装饰器可以在不必依赖任何外部库的情况下编写,因此可以将它们放在应用程序的任何合适位置.然而,装饰器的缺点是,如果您的设计不是SOLID,DRY并且您没有遵循Reused抽象原则,那么编写和应用它们非常麻烦.

但是如果您使用SOLID和基于消息的模式使用正确的应用程序设计,您会发现应用横向关注点(例如日志记录)只需要编写一个非常简单的装饰器,例如:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

侧节点.正如我所说,使用属性来描述纯元数据是好的,也是可取的.例如,获取一些仅允许具有特定权限的用户运行的代码.您可以按如下方式标记该代码:

[Permission(Permissions.Crm.ManageCompanies)]
public class BlockCompany : ICommand {
    public Guid CompanyId;
}
Run Code Online (Sandbox Code Playgroud)

此属性不描述运行哪些方面,也不引用外部库中的任何类型(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);
    }
}
Run Code Online (Sandbox Code Playgroud)