Caner Tosuner

Leave your code better than you found it

Castle Windsor ile Exception Handling Interceptor (Dynamic Proxy)

Daha önceki IoC ve AOP yazılarında  çeşitli konulara değinerek örnek projeler üzerinde anlatımlarda bulunduk. Bu yazımızda da Castle Windsor'dan yararlanarak projelerimizde sıklıkla kullanacağımız bir ExceptionAspect veya Interceptor oluşturacağız. 

Interceptor'lar veya Dynamic Proxies Aspect Oriented Programming'in bir implementasyonudur. Bu bize oluşturduğu proxy ile metodu intercept ederek kendi kodlarımızın arasına inject etmemizi sağlar.

IL kodlarına baktığımızda aşağıdaki resimde olduğu gibi geliştirmiş olduğumuz kodları try catch finally blokları arasına alınır.

 

 

 

 

 

 

 

Daha önceki Castle Windsor Kullanarak Cache Interceptor Oluşturma yazısındaki örneğimiz üzerinden ilerleyelim. O yazıda il kodu alarak geriye ilçeleri dönen bir case üzerinden caching işlemi yapan aspect geliştirmiştik. Bu yazımızda da aynı örnek üzerinden ilerleyerek projemiz için bir ExceptionHandling aspect oluşturalım. 

İlk olarak ExceptionHandlingInterceptor adında aspect'imizi oluşturalım.

public class ExceptionHandlingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        try
        {
            invocation.Proceed();      
        }
        catch (Exception ex)
        {
            LogManager.Log("Exception in : " + invocation.Method.Name + " method." + ex);

            invocation.ReturnValue = new BaseResponse { IsSuccess = false };
        }
    }
}

Yukarıdaki kodu incelediğimizde özetle şunu söylüyor ;

  • Invoke edilecek metodu try-catch-finally bloğu arasına al
  • Invoke edilen metot içerisinde hata alırsan catch bloğuna gir ve önce alınan hatayı log'a yaz
  • Sonrasında BaseResponse modelini initialize ederek client'a clear bir response model dön.

Tabi ki de çok daha farklı ayrıntıları log'a yazmanız gerekir ancak sample olduğundan şimdilik bunları yazalım.

İkinci adım olarak yazmış olduğumuz Exception aspect'ini container'a register etmek var.

Castle Windsor Kullanarak Cache Interceptor Oluşturma örneğinde ServiceInstaller class'ını olduğu gibi alıp ExceptionHandlingInterceptor'ını register işlemimizi yapalım.

public class ServiceInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component.For<CacheInterceptor>().LifestyleSingleton());
        container.Register(Component.For<ExceptionHandlingInterceptor>().LifestyleSingleton());

        container.Register(Component.For(typeof(ILocationService))
                 .ImplementedBy(typeof(LocationService))
                 .Interceptors(typeof(CacheInterceptor)
                 .Interceptors(typeof(ExceptionHandlingInterceptor))));
    }
}

Yukarıda ki register işlemi şunu söylüyor; "ILocationService interface'inin implementasyonu LocationService class'ı dır. Bu implementasyona ait 2 adet Interceptor var "CacheInterceptor" ve "ExceptionHandlingInterceptor" adında ve bu interceptor'lar LifeStyleSingleton olarak container'a register edilmişlerdir."

Uygulamamız hazır diyebiliriz. LocationService içerisinde bulunan GetDistrictsByCityCode adlı metodumuzda test etmek için aşağıdaki gibi kendimiz bir exception throw edelim.

public class LocationService : ILocationService
{
    public GetDistrictsByCityCodeResponse GetDistrictsByCityCode(int cityCode)
    {
        throw new NullReferenceException();
    }
}

Projemizi run edip ilgili endpoint'e istekte bulunduğumuzda exception fırlatılıp Interceptor içerisinde bulunan catch bloğuna düşecektir ve loglama işlemini yaptıktan sonra client'a BaseResponse modelini dönecektir.

 

Peki bunları yaparak ne sağladık ? 

  1. Exception Handling işini eski usül projede yüzlerce try-catch bloğu oluşturmak yerine AOP'in faydalarından yararlanarak tek bir yerden yönetebilir duruma getirdik.
  2. Çok daha reusable olan ve başka yerlerde de kullanabileceğimiz bir yapı tasarladık.
  3. Uygulama exception fırlattığında client'a saçma sapan StackTrace mesajı yerine her response da aldığı BaseResponse modelini dönerek response'larımızı daha tutarlı bir hale getirdik.

Postsharp Kullanarak Metot Execution Sürelerini Hesaplama

Daha önceki aspect oriented yazılarımızda ne aspect oriented'ın ne olduğunu ve nasıl kullanıldığını çeşitli örneklerle anlatmıştık ve AOP yaklaşımı ile yapılabileceklerin sınırının olmadığından bahsetmiştik. Bu yazımızda da özellikle server-side geliştirme yapanların sıkça ihtiyaç duyduğu metot execution süresinin AOP yaklaşımı ile nasıl yapıldığından bahsedeceğiz. 

Yazılımda performans bildiğiniz üzre en önemli konuların başında gelir ve büyük çapta 1 den fazla client'ın consume ettiği bir projeniz var ise metot execution sürelerini bir yerlerde logluyor olmak bizler için şu gibi durumlarda hayat kurtarıcı bir rol üstlenir; client der ki "Abi sayfa 8 saniyede açılıyor bizde bir sıkıntı yok service'den geç geliyor....vs.", server-side'da geliştirme yapan arkadaşta der ki "Yok abi ben 0.2 saniyede response dönmüşüm bu bizden değil...vs.". Bu gibi durumlarda eğer siz service olarak projenizden eminseniz bu durumu kanıtlamak için en güzel çözüm metot execution sürelerini hesaplayıp delil olarak ilgili mercilere sunmak. Ya da bu case'i de geçelim bizler developerlar olarak kendimize "arkadaş bi proje yaptık ama işler nasıl gidiyor, yazılımsal olarak performansı yerinde mi.." vs. gibi soruları sorup cevaplarını da biliyor olmamız gerekir.

Bunun için AOP çözümlerinden biri olan Postsharp'dan yararlanacağız. Case şu şekilde olacak; Bir Api projemiz olsun ve MoneyTransfer adında eft-havale-virman işlemlerini yapan end-point geliştirelim ve bu end-point'e gelen request-response'ların execution sürelerini LoggingAspect kullanarak loglayalım.

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

    [Serializable]
    public class LoggingAspect : OnMethodBoundaryAspect
    {
        private readonly Stopwatch timer = new Stopwatch();  

        public override void OnEntry(MethodExecutionArgs args)
        {
            timer.Start();
        }

        public override void OnExit(MethodExecutionArgs args)
        {
           var executionTime = timer.ElapsedMilliseconds;
            timer.Stop();
            var logMessage = "Method : " args.Method.Name +  " Execution Time : " + executionTime + " Millisecond";
            Console.Write(logMessage );
        }
    }

Sırada MoneyTransfer işlemi yapan metodu yazalım ve bu metot parametre olarak TransferRequest objesi alsın ve response olarak TransferResponse adında bir model dönsün.

        [LoggingAspect]
        public TransferResponse MoneyTransfer(TransferRequest request)
        {
            // back-end kodlarınız

            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; }
        }
    }

Hepsi bu kadar artık yazdığımız kodları test edebiliriz. TransferMoney metoduna request yolladığımızda metot scope'ları içerisindeki kodları run etmeden aspect'imiz içerisinde bulunan OnEntry metoduna girecektir ve burada timer'ı start ediyoruz. Sonrasında metot scope'ları içerisinde bulunan kodları çalıştırıp metottan çıktıktan sonra OnExit metoduna girip timer'ı stop edip devamında execution süresini millisecond cinsinden Console'a yazacaktır.

Metot execution süresi yukarıda da belirttiğim gibi önemli bir konudur ve ne nerde ne kadar süre harcayıp client'a gidiyor bunu ölçeklendirebiliyor olmak gerekir.

Not: Çok uzun süren execution sürelerinin çok büyük bir ihtimalle networksel sorunlardan dolayı kaynaklanabileceği ihtimalini de unutmamak gerekir.

Castle Windsor Kullanarak Cache Interceptor Oluşturma

Daha önceki yazılarımızda IoC nedir ve Castle Windsor Kullanarak Cache Interceptor Oluşturma konularına değinmiştik. Bu yazımızda ise yine Windsor kullanarak projelerimizde sıkça kullandığımız bir özellik olan Caching işlemini basitçe yapabilen bir interceptor oluşturacağız.

Bir Api projesi oluşturalım ve projemizde cache ile ilgili işlemlerin yönetildiği ICache isminde aşağıdaki gibi bir interface'imiz olsun ve arka planda kullandığınız herhangi bir cache yapısı olabilir (Redis, memory cache, vs.) bu interface'in implementasyonunu kullandığınız cache yapısına göre yapmış varsayalım.

public interface ICache : IDisposable
{
    object Get(string key);
 
    void Set(string key, object obj, DateTime expireDate);
 
    void Delete(string key);
 
    bool Exists(string key);
}

Şimdi ise projemize Castle'ı nuget'ten indirip kuralım ve sonrasında aşağıdaki gibi CacAttribute adında bir attribute tanımlayalım. Bu attribute'ü kullanarak cache'e atmak istediğimiz metotları bir nevi flag'lemiş olacağız.

    [AttributeUsage(AttributeTargets.Method)]
    public class CacheAttribute : Attribute
    {
        public int CacheDurationInSecond { get; private set; }

        public CacheAttribute(int cacheDurationInSecond = 300)
        {
            CacheDurationInSecond = cacheDurationInSecond;
        }
    }

Örnek olarak projemizde LocationController adında bir controller ve içerisinde GetDistrictsByCityCode adında int cityCode parametresine göre geriye o şehirde bulunan ilçelerin listesini dönen bir end-point tanımlayalım. Projemizde kullanacağımız modellerimizi aşağıdaki gibi oluşturalım.

        public class BaseResponse
        {
            public bool IsSuccess { get; set; }
        }

        public class GetDistrictsByCityCodeResponse : BaseResponse
        {
            public List<string> CityList { get; set; }
        }

Controller'ın kullanacağı ILocationService ve onun implementasyonunu aşağıdaki gibi tanımlayalım.

        public interface ILocationService
        {
            [Cache(300)]
            GetDistrictsByCityCodeResponse GetDistrictsByCityCode(int cityCode);
        }

        public class LocationService : ILocationService
        {
            public GetDistrictsByCityCodeResponse GetDistrictsByCityCode(int cityCode)
            {
                return new GetDistrictsByCityCodeResponse
                {
                    CityList = new List<string> {
                        "Alaçam","Asarcık","Atakum",
                        "Ayvacık", "Bafra", "Canik",
                        "Çarşamba","Havza", "İlkadım",
                        "Kavak","Ladik", "Ondokuzmayıs",
                        "Salıpazarı","Tekkeköy", "Terme",
                        "Vezirköprü", "Yakakent" },
                    IsSuccess = true
                };
            }
        }

Yukarıda görüldüğü üzre GetDistrictsByCityCode metodu üzerinde CacheAttribute'ünü tanımladık ve cache süresi olarak 300 saniye yani 5 dk set ettik. Şimdi ise LocationController'ı oluşturalım ve controller içerisinde tanımlayacağımız end-point ILocationService'i kullanarak GetDistrictsByCityCode metoduna ilgili isteği yapacak. ILocationService'i controller'da kullanabilmek içinde controller'ın constructor'ında seviyesinde ILocationService'i inject edeceğiz.

        public class LocationController : ApiController
        {
            private readonly ILocationService _locationService;
            public LocationController(ILocationService locationService)
            {
                _locationService = locationService;
            }
            [HttpGet]
            public HttpResponseMessage GetDistrictsByCityCode(int cityCode)
            {
                var response = _locationService.GetDistrictsByCityCode(cityCode);
                return Request.CreateResponse(response);
            }
        }

Metodumuz geriye BaseResponse'dan türemiş olan GetDistrictsByCityCodeResponse objesini dönecek ve bu objede bulunan IsSuccess parametresini kontrol ederek modeli cache'e atıp atmamaya karar vereceğiz. Bunu yapmamızın sebebi hatalı response'ları cache atmayı önlemek. LocationService içerisindeki metotta dummy olarak geriye Samsun ilinin ilçelerini return eden bir response oluşturduk ve projemizi deneme amaçlı bunu return edeceğiz.

Sırada cache işlemlerinin yapıldığı aspect'imizi tanımlama var. Aşağıda olduğu gibi CacheInterceptor adında aspect'imizi tanımlayalım.

    public class CacheInterceptor : IInterceptor
    {
        private readonly ICache _cacheProvider;
        private readonly object _lockObject = new object();
        public CacheInterceptor(ICache cacheProvider)
        {
            _cacheProvider = cacheProvider;
        }

        public void Intercept(IInvocation invocation)
        {
            //metot için tanımlı cache flag'i var mı kontrolü yapıldığı yer
            var cacheAttribute = invocation.Method.GetAttribute<CacheAttribute>();
            if (cacheAttribute == null)//eğer o metot cache işlemi uygulanmayacak bir metot ise process normal sürecinde devam ediyor
            {
                invocation.Proceed();
                return;
            }

            lock (_lockObject)
            {
               //eğer o metot cache işlemlerinin yapılması gereken bir metot ise ilk olarak dynamic olarak aşağıdaki gibi bir cacheKey oluşturuyoruz
                var cacheKey = string.Concat(invocation.TargetType.FullName, ".", invocation.Method.Name, "(", JsonConvert.SerializeObject(invocation.Arguments), ")");
                //bu key ile tanımlı bir cache objesi var mı kontrol ediyoruz
                var cachedObj = _cacheProvider.Get(cacheKey);
                if (cachedObj != null)//eğer var ise o objeyi alıp client'a return ediyoruz
                {
                    invocation.ReturnValue = cachedObj;
                }
                else
                {
                    //yok ise metottan çıktıktan sonra return edilen response'u 
                    invocation.Proceed();
                    var returnValue = invocation.ReturnValue;
                    var response = returnValue as BaseResponse;
                    if (response != null)
                    {
                        //eğer o response bizim beklediğimiz gibi BaseResponse'dan türeyen bir model ise o modeli alıyoruz ve  başarılı bir şekilde client'a dönülen bir response ise cache'e atıyoruz
                        if (response.IsSuccess)
                        {
                            var cacheExpireDate = DateTime.Now.AddSeconds(cacheAttribute.CacheDurationInSecond);
                            _cacheProvider.Set(cacheKey, invocation.ReturnValue, cacheExpireDate);
                        }
                    }
                }
            }
        }
    }

Yukarıdaki akışı anlatmak gerekirse;

  • İlk olarak ilgili metot üzerinde bir cache flag'i var mı yok mu ona bakıyoruz. Eğer metot üzerinde tanımlı CacheAttribute'ü var ise o metot bizim için cacheinterceptor tarafından cache işlemlerine tabi tutulur demek. Yoksa hiç cache'i karıştırmadan process devam eder.
  • CacheAttribute varsa sonraki adımda metot namespace + metot ismi + metot parametrelerinden oluşan bir dynamic cacheKey oluşturuluyor ve bu cacheKey ile cache provider'ınızda (redis, memory cache, vs.) daha önce tanımlanmış olup halen geçerliliği olan bir cache objesi var mı bu kontrol yapılıyor.
  • Eğer varsa cache de bulunan obje return ediliyor ve GetDistrictsByCityCode metoduna girmeden client'a cevap dönülüyor.
  • Eğer cache'de bulunan bir obje yoksa GetDistrictsByCityCode metoduna girip ilgili işlemleri yaptıktan sonra metottan çıkıyor. Sonrasında metodun return ettiği model BaseResponse'dan türeyen bir model ise ve IsSuccess'i true ise obje cache'e atılıyor ve daha sonra client'a dönülüyor.

Geriye son adım olarak da ilgili bağımlılıkları tanımlayıp CacheInterceptor'ını install etmek kalıyor.  Aşağıdaki gibi ilk olarak interceptor'ı install ediyoruz ve sonrasında kullanmak istediğimiz LocationService için register ediyoruz.

        public class ServiceInstaller : IWindsorInstaller
        {
            public void Install(IWindsorContainer container, IConfigurationStore store)
            {
                container.Register(Component.For<CacheInterceptor>().LifestyleSingleton());

                container.Register(Component.For(typeof(ILocationService))
                         .ImplementedBy(typeof(LocationService))
                         .Interceptors(typeof(CacheInterceptor)));
            }
        }

Installer'ı container'a tanımlamak için projemizde bulunan Global.asax içerisindeki Application_Start metodu içerisinde container'ı aşağıdaki gibi ayağa kaldırıyoruz.

protected void Application_Start()
{
    var container = new WindsorContainer();
    container.Install(new ServiceInstaller());
    container.Install(new WebApiControllerInstaller());
    GlobalConfiguration.Configuration.Services.Replace(
        typeof(IHttpControllerActivator),
        new ApiControllerActivator(container));
    GlobalConfiguration.Configure(WebApiConfig.Register);
}

Yukarıda bulunan WebApiControllerInstaller ve ApiControllerActivator daha önceki yazımızda  oluşturduğumuz installer ile birebir aynıdır. 

public class WebApiControllerInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly()
            .BasedOn<ApiController>()
            .LifestylePerWebRequest());
    }
}

public class ApiControllerActivator : IHttpControllerActivator
{
    private readonly IWindsorContainer _container;
 
    public ApiControllerActivator(IWindsorContainer container)
    {
        _container = container;
    }
 
    public IHttpController Create(
        HttpRequestMessage request,
        HttpControllerDescriptor controllerDescriptor,
        Type controllerType)
    {
        var controller =
            (IHttpController)this._container.Resolve(controllerType);
 
        request.RegisterForDispose(
            new Release(
                () => this._container.Release(controller)));
 
        return controller;
    }
 
    private class Release : IDisposable
    {
        private readonly Action _release;
 
        public Release(Action release)
        {
            _release = release;
        }
 
        public void Dispose()
        {
            _release();
        }
    }
}

Projemiz hazır ve artık yazmış olduğumuz end-point'i test edebiliriz. GetDistrictsByCityCode metoduna ilk gelen request'ten sonra return edilen response cache'e atılacaktır ve CacheAttribute tanımlarken verdiğiniz cacheDuration süresi içerisindeki ikinci request'i yaptığınızda GetDistrictsByCityCode scope'ları arasındaki işlemleri yapmadan ilgili response oluşturulan cacheKey ile bulup cache'den döndüğünü göreceksinizdir. 

Castle Windsor Kullanarak Logging Interceptor Oluşturma

Daha önceki Aspect Oriented yazılarında interceptor'lardan bahsetmiştik ve biz developer'lar için bulunmaz bir nimet olduğunu söylemiştik. Server-Side bir projede olmazsa olmaz özelliklerin başında gelen Logging, Exception Handling, Caching vs gibi özellikleri çok basit küçük interceptor'lar yazarak uygulamamıza bu özellikleri kazandırabiliriz. Daha önceki yazılarımızda bu ihtiyaçları karşılayabilmek için .Net tarafında oldukça entegrasyonu basit olan Postsharp kütüphanesinden faydalanmıştık. Bugün ki yazımızda ise IoC container'lardan Castle Windsor'ı kullanarak uygulamamız için bir Logging intercepter'ı geliştireceğiz. 

Örneğimiz şu şekilde; Email göndermek için kullanılan basit bir Service projemiz olsun ve parametre olarak Address ve html olarak Content alsın. İlk olarak VS de EmailSender adında bir Api projesi oluşturalım ve projemizin referanslarına Nuget üzerinden Castle Windsor'ın paketlerini indirip kuralım.

Sonrasında SendEmailRequest adında request modelimizi tanımlayalım

    public class SendEmailRequest
    {
        public string Address { get; set; }
        public string Content { get; set; }
    }

Email gönderme işlemini yapacak olan service interface'imiz ve onun impl. class'ını aşağıdaki gibi oluşturalım

    public interface IEmailService
    {
        bool Send(SendEmailRequest reqModel);
    }
    public class EmailService : IEmailService
    {
        public bool Send(SendEmailRequest reqModel)
        {
            //todo return true dedik ancak bu kısımda email göndermek için kullandığınız kodları yazmalıyız. 
            return true;
        }
    }

Email gönderme servisimiz artık kullanıma hazır. Artık bu service'i Api ile dışarıya açma zamanı. Projemize CommunicationController adında bir controller ve bu controller içerisinde HttpPost kabul eden SendEmail adında bir endpoint tanımlayalım.

    public class CommunicationController : ApiController
    {
        private readonly IEmailService _emailService;

        public CommunicationController(IEmailService emailService)
        {
            _emailService = emailService;
        }

        [HttpPost]
        public HttpResponseMessage SendEmail(SendEmailRequest reqModel)
        {
            var result = _emailService.Send(reqModel);
            if (!result)
            {
                return Request.CreateResponse(HttpStatusCode.NotFound);
            }
            return Request.CreateResponse(result);
        }
    }

Yukarıda görüldüğü üzre kullanacağımız IEmailService'ini Castle kullanarak EmailService'ine inject edeceğiz ve controller seviyesinde bu service interface'ini kullanarak email gönderme işlemini yapacağız.

Şimdi sırada LoggingInterceptor'ımızı oluşturma var. Bu interceptor ile log kayıtlarına MethodName, request ise aldığı parametreler ve response için return edilen değer bilgilerini atacağız. 

    public class LoggingInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            var serializer = new JavaScriptSerializer();
            var parametersJson = serializer.Serialize(invocation.Arguments);

            System.Diagnostics.Debug.WriteLine("Request of " + invocation.Method.Name + " is " + parametersJson);

            invocation.Proceed();

            var returnValueJson = serializer.Serialize(invocation.ReturnValue);

            System.Diagnostics.Debug.WriteLine("Response of " + invocation.Method.Name + " is: " + invocation.ReturnValue);
        }
    }

Logları şimdilik sadece Output Window'a yazdırdım ancak gerçek hayatta tabikide NLog vs gibi bir kütüphane kullanıyor olmamızda fayda var. 

Interceptor'ımızda hazır olduğuna göre artık IoC Initialize tarafına geçip gerekli register işlemlerimizi yapabiliriz. ServiceInstaller adında aşağıdaki gibi bir class oluşturalım ve içerisinde service'imizi ve interceptor'ımızı register edelim.

    public class ServiceInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(Component.For(typeof(IEmailService))
                     .ImplementedBy(typeof(EmailService))
                     .Interceptors(typeof(LoggingInterceptor)));
        }
    }

EmailSender servisini ve interceptor'ı register eden installer'ı tanımladık. Şimdi ise Web Api projemizin controller'ını register eden installer'ı oluşturalım.

    public class WebApiControllerInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(Classes.FromThisAssembly()
                .BasedOn<ApiController>()
                .LifestylePerWebRequest());
        }
    }

 

    public class ApiControllerActivator : IHttpControllerActivator
    {
        private readonly IWindsorContainer _container;

        public ApiControllerActivator(IWindsorContainer container)
        {
            _container = container;
        }

        public IHttpController Create(
            HttpRequestMessage request,
            HttpControllerDescriptor controllerDescriptor,
            Type controllerType)
        {
            var controller =
                (IHttpController)this._container.Resolve(controllerType);

            request.RegisterForDispose(
                new Release(
                    () => this._container.Release(controller)));

            return controller;
        }

        private class Release : IDisposable
        {
            private readonly Action _release;

            public Release(Action release)
            {
                _release = release;
            }

            public void Dispose()
            {
                _release();
            }
        }
    }

Son adım olarak ise oluşturduğumuz bu installer'ları container'a Install edeceğiz. Bunun için projemizde bulunan Global.asax içerisinde bulunan Application_Start metodu aşağıdaki gibi olacak.

        protected void Application_Start()
        {
            var container = new WindsorContainer();
            container.Install(new ServiceInstaller());
            container.Install(new WebApiControllerInstaller());
            GlobalConfiguration.Configuration.Services.Replace(
                typeof(IHttpControllerActivator),
                new ApiControllerActivator(container));
            GlobalConfiguration.Configure(WebApiConfig.Register);
        }

Hepsi bu kadardı :) 

Şimdi yazdığımız kodları test edelim. Postman kullanarak controller'da bulunan endpoint'e aşağıdaki gibi bir istek gönderip Output Window dan neler yazdırdığına bir bakalım.

 

Request sonrasında Interceptor araya girerek loglama işlemini aşağıdaki görselde olduğu gibi yapmakta.

Örneğimiz şimdilik burada bitiyor, sizlerde projeleriniz için bir çile haline gelebilme potansiyeli olan Loglama konusunu interceptor kullanarak son derece basit ve reusable hale getirebilirsiniz.

Sonraki yazılarımızda Interceptor kullanarak daha başka neler yapabiliriz fırsat buldukça inceleyeceğiz.

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.

Aspect Oriented Programming Nedir

Aspect Oriented Programming (AOP) tükçesi "Cephe Yönelimli Programlama" bir programlama paradigmasıdır. İsim olarak bazılarımıza yabancı geliyor olabilir çünkü çok yeni bir kavram değil ve gelişen yazılım teknolojileri ve AOP nin daha kolay ve verimli implement edilmesini sağlayacak "PostSharp" gibi tool'ların çıkmasıyla birlikte epey bir önemli hale gelir oldu AOP.

Biz yazılımcılar daha iyi kodlar yazmak için hep kullandığımız bir cümle var "Separation of Concern". AOP'nin çıkış noktası aslında buna dayanıyor diyebiliriz. AOP birbiriyle kesişen ilgilerin (Cross-Cutting Concerns) ayrılması üzerinedir. Uygulama genelinde kullanılacak olan yapıları (logging,exception hand., cache, etc.) core tarafta yazdığımız koddan ayırarak bir çeşit ayrı küçük programcıklar şeklinde yazıp projede kullanmayı hedefler diyebiliriz.

Örnek olarak 70.000 kişinin çalıştığı çok büyük bir holding için uygulama geliştiriyoruz geriye bütün çalışan listesini dönen bir metod yazıyor olalım ve klasik her uygulamada olması gereken belli başlı şeyler vardır; Cache,ExceptionHandling, Logging gibi bizde metodumuzda bunları yapıyor olalım;

 public IEnumerable<Employee> GetEmployeeList()
        {
			//Request'i yapan kişinin yetkisi varmı yokmu kontrol et
			//metoda girerken request'i log'la
			
            try
            {
                var resultList = DbQuery("Select * from Employee"); // database de ki tabloya sorgu attığımız varsayalım ve 70 bin kayıt gelsin
				
                //geriye dönen sonuçları cache'e at bir sonrakine cache'den ver

                return resultList;
            }
            catch (Exception ex)
            {
                // meydana gelen Exception'ı handle edip log'la ve client'a gidecek olan response'u modify et  
                throw;
            }
			
			//metoddan çıkarken response'u log'la
        }
}

Yukarıda bulunan metodu incelediğimizde ne kadar eksik olduğunu görebiliyoruz. Yorum satırlarında yazan işlemler için geliştirmeler yapmamız gerekmekte ancak bu geliştirmeyi nasıl yapacağız  ? CheckUserAuth(), LogRequest(), LogException(), LogResponse(), ModifyResponse() gibi metodlar yazıp bu metodları ilgili yerlerde her metodda yazmak herhalde ilk akla gelen çözüm ancak AOP bize daha farklı şekilde yapmamız gerektiğini söylüyor. Bunları ayrı modüller olarak tasarlayıp daha kullanılabilir, okunabilir ve SOLID prensiplerine uygun geliştirmeler yapmamız gerektiğini söyler.

Peki birbirleri ile çakışan ilgileri birbirlerinden nasıl ayıracağız ? İşte bu noktada karşımıza interceptor çıkmakta.

Interceptor

Interceptor’ belirli noktalarda metot çağrımları sırasında araya girerek çakışan ilgilerimizi işletmemizi ve yönetmemizi sağlamakta. Buda metotların çalışmasından önce veya sonra bir takım işlemleri gerçekleştirebilmemeizi sağlar ve AOP nin yapısı tamamiyle bunun üzerine kurulu desek yanlış olmaz heralde. Interceptor'u implemente etme olayına girmicem çünkü yukarıda da bahsettiğim gibi .Net tarafında Nuget üzerinden indirip kullanabileceğimiz Postsharp kütüphanesi bu işi diplerine kadar yapmakta ve bizlere sadece attribute tanımlamaları yapmayı bırakmakta. 

Şimdi yukarıda yazmış olduğumuz kodu gelin birde AOP standartlarına uygun şekilde yazalım.

        [UserAuthAspect]
        [LoggingAspect]
        [AppCacheAspect(25000)]
        [ExceptionAspect]
        public IEnumerable<Employee> GetEmployeeList()
        {
            var resultList = DbQuery("Select * from Employee");
            return resultList;
        }

[UserAuthAspect] [LoggingAspect] [AppCacheAspect] [ExceptionAspect] attribute'lerini tanımladık ve AOP nin dediği gibi Cross-Cutting yani kesişen yerleri Aspect'ler kullanarak attribute seviyesinde kullanılabilir hale getirdik.Yazmış olduğumuz 2. metot ile 1. metot arasındaki satır sayısı farkına baktığımızda dağlar kadar fark var ve en önemlisi daha okunabilir bir kod yazmış olduk.  

Aspect-Oriented Programming'in Sağladıkları

  1. İçi içe yazılmış ve sürekli tekrar eden kodlardan kurtulabiliyoruz,
  2. Daha temiz ve anlaşılır kodlar yazabiliyoruz,
  3. Yazmış olduğumuz kodları daha abstract hale getirerek modülerliğini arttırıyoruz,
  4. Bakım ve geliştirme maliyetlerini azaltıyoruz,
  5. Uygulamamızı daha yönetilebilir ve daha esnek hale getirebiliyoruz.

Görüldüğü üzre AOP yaklaşımı geliştirdiğimiz uygulamalar için bizlere bir çok faydalar sunmakta ve Postsharp gibi çeşitli tool'lar ile birlikte projenize AOP'ye uygun hale getirmek dahada kolay hale gelmiş durumda. Bundan sonraki AOP ile ilgili yazılarda Postsharp kullanarak Cache, Logging, ExceptionHandling gibi örnekler ile deva ediyor olacağız.