Caner Tosuner

Leave your code better than you found it

Caching With Postsharp

Cache bir proje için olmazsa olmazlardan biridir diyebiliriz ve ihtiyaç duyulduğunda çok can kurtarır. Öyleki çok fazla değişmesini düşünmediğiniz verileri cache de saklayarak reqeust response time konusunda projenize oldukça fazla performans kazandırabilirsiniz. Daha önceki web api için cache ve aspect oriented - postsharp konularından bahsetmiştik. WCF Service projenize Postsharp ile basit bir server-side cache yapısı kurabilirsin.

İlk adım olarak cache de tutacağımız CacheModel adlı model'i tanımlayalım.

public class CacheModel
{
    public DateTime CacheTimeOutInfo { get; set; }//cache'in ne zaman timeout olacağını belirten time bilgisi
    public object CacheData { get; set; }//cache'de tutulacak object
}

Şimdi ise CacheManager'a implement edeceğimiz ICache interface'i tanımlayalım ve içerisine cache metotlarımızı yazalım.

public interface ICache
{
   bool Contains(string key);//key varmı yokmu diye control ettiğimiz metot
   void Add(string key, CacheModel cacheData);//cache key'i ile birlikte cache model'i alıp cache'e ekleyen metot
   object Get(string key);//key parametresi alarak cache'de ki data yı return eden metot
   void Remove(string key);//key parametresine göre mevcut cache'i silen metot
   void RemoveAll();//bütün cache'i silen metot
}

Aşağıda yazacağımız class'ta ise üstte yazdığımız ICache'i implement eden cache yönetimini yapacağımız CacheManager class'ını oluşturacağızz.

    public class CacheManager : ICache
    {
        public bool Contains(string key)
        {
            var isExist = HttpRuntime.Cache[key];
            if (isExist != null)
            {
                var obj = GetCacheModel(key);
                if (obj.CacheTimeOutInfo > DateTime.Now)
                    return true;
                Remove(key);
            }
            return false;
        }

        public void Add(string key, CacheModel cacheData)
        {
            if (Contains(key))
                Remove(key);
            HttpRuntime.Cache[key] = cacheData;
        }

        public object Get(string key)
        {
            var obj = GetCacheModel(key);
            return obj.CacheData;
        }

        private CacheModel GetCacheModel(string key)
        {
            return (CacheModel)HttpRuntime.Cache[key];
        }

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

        public void RemoveAll()
        {
            var enumerator = HttpRuntime.Cache.GetEnumerator();
            while (enumerator.MoveNext())
            {
                HttpContext.Current.Cache.Remove((string)enumerator.Key);
            }
        }
    }

Sırada Postsharp kullanarak oluşturacağmız CacheAspect adında interceptor'ı yazama var. Bunun için nuget'ten postsharp'ı indiriyoruz ve aşağıdaki gibi CacheAspect adındaki interceptor'ımızı oluşturuyoruz.

    [Serializable]
    public class CacheAspect : OnMethodBoundaryAspect
    {
        //Metottan alacağımız cache süresi
        public int MethodCacheDurationInMinute = 0;

        public override void OnEntry(MethodExecutionArgs args)
        {
                 //cache key oluşturma
                var cacheKey = CreateMD5(GenerateCacheKey(args));

                var cacheManager = new CacheManager();

                var isCacheKeyExist = cacheManager.Contains(cacheKey);

                if (isCacheKeyExist)
                { 
                    //eğer request geldiğinde cache key cache de varsa client'a cahe de bulunan veriyi dön
                    args.ReturnValue = cacheManager.Get(cacheKey);
                    args.FlowBehavior = FlowBehavior.Return;
                }
                else
                {
                    //cache de yoksa normal akışta ilerlemesine devam eder
                    args.MethodExecutionTag = cacheKey;
                }
        }

        public override void OnSuccess(MethodExecutionArgs args)
        {
                //başarılı bir şekilde metottan çıktıysa cache'e at

                    var cacheKey = (string)args.MethodExecutionTag;

                    var cacheManager = new CacheManager();
                    var cacheModel = new CacheModel
                    {
                        CacheData = args.ReturnValue,
                        CacheTimeOutInfo = DateTime.Now.AddMinutes(MethodCacheDurationInMinute)
                    };
                    cacheManager.Add(cacheKey, cacheModel);
        }

        private static string GenerateCacheKey(MethodExecutionArgs args)
        {
            var generatedKey = string.Empty;

            if (args.Method.DeclaringType != null)
                generatedKey = args.Method.DeclaringType.Name + "-" + args.Method.Name + (args.Arguments.Any() ? "-" + args.Arguments.Aggregate((first, second) => first + "-" + second) : "");
            generatedKey += SerializeObjectToJson(args.Arguments);

            return generatedKey;
        }

        public static string CreateMD5(string input)
        {
            using (var md5 = System.Security.Cryptography.MD5.Create())
            {
                var inputBytes = Encoding.ASCII.GetBytes(input);
                var hashBytes = md5.ComputeHash(inputBytes);

                var sb = new StringBuilder();
                foreach (var t in hashBytes)
                {
                    sb.Append(t.ToString("X2"));
                }
                return sb.ToString();
            }
        }
    }

Artık sıra cache'i projemizde kullanmaya geldi. yazdığımız interceptor'ı projede hangi metodu cache'e atmak istiyorsak metodun başına attribute olarak ekleyip kaç dakikalık cache'de tutacağını belirteceğiz.

        [CacheAspect(MethodCacheDurationInMinute = 120)] //120 dakikalığına cachelemesini söylüyoruz
        public List<FooModel> Foo(FooRequest reqModel)
        {
            var fooManager=new FooManager();
            return fooManager.GetFooList();
        }

 

Kullanım olarak oldukç basit ve projede hangi metotta istersek aynı yukarıda olduğu gibi attribute olarak projemize ekleyebiliriz. Yazının başında da belirttiğim gibi cache bir çok zaman hayat kurtarır ve yabana atılmaması gereken bir konudur.

 

Yorum ekle

Loading