MVC'de Attribute Kullanarak Loglama Yapmak

  • Mart 29, 2018
  • /
  • Yorum Yok

     Projelerde, özellikle de hassas veriler içeren büyük projelerde, işlemlerin takip edilebilirliği her zaman büyük önem taşır. Geriye dönüp hangi kullanıcının ne işlemler yaptığını, hangi aşamalardan geçtiğini adım adım izleyebilmek, hataları ve nerelerde yanlışlıklar yapıldığını yakalama konusunda işimizi kolaylaştırır.

     Bu makalede MVC'deki ActionFilter'lar sayesinde kendi Attribute'larımızı oluşturacağız ve bu Attribute nesnelerini kullanarak kullanıcıların işlemlerini veritabanına loglayacağız.

     Attribute Nedir?

     MVC'de hayat bilindiği üzere Action'lar üzerinden akıyor. Bazen Action'lar çalışmadan önce veya çalıştıktan sonra bazı işlemler, kontroller yapılmasını isteyebiliriz. Böyle durumlarda Attribute'lar imdadımıza yetişir. Attribute'ları, kullanmak istediğimiz Action tanımlarının hemen üstüne köşeli parantezler içinde yazarız.     

[Attribute]
public void Kaydet()
{
 
}

[Attribute]
public class Okul
{
 
}

     Sorun 

     Websitenizde bir kullanıcı, önemli bir alana sürekli yanlış veya eksik bilgi giriyor. Bu yüzden sitenizi kullanamıyor ve sitenin hatalı olduğunu düşünüyor. Ama siz bu kullanıcının kim olduğunu, nerede yanlış yaptığını bilemiyorsunuz. Hatta bu hatadan haberiniz bile olmuyor.

     Çözüm

     Kendi Attribute classımızı yazarak, bu tarz geçmişe yönelik izlemek istediğimiz Controller ve Action'ları veritabanındaki Loglama amaçlı oluşturduğumuz tabloya kaydedebiliriz. C# ve MVC mimarisi sağolsun. İlk iş olarak, temel bir ActionFilter Attribute oluşturalım. 

public class LogAttribute : ActionFilterAttribute
{
    //attribute aracılığıyla yapılacak işlemler
}

     Attribute olarak kullanmak istediğimiz classlar, ActionFilterAttribute classından miras almalıdır.

     Loglanacak Verileri Almak

     Projeye bir Log classı ekleyelim. Bu class aşağıdaki alanları içermeli :

     * ID : Veritabanındaki primary key alanı
     * UserId : Loglanan işlemi yapan kullanıcı(tabi kullanıcı giriş işlemi olan sayfalar için geçerli)
     * IPAddress : İşlemin yapıldığı IP adresi. Bu bilgiyi loglayarak kullanıcının genellikle kullandığı IP'leri loglayabilir, uzun vadede şüpheli işlemleri yakalayabiliriz
     * UrlAccessed : İşlemin yapıldığı sayfa, olay mahalli.
     * Data : Sayfada gelen giden requestleri serialize edip, string olarak tutacağız
     * ExecutionMs : İşlemin tamamlanma süresi(milisaniye cinsinden). Bu bilgi sayesinde genellikle daha uzun süren Action'ları yakalayıp optimizasyonlar yapabiliriz
     * AddedDate : İşlemin tarihi     

public class Log
{
    public int ID { get; set; }
    public int UserId { get; set; } 
    public string IPAddress { get; set; }
    public string UrlAccessed { get; set; }
    public string Data { get; set; }
    public long ExecutionMs { get; set; }
    public System.DateTime AddedDate { get; set; } 
}

     Log classını oluşturduğumuza göre Attribute classımızı yazmaya başlayabiliriz.     

public class LogAttribute : ActionFilterAttribute
{
    private Stopwatch _stopwatch;

    public LogAttribute()
    {
        _stopwatch = new Stopwatch();
    }
}

     Öncelikle bir Stopwatch nesnesi tanımlıyoruz. Stopwatch classı, System.Diagnostics kütüphanesinde bulunur ve iki işlem arasındaki süreyi hesaplayabilmemizi sağlar. Kelime anlamına uygun bir iş yapıyor yani : kronometre. Yukarıda bahsettiğim, işlemlerin tamamlanma süresini Stopwatch sayesinde alacağız. Constructor methodunda yeni bir nesnesini oluşturuyoruz.

     OnActionExecuting Methodu     

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    _stopwatch.Reset();
    _stopwatch.Start();
}

     OnActionExecuting methodunu override ediyoruz. Log attribute'ını tanımladığımız Action'lar çalıştırılmadan hemen önce OnActionExecuting methodu tetiklenir. Bu methodda kronometreyi sıfırlayıp, tekrar başlatıyoruz. 

     OnActionExecuted Methodu     

public override void OnActionExecuted(ActionExecutedContext filterContext)
{
    _stopwatch.Stop();  //kronometreyi durdur

    //Log classının alanlarını doldur
    //Log classını veritabanına kaydet

    base.OnActionExecuted(filterContext);   //işlem devam etsin
}

     OnActionExecuted methodunu override ediyoruz. Log attribute'ını tanımladığımız Action'lar çalıştırıldıktan hemen sonra OnActionExecuted methodu tetiklenir. Bu methodda kronometreyi durdurduğumuzda bu Action'ın kaç milisaniyede çalıştırıldığını bulmuş oluyoruz. Log classının alanlarını doldurmaya hazırız artık.     

public override void OnActionExecuted(ActionExecutedContext filterContext)
     {
         _stopwatch.Stop();  //kronometreyi durdur

         //Log classının alanlarını doldur
         var request = filterContext.HttpContext.Request;    //Gelen requesti al
         int userId = GetUser();     //login olan kullanıcının alınacağı method; cookie'den, session'dan alınabilir  

         Log l = new Log();
         l.AddedDate = DateTime.Now;
         l.Data = SerializeRequest(request); //yukarıda alınan requesti serialize edip, string olarak yazıyorum. 
         l.ExecutionMs = _stopwatch.ElapsedMilliseconds; //çalışma süresi
         l.IPAddress = request.ServerVariables["HTTP_X_FORWARDED_FOR"] ?? request.UserHostAddress;
         l.UrlAccessed = request.RawUrl; //erişilen sayfanın ham url'i
         l.UserId = userId;

         //Log classını veritabanına kaydet

         base.OnActionExecuted(filterContext);   //işlem devam etsin
     }

     private string SerializeRequest(HttpRequestBase request)
     { 
         string result = string.Empty;
         #region Form
         List<string> formVals = new List<string>(); //eğer sayfada bir form varsa, formda gönderilen tüm inputları alıp bir listeye atıyorum.
         if (request.Form.AllKeys != null && request.Form.AllKeys.ToList().Count > 0)
         {
             foreach (string s in request.Form.AllKeys.ToList())
             {
                 formVals.Add(request.Form[s]);
             }
         }
         #endregion 

         result = Json.Encode(new { request.Form,    //gönderilen formun tamamı
             formVals,   //formdaki inputlara girilen veriler
             request.Browser.Browser,    //kullanıcının tarayıcısı
             request.Browser.IsMobileDevice,     //istek bir mobil cihazdan mı geldi
             request.Browser.Version,    //kullanıcının tarayıcı versiyonu
             request.UserAgent,      //yönlendiren kuruluşu bulmamızı sağlayan useragent bilgisi
             request.UserLanguages,  //tarayıcı dili
             request.UrlReferrer     //yönlendiren sayfayı bulmamızı sağlayan urlreferrer bilgisi
         });     //tüm bu bilgileri serialize edip stringe çeviriyorum

         return result;
     }

     Request ile alınabilecek veriler her sayfada farklılık göstereceğinden, bu veriler için ayrı alanlar açmak yerine serialize edip string olarak yazmak daha mantıklı geliyor bana. SerializeRequest methodunda bu işlemi yapıyorum.     

     Log Attribute nesnesi hazır. Artık loglamak istediğiniz Action'ın hemen üstüne Attribute eklemeniz yeterli.

[Log]
public bool Save()
{
    //kayıt işlemi

     Attribute'ları Action'lar ile kullanabileceğiniz gibi, Controller'lar ile de kullanabilirsiniz. Bir Controller içindeki tüm Action'lar aynı Attribute'ı çalıştırsın isterseniz aşağıdaki gibi Controller üstüne eklemeniz yeterlidir.     

[Log]
public class HomeController : Controller
{

     Attribute'lar miras ile de aktarılabilir. Eğer bir Controller'a Attribute tanımlarsanız, bu Controller'dan miras alan tüm diğer Controller'lara da aynı Attribute'ı tanımlamış olursunuz.

YORUMLAR
Ahmet Şeker
Temmuz 6, 2020 17:11
formdan gelen bilgiler içesinde html içerik olduğunda System.Web.HttpRequestValidationException: hatası alıyorum istemcide zararlı olabilecek bir değer Request.Form algılandı. bunun önüne nasıl geçebilirim. html encode denedim fakat yine hata vermeye devam ediyor başka bir çözüm önerin varmıdır. iyi çalışmalar.
CARAN
Ekim 9, 2019 09:16
Çok yararlı bir makale olmuş. Ellerinize sağlık.
ercan
Eylül 26, 2019 11:59
on numara 5 yıldız tam da aradığım gibi bir mekale
Semih
Temmuz 23, 2019 09:24
Ellerinize sağlık yararlı bir paylaşım olmuş. Kullanıcının sisteme giriş ve çıkış tarih ve saatini de tutmak istersek ne yapmalıyız?
yazılımcı
Haziran 24, 2019 06:15
Eline sağlık çok faydalı oldu.
Email Adresi *
Görüntülenecek İsim *
Yorum *
Paylaş
  • f