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.

 

PostSharp Kullanarak Metot Parametre Null Kontrolü

Daha önceki Aspect Oriented Programming'den ve Postsharp tool'undan bahsetmiştik. Postsharp ile Log, Exception Handling ve metot bazlı Authentication kontrolü gibi işlemleri yapabildiğimizden bahsetmiştik. Bu yazımızda ise Postsharp kullanarak metot bazlı parametre kontrolü (null mı değil i vs. gibi) nasıl yapabiliriz inceleyeceğiz.

Server side geliştirmesi yapmış arkadaşlar bilirlerki metotlarda gelen objeler için validation kontrolü yapmak bazen bizi çok yorabilir veya tahmin ettiğimizden daha fazla zaman aldırabilir. Postsharp ın OnEntry() metodunu kullarak gelen parametler için null mı değil mi diye kolayca kontrol edebiliriz.

Bunun için ilk olarak NullParamsControllingAspect adında OnMethodBoundaryAspect ten inherit olan class'ı oluşturalım ve içeriği aşağıdaki gibi olacaktır. Metoda yollanan null parametleri tespit edip string message olarak client'a ArgumentException fıraltarak bilgilendirme mesajını dönecektir.

[Serializable]
    public class NullParameterControllerAspect: OnMethodBoundaryAspect
    {
        public override void OnEntry(MethodExecutionArgs args)
        {
            var messages = new StringBuilder();

            for (int i = 0; i < args.Arguments.Count; i++)
            {
                if (args.Arguments.GetArgument(i) == null)
                {
                    messages.AppendFormat(args.Method.Name +" metodunda bulunan \"{0}\" parametresi null olamaz.\n", args.Method.GetParameters()[i].Name);
                }
            }

            Console.WriteLine(messages);

            if (messages.Length > 0)
            {
                throw new ArgumentException(messages.ToString());
            }
        }

Yukarıda görüldüğü gibi OnEntry() metodunda gelen parametreyi alıp null mı diye kontrol ediyoruz.

Test için aşağıdaki gibi Prgram.cs class'ını oluşturalım. TransferRequest adında bir class'ımız olsun ve bu class'ı null set ederek ve MoneyTransfer metodunu çağıralım.

 class Program
    {
        static void Main(string[] args)
        {
            TransferRequest req_null = null;
            MoneyTransfer(req_null, "Test");//null obje kullanarak metot çağırımı
        }

        [NullParameterControllerAspect]
        public static void MoneyTransfer(TransferRequest request, string param)
        {
            //transfer işlemleri
        }

        public class TransferRequest
        {
            public decimal Amount { get; set; }
            public string SenderIBAN { get; set; }
            public string ReceiverIBAN { get; set; }
        }
    }

 Projenizi çalıştırdığınızda ekran çıktısı aşağıdaki gibi olacaktır. 

PostSharp Kullanarak Request Response Loglama

Çok küçük projeleri saymazsak Log tutmak bir projede olmazsa olmazlardandır. Hele ki o proeje bir enterprise proje ise müşteri size burdan kuş bile geçse onun log'unu görmek istiyorum gibi bir cümle dahi kurabilir.Bu tür bir projede müşterinin loglamanızı isteyeceği şeylerin başında yapılan request ve response'lar gelir. Metota gelen request ve response'u loglamak için Aspect Oriented yazımızda da bahsettiğim üzre AOP paradigmasından yararlanarak request response log'larını tutabiliriz. Bunun için Postsharp kullanıcaz.

Postsharp ile Excetion Handling yazısında da bahsettiğimiz gibi OnMethodBoundaryAspect class ile sahip olduğumuz bazı metotlar vardı. Exception Handling için OnException metodunu kullanmıştık. Request Response log için OnEntry ve OnExit diye 2 metot mevcut. İsimlerinden de anlaşıldığı üzre bu metotlar ilgili metota giriş ve metottan çıkış anında bir takım işlemler yapmamızı sağlar.

*PostSharp kurulumu vs bilgileri için şu yazıyı inceleyebilirsiniz.

Case'imiz şöyle olsun TracingAspect adında OnMethodBoundaryAspect den inherit olan bir class tanımlayalım ve OnEntry & OnExit metotlarını override edelim ve bu metotlar içerisinde gelen request response'u JSON formatında loglayalım.

    [Serializable]
    public class TracingAspect : OnMethodBoundaryAspect
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        public override void OnEntry(MethodExecutionArgs args)
        {
            var jsonRequest = serializer.Serialize(args.Arguments);
            LogMessage(jsonRequest);
        }

        public override void OnExit(MethodExecutionArgs args)
        {
            var jsonResponse = serializer.Serialize(args.ReturnValue);
            LogMessage(jsonResponse);
        }

        private void LogMessage(string message)
        {
            Console.Write(message);
        }
    }

 

Şimdi ise EFT işlemi yapan bir metot yazalım ve bu metot parametre olarak TransferRequest objesi alsın ve response olarak TransferResponse adında bir model dönsün.

 class Program
    {
        static void Main(string[] args)
        {
            var request = new TransferRequest
            {
                Amount = 540,
                ReceiverIBAN = "TR33 0006 1005 1978 6457 8413 26",
                SenderIBAN = "TR33 0006 1005 1978 6457 8413 26"
            };

            var resp = MoneyTransfer(request);
        }

        [TracingAspect] //Aspect'i ekliyoruz
        public static TransferResponse MoneyTransfer(TransferRequest request)
        {
            var resp = new TransferResponse
            {
                IsSuccess = true,
                Message = "Transfer İşleminiz Gerçekleşti."
            };
            return resp;
        }

        public class TransferRequest
        {
            public decimal Amount { get; set; }
            public string SenderIBAN { get; set; }
            public string ReceiverIBAN { get; set; }
        }

        public class TransferResponse
        {
            public bool IsSuccess { get; set; }
            public string Message { get; set; }
        }
    }

Main fonksiyon içerisinde MoneyTransfer metoduna istekte bulunduğumuzda Metota girerken OnEntry'ye, metotdan çıkarken de OnExit'e düşecektir ve LogMessage fonsiyonuna aşağıdaki gibi message parametlerini gönderecektir.

OnEntry => 

  • [{"Amount":540,"SenderIBAN":"TR33 0006 1005 1978 6457 8413 26","ReceiverIBAN":"TR33 0006 1005 1978 6457 8413 26"}]

OnExit => 

  • {"IsSuccess":true,"Message":"Transfer İşleminiz Gerçekleşti."}
     

Görüldüğü üzre Postsharp sayesinde request ve response log tutma işlemini çok kolay bir şekilde halledebiliyoruz ve aynı zamanda AOP bizlere reusable aspect'ler yazın diyor bizde TracingAspect'i uyulamada istediğimiz her hangi bir yerde kullanıp log tutma işlemini tek bir yerden yönetebiliriz.

PostSharp Kullanarak Exception Handling ve Log İşlemi

Daha önce Aspect-Oriented Programming yazımızda AOP'den bahsetmiştik ve AOP'nin .Net tarafında uyarlanabilmesini sağlayan tool'lar ve bu tool'ların en yaygın olanı Postsharp olduğunu söylemiştik. Bu yazıda Postsharp kullanarak uygulamamızda meydana gelecek olan exception'ları yakalamayı sağlayan bir geliştirme yapacağız.

Başlarken

Öncelikle VS'yu açıp yeni bir proje oluşturuyoruz.

  • File > New > Project > Console Application

İsim olarak ben "PostSharpExcHand" dedim siz istediğiniz bir ismi verebilrisiniz, sonrasında Ok'e tıklayıp projeyi oluşturuyoruz.

Daha sonra Postsharp'ı projemize ekleme işlemi var. Bunun için;

  • Tools > NuGet Package Manager > Manage NuGet Packages for Solution

Search kısmına "PostSharp" yazıp çıkan sonuçlar arasından postsharp'ı bulup indiriyoruz sornasında solution'da bulunan hangi projeye eklemek istiyorsak onu seçip yüklemi işlemini bitiriyoruz.

Aspect Tanımlama

AOP yazımızda aspect'lerden bahsetmiştik. Projemizde kullanacağımız reusable modülleri aspectler halinde tanımlayıp kullanmak istediğimiz yerlerde aynı metot üstünde attribute tanımlarmış gibi eklediğimizden bahsetmiştik. Bu yazıda Exception anında çalışacak olan aspect'i tanımlicaz. Bunun için projeye ExceptionHandlerAspect adında bir class ekleyelim. Postsharp ile birlikte gelen OnMethodBoundaryAspect adında bir class var ve bu class'tan inherit olan class'lar aşağıda da görüldüğü gibi override edebileceği bazı metodlara sahip oluyorlar.

Bu metotları kullanarak log,exception handling gibi bir çok ihtiyaca çözüm üretebilirsiniz. Biz bu yazıda exceptionHandling işlemi yapacağımız için oluşturduğumuz ExceptionHandlerAspect class'ını OnExceptionAspect class'ından türetiyoruz ve bu metotla birlikte gelen OnException metodunu override ediyoruz.

   [Serializable]
    public class ExceptionHandlerAspect  : OnExceptionAspect
    {
        public override void OnException(MethodExecutionArgs args)
        {
            string msg =  string.Format("{0}: Error running {1}. {2}{3}",DateTime.Now, args.Method.Name, args.Exception.Message, args.Exception.StackTrace);
            Debug.WriteLine(msg);
            args.FlowBehavior = FlowBehavior.Continue;
        }

Yukarıda görüldüğü gibi uygulamada exception meydana geldiğinde OnException metoduna düşecektir ve burda ihtiyaca göre log atma vs. gibi işlemleri yapabliriz.

Tanımladığımız Aspect'i Kullanma

Şimdi ise sırada tanımladığımız Aspect'i projede kullanma var. Bunun için uygulamada Exception olabilieceğini düşündüğümüz metotların başına attribute olarak ExceptionHandlerAspect'i ekleyebilriz.

    class Program
    {
        static void Main(string[] args)
        {
            ThrowSampleException();
        }
 
        [ExceptionHandlerAspect]//Attribute olarak Aspect'i ekledik
        private static void ThrowSampleException()
        {
            throw new NotImplementedException ();
        }
    }

Görüldüğü üzre yukarıda bulunan ThrowSampleException()  içerisinde exception alındığında üzerinde tanımlı bulunan ExceptionHandlerAspect'inden dolayı bu class içerisine gidip OnException metoduna düşecektir ve ilgili exception açıklamasını alıp artık canınız ne isterse onu yapabilirsiniz :) Log atabilirsiniz, çalışmasını istediğiniz ayrı bir process olabilir onu çalıştırabilirsiniz vs. vs.

ThrowSampleException IL Kodları

ExceptionHandlerAspect ThrowSampleException metoduna ne kazandırdı diye IL kodlarına bakacak olursak aslında try/catch kullandı ve metot içerisinde bulunan kodları try bloğunun içerisine alarak meydana gelecek olan exception'lar için catch bloğunda bunları handle eden kodları yazdı.

private static void ThrowSampleException()
{
    try
    {
        throw new NotImplementedException();
    }
    catch (NotImplementedException exception)
    {
        MethodExecutionArgs methodExecutionArgs = new MethodExecutionArgs(null, null);
        MethodExecutionArgs arg_1F_0 = methodExecutionArgs;
        MethodBase m = Program.<>z__Aspects.m2;
        arg_1F_0.Method = m;
        methodExecutionArgs.Exception = exception;
        Program.<>z__Aspects.a0.OnException(methodExecutionArgs);
        switch (methodExecutionArgs.FlowBehavior)
        {
        case FlowBehavior.Default:
        case FlowBehavior.RethrowException:
            IL_55:
            throw;
        case FlowBehavior.Continue:
            methodExecutionArgs.Exception = null;
            return;
        case FlowBehavior.Return:
            methodExecutionArgs.Exception = null;
            return;
        case FlowBehavior.ThrowException:
            throw methodExecutionArgs.Exception;
        }
        goto IL_55;
    }
}

Reusability

Evet arkadaşlar aslında AOP kullanarak exception handling yapmamızda ki en büyük amaç bu Reusability. Tanımlamış olduğumuz aspect'i artık projenin her yerinde kullanabiliriz ve bu durum bizi çookk ama çok büyük bir iş yükünden kurtarmış olup spaghetti code'lar yazmamıza engel olmuştur.

Son olarak; AOP candır.. :)