22 Nisan 2016 Cuma

MVC Output Cache Kullanımı

Öncelikle herkese merhabalar smiley Bu makalemde Asp.Net MVC uygulamalarında sıklıkla kullanılan bir önbellekleme yöntemi olan OutputCache filter’ının kullanımını inceliyor olacağız. Web projelerinin en büyük sıkıntıları hiç şüphesiz performans kayıplarıdır. Bunu büyük ölçüde absorbe  etmek içinde doğru yerlerde olma şartıyla caching işlemleri yapılır.
Örneğin günde yüzbinlerce kişinin girdiği, hepsiburada.com gibi bir siteyi ele alalım. Kategoriler arasında dolaşıyor, ürünleri inceliyor ve sipariş veriyorsunuz. Ancak hangi sayfada olursanız olun üst tarafta kategorileri hep görüyorsunuz. Yüzbinlerce kişinin bu sitede en az 3 sayfayı gezdiğini bile düşünürseniz kategoriler kısmının her seferinde veri tabanından çekiliyor olması büyük performans kayıpları yaşatacaktır.Sonuçta bu kısım an ve an değişiklik gösterebilen bir bölüm değil. Böyle bir siteye kategori eklenmesi belki birkaç ayda bir gerçekleşir.
Sonuç itibariyle böyle bir durumda yani kısa süreçler içerisinde değişime uğramayan veya anlık bilgi üretmeyen kısımların önbelleklenmesi (caching) büyük ölçüdeki performans kayıplarını bir hayli absorbe edecektir.
Bunun yanı sıra az önce “anlık bilgi üretmeyen…” kısmıyla da belirttiğim gibi durumlarda ise caching yapılması mümkün olup, istenilen sonuçların doğru üretilebilmesi çeşitli konfigürasyonlar ile sağlanabilmektedir.

Caching Çeşitleri

Caching işlemlerini Data Caching ve Output Caching olmak üzere ikiye ayırabiliriz.
Data Cache işlemleri .Net üzerinde, desteklediği tüm veri ve referans tiplerini ön bellekleyebileceğimiz ve her bir istekte ise önbellekten aynı veriyi alabileceğimiz bir model sunar. Böyle bir önbellekleme için yukarıda verdiğimiz örneği ele alırsak, veri tabanına bir kere bağlanılır ve çekilen kategori nesneleri data cache ile ön belleklenebilir.
Makalede inceleyeceğimiz Output Cache modeli ise çıktıların yani render edilmiş View bilgilerinin ön belleklenmesini ve timeout olana kadar gelen her bir istekte ise bir önceki ön belleklenmiş bilgilerin sunulmasını sağlar. Aynı örnek üzerinden ilerleyecek olursak output cache ile kategoriler kısmının ön belleklenmesi için kategoriler kısmını içeren bir PartialView’ın tamamının cache edilmesi gerekir.
Anlık değişim içerisinde olmayan bir çok verinin aynı sayfa üzerinde olacağı durumlarda, toplu bir cache mekanizmasına sahip olduğu için Output Cache kullanımı daha tutarlı bir çözüm olacaktır.
Cache yöntemlerinden ve istisna durumlarından da bahsettiğimize göre artık yapacağımız küçük uygulamalara geçebiliriz laugh
OutputCache kullanım kolaylığı sağlanması için Action Filter sınıfından kalıtılmıştır. Bu sayede Controller veya altında oluşturulabilecek Action’lar için Attribute olarak kullanılabilirler.

OutputCache Attribute'ün Kullanımı

Uygulamalarımızı yapabilmek için bir adet Asp.Net MVC 3 ya da 4 projesi açalım.
HomeController altında GetTime adında bir adet action yaratalım ve aşağıdaki gibi kodlayalım;
namespace MVCAuth.Controllers
{
    public class HomeController : Controller
    {
        [OutputCache(Duration=10)]
        public ActionResult GetTime()
        {
            ViewBag.Date = DateTime.Now.ToString();
            return View();
        }
    }
}
Eklediğimiz Action için birde View ekleyelim ve onu da aşağıdaki gibi kodlayalım;
@{
    ViewBag.Title = "GetTime";
}

<h2>GetTime</h2>
@ViewBag.Date
Projeyi build edip, çalıştırırsanız karşınıza gelecek olan ilk ekranda o andaki saati görüyor olacaksınız. Siz sayfayı açtığınızda ise render edilen bu sayfa arka planda cache edildi ve bundan sonraki 10 saniyelik kısımda ise cache edilen sayfa ile karşılaşacağınız için aynı saat bilgisini görüyor olacaksınız.
Şimdi ise işin en güzel ve söylediğimiz şeylerin tutarlılığını kanıtlayacağımız kısma geldik laugh
Az önce dedik ki, bu sayfaya ilk girdiğimizde sayfa render edildi, bize gösterildi ve cache edildi. Timeout süresi dolana kadarda aynı sayfaya ulaşmaya çalıştığımızda cacheden gelen sayfa ile karşılaşacağız. Bunu bir de projeyi debug ederek irdeleyelim. Yukarıda kodlamış bulunduğumuz GetTime action kodlarındaki ilk satıra (ViewBag.Date...) bir adet breakpoint ekleyelim ve projeyi debug edelim.

Sayfaya ilk girişte akış breakpoint üzerine düşecektir. Fakat 10 saniyelik kısımda sayfaya tekrar ulaşmaya çalıştığınızda sayfa önünüze gelmesine rağmen, akış breakpoint üzerine düşmeyecektir. Bu da cache işlemi boyunca söz konusu action'ın hiç tetiklenmeyeceği anlamına gelmektedir. Teoride bahsettiğimiz şeylerin, gerçekte de örtüştüğünü gördüğümüze göre uygulamalarımıza devam edelim smiley

Client Bazlı Caching

OutputCache ile yapılabilecek cache işlemlerinin lokasyonunu diğer bir değişle baz alınacak referans yerini belirleyebilirsiniz. Bu konuyu derinlemesine incelerken referans edindiğimiz bir yabancı makale vardı (Linki aşağıda bulabilirsiniz) Aslında söz konusu istisna durum için en iyi örneği yaptığı için bende aynı örnek üzerinden devam ediyor olacağım.
Bu kısımda ise yine bir action yazalım. Adı GetUser olan bu action View kısmında membershipte login olmuş olan kullanıcının adını bize göstersin. Böyle bir action'ın yukarıdaki örnekte olduğu gibi cache edilmesi büyük bir istisna oluşturacaktır.
Örneğin kullanıcının adını görmemizi sağlayacak bu action 1 saatliğine cache edilmek üzere ayarlanmış olsun. Bu durumda A kullanıcısı sisteme login olduğu anda bu isim cache edilecektir. 1 saatlik süre içerisinde ise sisteme kim login olursa olsun yine A adını görüyor olacaktır.
Fakat burdaki cache işleminin Client Bazlı yapılması bu sorunu giderecektir. Bu sayede sayfa her bir client için ayrı ayrı cache edilecektir. Ancak burada ise bir performans kaybı yaşanacaktır. Sisteme 10 ayrı kişinin login olup bu sayfayı ziyaret ettiğini düşünürsek, bu sayfa 10 ayrı cache işlemine tabi tutulacaktır. Bu sorunu bir nebze giderebilmek için ise OutputCache işleminin NoStore özelliği kullanılabilir.
NoStore özelliği true olarak set edilen OutputCache işlemlerinde, render bilgileri kalıcı olarak saklanmazlar. Bu örnek için hazırladığım Action ve View kodları ise aşağıdaki gibidir;
namespace MVCAuth.Controllers
{
    public class HomeController : Controller
    {
        [OutputCache(Duration=3600,Location=OutputCacheLocation.Client, NoStore=true)]
        public ActionResult GetUser()
        {
            ViewBag.Name = User.Identity.Name;
            return View();
        }
    }
}
View;
@{
    ViewBag.Title = "GetUser";
}

<h2>GetUser</h2>
@ViewBag.Name
Cache işleminin Client bazlı yapılması için yazılan attribute'e Location özelliğinin Client olarak  ve bilgilerin kalıcı olarak saklanmaması için de NoStore özelliğinin true olarak set edilmesi yeterli olacaktır.

Parametreleri Cache İşleminden Yalıtma

Makalenin girişinde anlık bilgi üreten sayfaların cache edilebilmesi için bazı işlemlerin uygulanacağından bahsetmiştik. Bir adet haber sitesi tasarladığımızı düşünelim. Proje de ise haberlerin detayını gösterecek bir sayfa tasarlıyor olalım. Bunun için yazılacak Detail adlı action ise gösterilecek haberin Id numarasını parametre olarak almaktadır.
Bu sayfanın cache edilmesi halinde, sayfada gösterilecek ilk haber hangisi ise cache timeout olana kadar hangi Id ile sayfa çağrılırsa çağrılsın aynı haber gösterilecektir. Bu durum da Id parametresinin cache işleminden yalıtılması gerekmektedir.
OutputCache mekanizması route value ve query string verilerinin yalıtılmasını VaryByParam özelliği ile sağlayabilmektedir. Yalıtılmak istenilen verilerin bu özelliğe aralarında virgül konularak set edilmesi yeterlidir.
Örnek olarak aşağıdaki kodu inceleyebilirsiniz;
namespace MVCAuth.Controllers
{
    public class HomeController : Controller
    {
        private readonly List<News> newsRepository;

        [OutputCache(Duration=3600, VaryByParam="id")]
        public ActionResult Detail(int id)
        {
            ViewBag.News = newsRepository.Where(q => q.Id == id).SingleOrDefault();
            return View();
        }
    }
}
Yukarıdaki action gelen Id bilgisine göre repoda bulunan haberi çekmekte ve View'e göndermektedir. OutputCache işleminin yukarıdaki gibi yapılması, Id değerinin cache işleminden yalıtılmasını sağlayacaktır.
Bunun yanısıra VaryByParam'ın "*" olarak set edilmesi ise söz konusu action'a gelecek tüm route value ve query stringlerin pragmatik bir şekilde yalıtılmasını sağlayacaktır. Çok fazla parametre alan action'lar için böyle bir yol izlenilmesi, tüm parametrelerin tek tek yazılmasındansa daha kolay bir çözüm sağlayacaktır.
Makaleyi yazarken referans aldığım yabancı kaynağı görmek için buraya tıklayabilirsiniz.
Bir sonraki makalemde görüşmek üzere, herkese esenlikler dilerim :)
H.Burak TUNGUT

Yazar Hakkında