Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
pamswam opened this issue Jun 16, 2020 · 9 comments
Labels

Comments

@pamswam
Copy link

pamswam commented Jun 16, 2020

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
Copy link
Owner

pamidur commented Jun 16, 2020

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
Copy link
Author

pamswam commented Jun 16, 2020

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

@pamidur
Copy link
Owner

pamidur commented Jun 16, 2020

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

@GioviQ
Copy link

GioviQ commented Aug 6, 2020

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
Copy link
Owner

pamidur commented Aug 6, 2020

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

@pamidur
Copy link
Owner

pamidur commented Aug 6, 2020

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
Copy link

GioviQ commented Aug 6, 2020

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
Copy link
Owner

pamidur commented Aug 6, 2020

@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
Copy link
Owner

pamidur commented Oct 18, 2021

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

@pamidur pamidur closed this as completed Oct 18, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants