pamidur / aspect-injector

AOP framework for .NET (c#, vb, etc)
Apache License 2.0
745 stars 112 forks source link

How to Inject TelemetryClient (with autoflush) or pass any other runtime object to the aspect (.net core 3.1 console app) #128

Closed pamswam closed 2 years ago

pamswam commented 4 years ago

Hi there, Please can you show some light on how to pass runtime objects (or create and manage logger inside the aspect, ideally a singleton)

Writing to Console.Writeline works, however in production, writing to console is not sufficient.

Many thanks!

pamidur commented 4 years ago

Hi @pamswam,

Ok let's see what options we have:

Option 1: Create and manage logger inside aspect:

This is quite easy provided you use global aspects. Global aspects are singletons by themselves. So everything you create in their constructor are singletons as well.

    [Aspect(Scope.Global)]
    public class LogAspect : Attribute
    {
        private readonly TelemetryClient_logger;

        public LogAspect(ILogger logger)
        {
            _logger = new TelemetryClient();
        }
...

You can still use Scope.PerInstance but then make sure you manage your instance TelemetryClient properly (e.g. create one in static constructor of an Aspect)

Option 2: Manage runtime objects with IoC container and inject all you need trough constructor:

You need a static class with a static method:

 public static object GetInstance(Type type)

Then tell your aspect to use this class as a factory (use can use Global scope as well):

    [Aspect(Scope.PerInstance, Factory = typeof(ApplicationServices))]
    public class LogAspect

Then request TelemetryClient from constructor

        private readonly TelemetryClient _logger;
        public LogAspect(TelemetryClient logger)
        {
            _logger = logger;
        }

Then you can use any IoC container to setup your services, in example below I use Microsoft.Extensions.DependencyInjection. It requires all services to be registered, so I had to register both LogAspect and TelemetryClient

                .AddTransient<LogAspect>()
                .AddSingleton<TelemetryClient>()

One thing to pay attention to is since Aspect has constructor injection, you can't have it as Attribute. so you need a separate Attribute like this:

    [Injection(typeof(LogAspect))]
    public class LogAttribute : Attribute
    {
    }

Full code below (you can copy/paste to experiment yourself):

    public static class ApplicationServices
    {
        public static readonly ServiceProvider ServiceProvider;

        static ApplicationServices()
        {
            ServiceProvider = new ServiceCollection()
                .AddTransient<LogAspect>()
                .AddSingleton<TelemetryClient>()
                .BuildServiceProvider();
        }

        public static object GetInstance(Type type) => ServiceProvider.GetRequiredService(type);
    }

    class Program
    {
        static void Main(string[] args)
        {
            new TestClass().Do();
        }
    }

    [Log]
    public class TestClass
    {
        public void Do() { }
    }

    [Injection(typeof(LogAspect))]
    public class LogAttribute : Attribute
    {
    }

    [Aspect(Scope.PerInstance, Factory = typeof(ApplicationServices))]
    public class LogAspect
    {
        private readonly TelemetryClient _logger;

        public LogAspect(TelemetryClient logger)
        {
            _logger = logger;
        }

        [Advice(Kind.Before)]
        public void LogEnter([Argument(Source.Name)] string name)
        {
            _logger.TrackEvent($"Calling '{name}' method...");  
        }
    }
pamswam commented 4 years ago

Awesome! Will give a try! Thank you for the super quick response and thanks a bunch for this awesome library!

pamidur commented 4 years ago

Thanks for using it, it encourages me to develop it further. Feel free to share your questions, feedback and feature requests!

GioviQ commented 4 years ago

I started with UniversalWrapper sample with logger injected as above. But in static method WrapAsync I cannot use not static variable. Sure I am missing something... Are statics in UniversalWrapper mandatory?

pamidur commented 4 years ago

@GioviQ , statics aren't mandatory at all, feel free to make them instance methods.

pamidur commented 4 years ago

There are a few mandatory things in AspectInjector e.g. 'Around advice should return object', but these limitations are enforced by both compiler and roslyn analyzer. So as a rule - if it compiles successfully - it is valid code for aspect injector :)

GioviQ commented 4 years ago

Thank you @pamidur it's working now. See https://github.com/enkodellc/blazorboilerplate/blob/development/src/Server/BlazorBoilerplate.Server/Aop/ApiResponseExceptionAspect.cs

Maybe it can be written with less code.

pamidur commented 4 years ago

@GioviQ Looks good to me. Currenty I am afraid this is least possible amount of code, although we have plans for generic Around advices for future releases.

pamidur commented 2 years ago

Moved here #166 and see also #148 Feel free to reopen if needed