0

I would like to implement and use an AOP logging in my Blazor Server .NET 5 (C# 9) application, for generic enter-exit-exception scenarios for the marked method(s).

I know how to do it with MethodBoundaryAspect.Fody NuGet package and a custom Attribute, but with Dependency Injection I cannot inject an ILogger into the custom Attribute (which lifecycle also not controlled by the build in service provider), so it seems this is not a viable solution anymore. I don't stick to Attribute as a solution, it just seems very practical.

My overall target is to get rid of the boilerplate code lines for general logging in product methods. With another attribute (or something else) elapsed time measuring would be a piece of cake with the desired solution.

Tomcat
  • 38
  • 4
  • Would you be happy with a solution that requires the enclosing type to implement an interface that provides the logger? i.e. make the attribute expect the instance to implement that interface? – Kirk Woll Apr 20 '22 at 20:22
  • If you mean by the enclosing type to provide ILogger as a constructor injection into the Attribute, I've tried that. When I try to put the attribute to a method the compiler arguing with 2 errors: "CS0181: Attribute constructor parameter has type ILogger, which is not a valid attribute parameter type" and "CS0120: An object reference is required for the non-static field, method, or property". How could I inject the ILogger to the attribute from the method annotation point (where I use the ...Attribute) ´[...Attribute(_logger]) private void SomeMethod(...)´ ? – Tomcat Apr 21 '22 at 08:02
  • 1
    No, I mean you have access to the instance in your methods via the [Instance](https://github.com/vescon/MethodBoundaryAspect.Fody/blob/master/src/MethodBoundaryAspect/Attributes/MethodExecutionArgs.cs#L20) property on `MethodExecutionArgs`. Just cast that to a new interface you define (say, `ILoggerProvider`) which would be implemented by the class with the methods you want to log. In other words, don't even bother trying to pass the logger via a constructor. Bypass that problem entirely. – Kirk Woll Apr 21 '22 at 12:23
  • I've tried to solve it also with a static class (based on this [thread](https://stackoverflow.com/questions/48676152/asp-net-core-web-api-logging-from-a-static-class)) and getting an ILoggerFactory through that to the Attribute, but your solution is also totally viable. Maybe even better from the memory usage aspect. How can I mark your comment as a solution? – Tomcat Apr 22 '22 at 14:07
  • You can't, but since it seems to have helped you, I'll write up a summary in an answer. – Kirk Woll Apr 22 '22 at 14:13

1 Answers1

1

It's true that you can't use dependency injection to inject services into the constructor of your attribute. Considering that they represent compile-time meta data this shouldn't be too surprising.

However, if we take a step back, we can take advantage of interfaces in combination with information passed to your method interceptors. We'll focus on OnEntry, but the same will apply to OnExit and OnException.

public virtual void OnEntry(MethodExecutionArgs arg)
{
}

MethodExecutionArgs has a variety of useful properties but we'll just focus on one: the Instance property. Given that this property represents the instance of the class that contains the method being intercepted, you can choose to make some assumptions about what interfaces that class implements.

If you define a new interface, say ILoggerProvider you can have the class that contains the methods you want to log implement this interface to give your aspect a handle to ILogger:

public interface ILoggerProvider
{
    ILogger Logger { get; }
}

Putting this all together, your class should resemble something like this:

public class MyProduct : ILoggerProvider
{
    public ILogger Logger { get; }

    public MyProduct(ILogger logger)
    {
        Logger = logger;
    }

    [Log]
    public void M()
    {
    }
}

And finally, your aspect should look something like:

public class LogAttribute : OnMethodBoundaryAspect
{
    private ILogger GetLogger(MethodExecutionArgs args) => ((ILoggerProvider)args.Instance).Logger;

    public override void OnEntry(MethodExecutionArgs args)
    {
        var logger = GetLogger(args);
        // Log whaat you will
    }

    public override void OnExit(MethodExecutionArgs args)
    {
        var logger = GetLogger(args);
        // Log whaat you will
    }

    public override void OnException(MethodExecutionArgs args)
    {
        var logger = GetLogger(args);
        // Log whaat you will
    }
}

Note: this assumes that the methods you want to intercept are not static methods, as they will obviously have a null Instance.

Kirk Woll
  • 76,112
  • 22
  • 180
  • 195
  • Thanks Kirk for the solution! I would rather write (because of possible static methods usage): `private ILogger GetLogger(MethodExecutionArgs args) => (args.Instance as ILoggerProvider)?.Logger;` and then use like `logger?.LogTrace(...)` but without it, it could work also of course. – Tomcat Apr 28 '22 at 12:37
  • Yeah, it's up to you whether you want failure to implement the interface to lead to simply not logging vs throwing an exception (class cast exception) to indicate that there's a bug somewhere. Either way could make sense depending on your own needs. – Kirk Woll Apr 28 '22 at 12:53