I've been looking for a specific solution for AOP logging. I need a interception that makes possible do something like this:
[MyCustomLogging("someParameter")]
The thing is, I saw examples in others DI frameworks that makes this possible. But my project is already using Autofac for DI and I don't know if is a good idea mix up with Unity (for example). In Autofac.extras.dynamiclibrary2 the class InterceptAttribute is sealed.
Anyone has an idea for this problem?
Ps.: I would be satisfied with this:
[Intercept(typeof(MyLoggingClass), "anotherParameter"]
Although the use of attributes to enrich types with metadata to feed cross-cutting concerns with data to use isn't bad, the use of attributes to mark classes or methods to run some cross-cutting concern usually is.
Marking code with the attribute like you shown has some serious downsides:
Order
property), but changing the order means making sweeping changes through the code to change the Order
of the attributes. Forgetting one will cause bugs. This is a violation of the Open/closed principle.typeof(MyLoggingClass)
), this makes your code still statically dependent on the cross-cutting concern. Replacing the class with another will again cause you to do sweeping changes to your code base, and keeping the hard dependency makes it much harder to reuse code or decide at runtime or time of deployment whether the aspect should be applied or not. In many cases, you can't have this dependency from your code to the aspect, because the code lives in a base library, while the aspect is specific to the application framework. For instance, you might have the same business logic that runs both in a web application and a Windows service. When ran in a web application, you want to log in a different way. In other words, you are violating the Dependency inversion principle. I therefore consider applying attributes this way bad practice. Instead of using attributes like this, apply cross-cutting concerns transparently, either using interceptions or decorators. Decorators are my preferred approach, because their use is much cleaner, simpler, and therefore more maintainable. Decorators can be written without having to take a dependency at any external library and they can therefore be placed in any suitable place in your application. Downside of decorators however is that it's very cumbersome to write and apply them in case your design isn't SOLID, DRY and you're not following the Reused abstraction principle.
But if you use the right application design using SOLID and message based patterns, you'll find out that applying cross-cutting concerns such as logging is just a matter of writing a very simple decorator such as:
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);
}
}
Without a proper design, you can still use interception (without attributes), because interception allows you to 'decorate' any types that seem to have no relationship in code (share no common interface). Defining which types to intercept and which not can be cumbersome, but you will usually still be able to define this in one place of the application, thus without having to make sweeping changes throughout the code base.
Side node. As I said, using attributes to describe pure metadata is fine and preferable. For instance, take some code that is only allowed to run for users with certain permissions. You can mark that code as follows:
[Permission(Permissions.Crm.ManageCompanies)]
public class BlockCompany : ICommand {
public Guid CompanyId;
}
This attribute does not describe what aspects are run, nor does it reference any types from an external library (the PermissionAttribute
is something you can (and should) define yourself), or any AOP-specific types. It solely enriches the code with metadata.
In the end, you obviously want to apply some cross-cutting concern that checks whether the current user has the right permissions, but the attribute doesn't force you into a specific direction. With the attribute above, I could imagine the decorator to look as follows:
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With