pamidur / aspect-injector

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

Get value from Aspect #106

Closed murseld closed 4 years ago

murseld commented 4 years ago

How can i get values from aspect constructor when i use it ?

[CacheAspect(typeof(MemoryCacheManager), 30)] public Student Save(Student model) { return model; } Cache.txt

for example i want to use Microsoft Memory Cache and cacheMinutes equal 30 minutes and i want to set this settings on aspect.

pamidur commented 4 years ago

Hi,

There is Argument(Source.Triggers) that you can use to get all the attributes that lead your aspect's advice to be triggered.

    [Injection(typeof(CacheAspect))]
    class Cache: Attribute
    {
        public Cache(string data)
        {
            Data = data;
        }

        public string Data { get; }
    }

    [Aspect(Scope.Global)]
    class CacheAspect
    {
        [Advice(Kind.Before)]
        public void LogCall([Argument(Source.Triggers)] Attribute[] triggers)
        {
            // Note you can have as many triggers as you want. You can get here all of them.
            // Despite the number of triggers the aspect will be executed only once.

            var trigger = triggers.OfType<Cache>().FirstOrDefault();

            if (trigger!=null) // If aspect is triggered with cache attribute.
            Console.WriteLine($"Trigger's data is: {trigger.Data}");
        }
    }

    class Program
    {
        [Cache("My parameter")]
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
murseld commented 4 years ago

thank you pamidur but i cant work it. because i need executing just one time like RuntimeInitialize method on Postsharp. Because i have to get instance from CacheManager on this method. I am sharing you postsharp code please review it because i want to left postsharp it is not working on linux os.

thank you..

new_code.txt postsharp_code.txt

pamidur commented 4 years ago

The thing is that AspectInjector unlike Postsharp doesn't mess with your code in runtime. That is why "method initialization" is done on compilation. Another difference is that attribute in AspectInjector in only a trigger. The life of the attribute instance end on Advice method end. This means that you cannot store ref to cache manager in an attribute. But you can store it aspect itself.

Now aspect can be Global(Singleton) or PerInstance(aspect instance per class instance). The latter mean per target class, and not per trigger(attribute). This feature I'll add in the future, thanks for the idea :)

This means that some things should be done a bit different in AspectInjector.

Like this:

using AspectInjector.Broker;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Reflection;
using System.Runtime.Caching;
using System.Text.RegularExpressions;

namespace AspectInjectorTest
{

    public class Program
    {
        public static void Main()
        {
            var p = new Program();
            Console.WriteLine("1+2:");
            var r1 = p.TestCalculation(1, 2);

            Console.WriteLine();
            Console.WriteLine("1+2:");
            var r2 = p.TestCalculation(1, 2);

            Console.WriteLine();
            Console.WriteLine("2+2:");
            var r3 = p.TestCalculation(2, 2);

            Console.ReadLine();
        }

        [Cache(typeof(MemoryCacheManager), 1)]
        public int TestCalculation(int a, int b)
        {
            Console.WriteLine("Real calculation.");
            return a + b;
        }
    }

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    [Injection(typeof(CacheAspect))]
    public class Cache : Attribute
    {

        public Cache(Type cacheType, int cacheByMinute)
        {
            //add null checks?

            CacheType = cacheType;
            CacheByMinute = cacheByMinute;
        }

        public Type CacheType { get; }
        public int CacheByMinute { get; }
    }

    [Aspect(Scope.Global)]
    public class CacheAspect : Attribute
    {
        private readonly ConcurrentDictionary<Type, ICacheManager> _cacheManagers
            = new ConcurrentDictionary<Type, ICacheManager>();

        [Advice(Kind.Around, Targets = Target.Method)]
        public object OnInvoke(
            [Argument(Source.Metadata)] MethodBase metadata,
            [Argument(Source.Arguments)] object[] arguments,
            [Argument(Source.Instance)] object target,
            [Argument(Source.Target)] Func<object[], object> method,
            [Argument(Source.Triggers)] Attribute[] triggers
            )
        {
            var cacheTriggers = triggers.OfType<Cache>().ToArray();

            if (cacheTriggers.Length != 0)
            {
                var methodName = string.Format("{0}.{1}.{2}",
                    metadata.ReflectedType.Namespace,
                    metadata.ReflectedType.Name,
                    metadata.Name);

                // Warning! not every type gives accurate ToString() results, you might want to serialize the args or even better take their hashcode
                var key = string.Format("{0}({1})", methodName,
                     arguments.Select(x => x != null ? x.GetHashCode() : 0).Sum());

                // method might be cached in different caches (memory, file, redis) with different settings at the same time
                foreach (var trigger in cacheTriggers)
                {
                    var cache = GetCacheManager(trigger.CacheType);

                    if (cache.IsAdd(key))
                    {
                        Console.WriteLine($"Result from cache {trigger.CacheType.Name}");
                        return cache.Get<object>(key);
                    }
                }

                //if no cache has a result
                Console.WriteLine("No results found in cache");

                var result = method(arguments);

                //add result to every cache
                foreach (var trigger in cacheTriggers)
                {
                    GetCacheManager(trigger.CacheType).Add(key, result, trigger.CacheByMinute);
                    Console.WriteLine($"Storred in {trigger.CacheType.Name} for {trigger.CacheByMinute} min");
                }

                return result;
            }

            //this executes in unlikely case when this aspect is triggered not by Cache attribute, but something else
            return method(arguments);
        }

        public ICacheManager GetCacheManager(Type cacheType)
        {
            if (typeof(ICacheManager).IsAssignableFrom(cacheType) == false)
                throw new Exception("Wrong Cache Manager");

            return _cacheManagers.GetOrAdd(cacheType, t => (ICacheManager)Activator.CreateInstance(t));
        }

    }

//no changes below that line
    public interface ICacheManager
    {
        T Get<T>(string key);
        void Add(string key, object data, int cacheTime);
        bool IsAdd(string key);
        void Remove(string key);
        void RemoveByPattern(string pattern);
        void Clear();
    }

    public class MemoryCacheManager : ICacheManager
    {

        protected ObjectCache Cache => System.Runtime.Caching.MemoryCache.Default;

        public T Get<T>(string key)
        {
            return (T)Cache[key];
        }

        public void Add(string key, object data, int cacheTime = 60)
        {
            if (data == null)
            {
                return;
            }

            var policy = new CacheItemPolicy { AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(cacheTime) };
            Cache.Add(new CacheItem(key, data), policy);
        }

        public bool IsAdd(string key)
        {
            return Cache.Contains(key);
        }

        public void Remove(string key)
        {
            Cache.Remove(key);
        }

        public void RemoveByPattern(string pattern)
        {
            var regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
            var keysToRemove = Cache.Where(d => regex.IsMatch(d.Key)).Select(d => d.Key).ToList();

            foreach (var key in keysToRemove)
            {
                Remove(key);
            }
        }

        public void Clear()
        {
            foreach (var item in Cache)
            {
                Remove(item.Key);
            }
        }
    }
}
murseld commented 4 years ago

Hi @pamidur thank you very much its working. this will be excelent then postsharp because you are the best :) thanks for interesting...

pamidur commented 4 years ago

I was glad to help, thanks for using AspectInjector!