Caner Tosuner

Leave your code better than you found it

SOLID Prensipleri

SOLID Candır !

SOLID'in geçmişine baktığımızda çokta uzağa gitmemize gerek yok. İlk olarak 2000'li yılların başında Michael Feathers tarafından ortaya atıldı ve sonrasında Robert C. Martin tarfından "first five principles of object-oriented programming and design" olarak "SOLID" adını aldı.

 

Neden SOLID'e ihtiyaç duyuldu ?

"Bir kere yazılan kod asla ilelebet aynı kalamaz..!" aslında SOLID'in ortaya çıkış sebebi bu cümlede gizlidir. Aylar-yıllar geçtikten sonra yazmış olduğumuz kodlara bir değişiklik yapmak istediğimizde bu değişikliği ne kadar efor sarf ederek ve ne kadar sürede yapacağız..? SOLID projeyi yazdıktan sonraki süreçte bu soruya en iyi şekilde cevap verebilmemizi sağlamak için var.

Şu gerçektir ki bazen 7 ay gibi bir efor harcanıp production'a alınmış bir proje için çok değil 6 ay sonra ki süreçte müşteri bir değişiklik veya extra modül istediğinde o modülün projeye eklenmesi neredeyse projeyi production'a almak için harcanan süre kadar sürebiliyor.

7 ay gibi bir sürede projeyi bitir canlıya al, canlıya alındıktan 6 ay sonra müşteri gelsin yeni bir feature istesin sonra müşteriye "benim bunun geliştirmesini yapmak için 6 aya daha ihtiyacım var.." dediğinizde müşteri heralde ufaktan şöyle olur "WTF...man ?"

SOLID prensiplerinin amacı aslında bizlerin daha iyi programlar yazmamızdan çok ileride istenecek değişimlere açık olup bu değişimlere en az eforla ayak uydurabilecek kodlar yazmamızı sağlamaktır. Nesne yönelimli programla yapıyorsak dünyada standart kabul edilen bu 5 prensibe uygun kodlar yazıyor olmamız gerekir.

 

 Peki nedir bu ilk 5 prensip ?

  • – Single-Responsibility Principle
  • – Open-Closed Principle
  • L  – Liskov Substitution Principle
  • I  – Interface Segregation Principle
  • D  – Dependency Inversion Principle

Her bir prensip için sonrasında ayrı ayrı blog'lar yazacağım ancak kısaca bir kaç kelimeyle şu şekilde özetleyebiliriz;

Single-Responsibility Principle

Bir class'ın sadece tek bir işi olmalı ve sadece o işten sorumlu olmalıdır. Örneğin Log tutmak için yaptığınız bir Logger class'ı sadece Log işlemlerinden sorumlu olmalıdır.

 

Open-Closed Principle

Uygulamada yazdığımız objeler yada entity'ler gelişime açık değişime kapalı olmalıdır. Örnek olarak çok if-else yazmaktan kaçının demek desek yanlış olmaz. Bir şekilde daha öcnesinde yazmış olduğunuz metodunuzu genişletmek istediğinizde interface yada abstract class'lardan faydalanarak daha yönetilebilir ve ihtiyaç duyulduğunda genişletilebilir kodlar yazmak gerekmektedir.

 

Liskov Substitution Principle

Liskov prensibi için kısaca yerine geçme prensibi diyebiliriz. Bir base class'tan türetilen class'ların yeri geldiğinde ihtiyaç halinde üst class'ların yerine de kullanabileceğini söylemekte.

 

Interface Segregation Principle

Client'lar ihtiyaç duymadıkları bir interface'i kullanmaya zorlanmamalı veya ihtiyaç duyduğu interface'e ait tek bir metod için bütün interface'in metodları implemente etmemelidir. Interface Segregation prensibi bu gibi durumlar için interface'lerinizi ayırın ve bir interface'e gerektiğinden fazla görev yüklemeyin der. Böylelikle client geliştirmesini yaparken ihtiyaç duymadığı hiç bir metodu implemente etmek zorunda kalmicaktır.

 

Dependency Inversion Principle

Bağımlılığı tersine çevirme prensibine göre base class'lar veya metodlar vs. alt seviyeli sınıflara veya metodlara bağımlı olmamalıdır ve alt class'larda yapılan bir değişiklik base class'ları etkilememelidir. Örnek olarak kısa bir süre önce gözlüklerimin camını değiştirmiştim ve nedendir bilinmez tam o sırada aklıma bu prensip gelmişti :)

Alt modül ; gözlük camı,

Üst modül ; gözlük

olsun. Gözlükçüye gittiniz ve camları değiştireceksiniz adam dedi ki "Camlar gözlüğe bağlı olduğundan camları değiştirmek için komple gözlüğü değiştirmeniz gerekmekte.."  . En iyi ihtimal adama "manyak mısın sen arkadaş.. " dersiniz. İşte bu prensip bu gibi durumlar için var. Alt modül üst modüle bağımlı olsun ama bağımlılık başımızın belası da olmasın tabi ki :)

 

Yazımız burada bitiyor ancak her bir prensip için ayrı ayrı blog yazıları yazıyor olacağım..just follow :)

Web Api Exception Handling and Logging

Herhangi bir yazılım projesinde Exception alındığında bu exception'nın handle edilip belli başlı bazı süreçlerden geçirilip log atılmasına kadar geçen süreçler oldukça önemlidir ve enterprise bir projede development yapıyor isek ExceptionLog olmazsa olmazlardan biridir. Projeye yeni başlarken mimarisi ilk düşünülüp tasarlanması gerekir çünkü proje ilerledikçe log, exception handling veya cache gibi yapıları entegre etmek biraz daha zorlaşacaktır.

Web Api tarafında Exception handling ve log işlemleri yapmak biraz daha keyifli ve basit diyebiliriz çünkü Microsoft sağolsun bazı şeyleri bizim yerimize düşünüp kolaylaştırmış.

İlk olarak şöyle bir controller metodumuz olsun ve bu metoda requestte bulunduğumuzda ne gibi bir response ile karşılaşıyoruz onu görelim.

[HttpGet]
public IHttpActionResult CheckValue(int value)  
{
    if(value > 20)
    {
        throw new ArgumentOutOfRangeException();
    }
    return Ok(value);
}

Herhangi bir 3th party tool (DHC, Postman etc.) kullanarak ilgili metoda parametresi "10" olacak şekilde request attığımızda respose olarak göndermiş olduğumuz "10" değerini status code 200 - OK olacak şekilde döner.

Aynı requesti parametre "25" olacak şekilde attığımızda ise yukarıda fırlattığımız "ArgumentOutOfRangeException" hata açıklamasını status code 500 - Internal Server Error olacak şekilde döner.

Uygulamada controller seviyesinde bir exception aldığımızda olması gereken exception'nın handle edilip kullanıcıya doğrudan Exception class'ından fırlatılan hata mesajı değilde(ihtiyaca göre) ilgili daha anlamlandırılmış bir şekli response'a eklenip client'a dönüyor olması ve bu exception'nın log olarak bir yerde tutuluyor olması gerekir.

Yukarıda bahsettiğimiz gibi WebApi için Exception alındığında Log işleminin yapılmasını için kullanıma sunulan abstract class ve onun metodlarını inceleyeceğiz. 

1.Logging ExceptionLogger class Log metodunu kullanarak uygulamadaki herhangi bir exception'nın loglanmasını sağlayabiliriz. Bunun için aşağıda UnhandledExceptionLogger isimli class'ımızı kullanarak ilerleyeceğiz.

public class UnhandledExceptionLogger : ExceptionLogger  
{
    public override void Log(ExceptionLoggerContext context)
    {
        var log = context.Exception.ToString();
        //Loglama için ilgili ilemlerin yapıldığı yer (db log, file log vs gibi)
    }
}

Üstte tanımladığımız UnhandledExceptionLogger class'ını Web Api projesinde kullanabilmek için register etmemiz gerekmekte. Register işlemi ile birlikte WebApi nin otomatik olarak default set ettiği ExceptionLogger'ı kendi yazdığımız UnhandledExceptionLogger class'ı ile değiştiriyoruz. Bu işlem için aşağıdaki kodu Global.asax.cs içerisindeki Application_Start metoduna yazıyoruz.

config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());

 

Bu adımdan sonra uygulamada bir unhandled exception fırlatıldığı anda logger'ımız onu yakalayıp yazmak istediğimiz bir yere (db, file etc.) yazmamızı sağlayacaktır.

 

2.Excepiton Hander Bu kısımda uygulamamızda fırlatılmış olan exception'nı yakalayıp response da değiştirmek istediğimiz yerleri değiştirip client'a dönme işlemlerini yapacağız. Bunun için ExceptionHandler class'ından faydalanacağız. Bu class ExceptionLogger ve ExceptionFilter dan sonra eğer ilgili exception handle edilmediyse çağrılır. GlobalExceptionHandler ismindeki class'ımız aşağıdaki gibi olacaktır.

 public class GlobalExceptionHandler : ExceptionHandler
    {
        /// <summary>
        /// This function is used for to change the response when an exception occurs.
        /// </summary>
        /// <param name="context"></param>
        public override void Handle(ExceptionHandlerContext context)
        {
            var result = new HttpResponseMessage(HttpStatusCode.InternalServerError)
            {
                Content = new StringContent(context.Exception.Message),
            };
            context.Result = new ExceptionActionResult(result);
        }
    }

 

Yukarıda exception fırlatıldığında yakalaıp client'a dönen response'u modify ettik. Bunun için kendi custom yazdığımız ExceptionActionResult class'ını kullanacağız. O da aşağıdaki gibi olacaktır.

    /// <summary>
    /// This class is a kind of Custom Action Result. When we get any exception and want to handle the web api method that returns HttpActionResult, we can use this class and it's functions.
    /// </summary>
    public class ExceptionActionResult : IHttpActionResult
    {
        private HttpResponseMessage _httpResponseMessage;

        public ExceptionActionResult(HttpResponseMessage httpResponseMessage)
        {
            _httpResponseMessage = httpResponseMessage;
        }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            return Task.FromResult(_httpResponseMessage);
        }
    }

 

Şimdi sırada 1.Adımda da yaptığımız gibi yazış olduğumuz Handler'ı WebApi'nin default set ettiğiyle değiştirme işlemi var bunu da Global.asax.cs içerisindeki Application_Start metodunda aşağıdaki gibi yapıyoruz.

config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());  

 

Uygulamamız hazır. Bu entegrasyondan sonra uygulamamıza 2 şey kazandırmış olduk.

  1. Exception alındığında Log atma işlemi
  2. Fırlatılan unhandled exception'ı yakalayıp kendi yazdığımız custom HttpActionResult ile değiştirip client'a giden respons'u daha anlamlı ve yönetilebilir bir hale getirmiş olduk.

Yazımızın en başında yazmış olduğumuz CheckValue metoduna veya uygulamada herhangi bir metoda request'te bulunup exception fırlatıldığında artık bütün response'lar artık şu şekilde olacaktır.

StatusCode : 500 Internal Server Error

Body : Specified argument was out of the range of valid values.

Gördüğünüz üzre projede exception alındığında log'umuzu atıp client'a dönen response'u da daha anlamlı bir hale getirmiş olduk. 

Ben örnek uygulamada response dönerken statusCode HttpStatusCode.InternalServerError yani 500 set ettim ancak siz projenizde kullanırken daha farklı statusCode'lar da ihtiyaca göre client'a dönebilirsiniz.

 

 

=> http://www.exceptionnotfound.net/the-asp-net-web-api-exception-handling-pipeline-a-guided-tour/

NLog EventLog

Daha önce ki Log4Net yazımızda log tutmanın ne kadar önemli olduğu ve .Net tarafında kullanabileceğimiz free yapılardan biri olan Log4Net implementasyonunu incelemiştik. Bu yazıda ise bir diğer free framework olan NLOG dan bahsedeceğim. Nlog ile mail atabilir, file log'a, console'a, event log'a log, db ye log atabiliriz.

NLog ile ;

Fatal Üst Seviye : Sistem çökmeleri

Error Uygulama hataları ( Exceptions )

Warn Uyarılar, yinede uygulama çalışmaya devam edebilir.

Info Bilgilendirme herhangi bir amaçlı olabilir. Kullanıcı bilgileri güncellendi vs.

Debug Çalıştırılan sorgular, oturum süresinin bitmesi vs.

Trace Bir eylem başladı diğeri bitti gibi. Örn : Fonksiyon başlangıcı ve bitişi durumları( En Alt Seviye )

seviyelerinde log tutabiliriz. Biz bu yazıda NLog ile Event Log'a nasıl log atılır bunu inceliyor olucaz.

1.Adım İlk olarak işletim daha önceden yğklenmiş olan PowerShell programını yönetici olarak açıyoruz ve MyTestAppLog adında bir event log ve bu event log altında MyTestAppSource adında bir source oluşturuyoruz. Daha sonrasında oluşturduğumuz uygulama ve source isimlerini webconfig tarafta konfigurasyon yaparken ilgili alanlara set edicez.

PowerShell de aşağıdaki gibi code satırını yazıyoruz.

  New-EventLog -LogName MyTestAppLog -Source MyTestAppSource  

Sonradan işletim sisteminde bulunan search kısmına view event log yazarak event viewer'ı açıyoruz ve oluşturduğumuz MyTestAppLogve MyTestAppSource ekranda görmemiz gerekiyor.

 

 

2.Adım İkinci olarak VS da boş bir Web projesi açıp Nuget paket yöneticisine Install-Package NLog yazıp ilgili kütüphaneyi indiriyoruz ve projemizdeki WebConfig dosyası içerisine NLog configurasyonunu sağlayan satırları yazıyoruz.

Configuration tagları arasına aşağıdakini yazıyoruz

<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>  

Daha sonrada configuration tag'inin kapandığı yerin altına da şu şekilde yazıyoruz

<nlog
    autoReload="true"
    throwExceptions="true">
    <variable name="appName" value="MyTestApp" />
 
    <targets async="true">
        <target type="EventLog"
            log="MyTestAppLog"
            name="eventlog"
            source="MyTestAppSource"
            layout="${message}${newline}${exception:format=ToString}"/>
    </targets>

    <rules>
      <logger name="*" writeTo="eventlog" minlevel="Info" />
    </rules>
  </nlog>

 Üstteki configurasyonda ;

  • <target type="EventLog" diyerek EventLog configurasyonu olduğunu belirtiyoruz,
  • log="MyTestAppLog" EventLog adı,  
  • name="eventlog" diyerek isimlendiriyoruz,
  • source="MyTestAppSource" EventLog da tanımlı olan Source adı.
  • layout="$ ile log formatını belirliyoruz.
  • <rules> tag'i arasına yazdığımız kod satırıyla eventlog adında tanımlı olan log configurasyonu için minimum Info seviyesinde log tut.

 

3.Adım Şimdi C# tarafına geçip nasıl log atacağız onu yazalım

 public class NLogManager 
    {
        //WebConfigde tanımladığımız gibi info ve yukarı seviyeler için eventLog'a log atacaktır
        public void LogError(LogModel entry)
        {
           var logger = NLog.LogManager.GetCurrentClassLogger();
           logger.Log(LogLevel.Info, "Info Logged");
           logger.Log(LogLevel.Error, "Error Logged");
        }
    }

Uygulamamızı çalıştırdığımızda event log da aşağıdaki gibi log düştüğünü göreceğiz

 

 

 

Web Config Custom Section

WebConfig Asp.Net tabanlı projelerde istemci tarafında ilk çalıştırılan XML tabanlı bir sayfadır diyebiliriz. İçerisinde bir çok bilgi barındırır ve bir istemci yani kullanıcı websitenize erişmeye çallıştığında ilk webconfig çalışır ve içerisinde set edilen bilgilere göre projenizi çalıştırır & ayağa kaldırır.

Nedir bu bilgile diye soracak olursa; 

  • Debug, Release mode ayarı,
  • .Net Framework bilgisi,
  • Varsa Database conn string bilgisi,
  • Kullanılan .Net Kütüphanelerinin versiyon bilgileri,
  • Uygulama Debug modda nasıl Release modda nasıl çalışacak bunun bilgisi,
  • Site içi ve dışı yönlendirme ayarları vs.

Görüldüğü üzre uygulama ile ilgili bir çok ayar mevcut kısacası uygulamanın configurasyonu ile ilgili sayısızca özellik & ayar set edilebilir.

Bunun gibi ayarların dışında web.config içerisinde uygulama içi kullanılan çeşitli özel bilgiler & ayarlar da saklayabilir. Peki neden bu bilgileri burda saklıyoruz veya turuyoruz ?? Uygulamada Constant diye herhangi bir class oluşturup bunun içerisinde de tutabiliriz. Ama bu sefer ne olur derseniz. Orda tuttuğunuz bilgiyi değiştirmek için tekrardan projeyi açıp class içerisinde set edilen değeri değiştirip uygulamayı tekrardan ilgili yere deploy etmemiz gerekmekte. İşte aslında WeConfig bizi bu durumdan kurtarıyor. Webconfig dosyası deploy edildiği yerde projede ki tanımlı olduğu haliyle XML formatında tutuluyor yani uygulama içinde tanımlı olan class gibi dll'e çevrilip içini tekrardan açamayacağımız bir türde durum değil. Bu nedenle değişme olasılığı yüksek olan bilgileri WebConfig içerisinde tutup ihtiyaç duyulduğunda deploy edildiği dosyada ki folder e giderek çok rahat bir şekilde içini açıp ilgili ayarı değiştirebiliriz. Örnek olarak; uygulama kullanılan iletişim sayfasında ki email adresi veya uygulamaın herhangi bir yerinde kullanılan ve sonradan değiştirmeye açık olan bir text/metin. 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>   
     <system.web>
      <compilation defaultLanguage="c#" debug="true" />
    </system.web>
    
    <appSettings>
      <add key="ContanctEmailAddress" value="info@canertosuner.com" />
    </appSettings>
 </configuration>

Yukarıda da görüldüğü gibi uygulama içerisinde yönetmek istediğimiz değerleri <appSettings> içerisinde unique bir key vererek value alanlarına değerleri set edebiliriz. Bu değeri code tarafından okumak istediğimizde de bilindiği üzre şu şekilde yapıyoruz;

string emailAddress= ConfigurationSettings.AppSettings["ContactEmailAddress"];

Peki ya kendimiz aynı <appSettings> section'ı olduğu gibi bir custom secton yapmak istersek..

Case şu şekilde olsun. Kredi faizi hesaplayan bir uygulamamız var ve bu uygulamadaki faiz oranlarını webconfig den okuyup hesaplıyor. Aslında bu gibi bilgiler direkt olarak DB den de alınabilir ama bizim rojemizde webConfig den alalım. 

1.Adım WebConfigde section tanımı yapma

<configuration>
  <configSections>
    <section name="KrediFaizleri" type="MyProject.KrediFaizleriSection" />
  </configSections>
</configuration>
 

Yukarıda da görültüğü gibi KrediFaizleri adında bir section ımızın olacağı bilgisini verdik.

 

2.Adım Section ve içerisindeki elementleri yazma

</configSections>  tag'inin altında aşağıdaki gibi kodumuzu yazalım

  <KrediFaizleri> 
    <Krediler>
      <add tip="ihtiyac" oran="1.65"/>
      <add tip="tasit" oran="1.96"/>
      <add tip="konut" oran="1.20"/>
    </Krediler>
  </KrediFaizleri>

Burda Krediler diye bir array var ve içerisinde Kredi objesi olduğunu düşünelim. Bu obje içerisinde de tip ve oran adında 2 tane string property var. Aslında yukarıda ki xml kodunun tam karşılığı bu. Yukarıda 3 adet kredi için oran bilgisi girdik şimdi sırada bu değerleri webconfigden okumamızı sağlayacak C# kodlarını yazmaya geldi

 

3.Adım KrediFaizleri elementini tanımlama

Yukarıda Krediler arrayinin içerisinde Kredi objesi olduğunu söylemiştik.Aslında bu bir element. Biz adına KrediFaizleriElement diyelim ve classımız aşağıdaki gibi olacaktır.

 public class KrediFaizleriElement : ConfigurationElement
    {
        [ConfigurationProperty("tip", IsKey = true, IsRequired = true)]
        public string KrediTipi
        {
            get { return (string)this["tip"]; }
            set { this["tip"] = value; }
        }

        [ConfigurationProperty("oran", IsKey = true, IsRequired = true)]
        public string Oran
        {
            get { return (string)this["oran"]; }
            set { this["oran"] = value; }
        }
    }

 

4.Adım KrediFaizleri array'ini tanımlama

Bu adımda webconfig de ki array'i almamızı sağlayan ConfigurationElementCollection den türeyen bir class yazıyoruz. Bu class'ı kullanarak tanımladığımız section içerisinde ki collection'ı içerisindeki Element ler ile birlikte alıyoruz.

[ConfigurationCollection(typeof(KrediFaizleriElement))]
    public class KrediFaizleriCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new KrediFaizleriElement();
        }

         protected override object GetElementKey(ConfigurationElement element)
        {
            return ((KrediFaizleriElement)element).KrediTipi;
        }
    }

  

5.Adım KrediFaizleriSection class ını oluşturma

Fark ettiyseniz  en içten en dışa doğru ilerliyoruz. İlk olarak Element sonra Collection şimdide sırada Section var. Aşağıda yazılan KrediFaizleriSection webconfigde tanımladığımız KrediFaizleri tag'ini okumamızı sağlayacak olan class. İçerisinde Faizler array i var ve bu array'in içerisindede KrediFaizleriElement objeleri bulunacak.

public class KrediFaizleriSection: ConfigurationSection
    {
        [ConfigurationProperty("KrediFaizleri", IsDefaultCollection = true)]
        public KrediFaizleriCollection Faizler
        {
            get { return (KrediFaizleriCollection )this["KrediFaizleri"]; }
            set { this["KrediFaizleri"] = value; }
        }
    }

   

Son Adım Webconfig de tanımlı değerleri okuma ve filtreleme işlemi.

Bu adıma kadar şu sırayla lerledik;

  1. WebConfig de Section tanımlama,
  2. WebConfig de Section içerisindeki Collection ve Elementleri tanımlama,
  3. C# tarafında Element&Model class'ını oluşturma,
  4. C# tarafında Collection class'ını oluşturma,
  5. C# tarafında Section class'ını oluşturma,

Şimdi ise sırada bu class ları kullanarak WebConfig de tanımlı değerleri okuma işlemi var. Bunun için aşağıdaki class ı kullanabiliriz. 

 public static KrediFaizleriElement KrediOranınıGetir(string krediTipi)
        {
            //KrediFaizleri section'nını içerisindeki elementler ile birlikte aldık
            var element=
                ConfigurationManager.GetSection("KrediFaizleriSection") as KrediFaizleriSection;

             //Paramaetre olarak verilen kredi tipine göre linq sorgusu yaptık
             var returnValue = element.Faizler.Cast<KrediFaizleriElement>().SingleOrDefault(c => c.KrediTipi== krediTipi);
           
           //İlgili kredi WebConfigde varmı kontrol ettik
            if (returnValue == null)
                throw new InvalidOperationException(string.Format("Kredi Tipi {0} bulunamadı !", krediTipi));
            //Bulunan krediyi geri döndürdük
            return returnValue;
        }

İşlemlerimiz bu kadar. KrediOranınıGetir metodunu gerekli yerde çağrısını yaparak kullanabiliriz.

Log4Net delete last N days files

Daha önceki yazımızda projemize nasıl Log4Net implemente edilir burada ki yazıda incelemiştik. Log4Net te bahsettiğimiz gibi bizim verdiğimiz kritere uygun boyutlarda belirttiğimiz klasör altında log dosyalarını saklıyordu. Peki ya o klasör altında aylarca yıllarca çok fazla log dosyası oluşur ve GB boyutunda yer kapladığı düşünülürse ne olacak??? "Hadi be sende GB boyutunda log nerde biriksin.." falan diyebilirsiniz ancak projenizin büyüklüğüne göre bunun olma olasılığı oldukta büyük bir ihtimal.
İşte bu gibi durumlardan kurtulmak için bir File Clear türünde bir manager yazmamız gerekmekte.

Akış şu şekilde olacak; Log4Net kendi işini yapıp loglarını dosyalara atmaya devam ederken uygulamanın her Application_Start adımında file clear manager çalışıp Log klasörüne gidip bakıcak. İçerisindeki dosyalardan son 2 haftadan daha eski tarihte oluşmuş dosyalar var ise gidip bu dosyaları silecek.

ilk olarak aşağıda oludğu gibi Log4NetFileCleanUpManager adında bir class oluşturuyoruz

public class Log4NetFileCleanUpManager
    {
        private DateTime _lastDateToKeepStoring;
        public Log4NetFileCleanUpManager()
        {
            _lastDateToKeepStoring = DateTime.Today.AddDays(-14);
        }

        public void CleanUp()
        {
            var repo = LogManager.GetAllRepositories().FirstOrDefault();

            if (repo == null)
                throw new NotSupportedException("Log4Net has not been configured yet !");

            var app = repo.GetAppenders().FirstOrDefault(x => x.GetType() == typeof(RollingFileAppender));
            if (app != null)
            {
                var appender = app as RollingFileAppender;
                string directory = Path.GetDirectoryName(appender.File);
                 CleanUp(directory);
           }
        }

        public void CleanUp(string logDirectory)
        {
            if (string.IsNullOrEmpty(logDirectory))
                throw new ArgumentException("logDirectory is missing");

            var dirInfo = new DirectoryInfo(logDirectory);
            if (!dirInfo.Exists)
                return;
 
            var fileInfos = dirInfo.GetFiles();
            if (fileInfos.Length == 0)
                return;
 
            foreach (var info in fileInfos)
            {
                if (info.CreationTime < _lastDateToKeepStoring)
                {
                    info.Delete();
                }
            }
        }
    }
Kod şu şekilde çalışıyor. Constructor da hangi tarihten sonraki kayıtları silineceği bilgisi veriliyor (Son 14) gün. CleanUp fonksiyonunda Log4Net için webconfigde tanımlı RollingFileAppender bulunuyor ve bunun için tanımlı log klasörünün directory si bulunup CleanUp(string logDirectory) metduna yollanıyor. Burada o klasörün içerisindeki dosyalarda teker teker gezinip oluşturulma tarihlerine bakıyor. Oluşturulma tarihi bizim belirttiğimiz tarihten önce ise o dosyayı siliyor.
 
 
Son olarak Application_Start metodunda aşağıdaki configurasyonu yapmamız gerekmekte.
XmlConfigurator.Configure();
var task = new Log4NetFileCleanUpManager(); 
task.CleanUp();
 

Log tutmak ne kadar önemli ise tuttuğumuz log'un yönetimi ve başa bela olmaması da o kadar önemlidir. Üstte de söylediğim gibi projenin büyüklüğüne göre onlarca GB boyutunda .txt dosyaları ansızın oluşabilir ve sonrasında down !

OraOledb Not enough storage is available to complete this operation.

There are like 100 questions and answers about this problem but none of them did solve my problem.

What I was was trying to do is getting blob data from oracle database using ado.net just like below code,

OleDbParameter returnValue = new OleDbParameter("v_PictureBlodData", OleDbType.LongVarBinary);


and when I try to execute the store procedure & query I was getting an error which sais "Not enough storage is available to complete this operation."

Most of the comments for this problem is about, server Ram capacity or Storage capacity. But I was really sure that my server had enough Ram and Storage capacity.

 

So, what solved my problem is just giving a size value for OleDbParameter which is 4000000

OleDbParameter returnValue = new OleDbParameter("v_PictureBlodData", OleDbType.LongVarBinary, 4000000);


If you guys are sure about your server capacity, you can check it by trying to give a size value for the parameter.

Log4Net İmplementasyonu

"Log uğrunda ölen olmadıktan sonra log değildir !!"

Önceki çalıştığım firmalardan birinde uzun soluklu bir bankacılık projesi geliştiriyorduk ve daha önce ilgilenen arkadaşlar bilirler ki development süreci oldukça meşakatlidir. Önce Dev de çalış sonra Test'e at sonra UAT e al sonra PROD a taşı orda testleri yap vs. vs. bir sürü ortam birsürü bug ve en önemlisi yüzlerce şöyle cümleler duyabilirsiniz "TEST te sıkıntı yok ama UAT de çalışmıyor yada sadece PROD da rastladığımız bir durum..." bu gibi durumlarda loglardan yürüyerek ilerlemek sorunun kaynağına en kısa sürede ulaşmanızı sağlayabilir. Tabi log alt yapınız var ise :) Ama bitmedi log altyapınızın olmasıda o loga bir bakış attığınızda ahanda tamam sorunu buldum diyebileceğiniz anlamına gelmeyebilir. Çok fazla yaşadığım bir olaydan bahsetmek istiyorum ve bir arkadaşımın bu olay sonrası projedeki Test Müh. arkadaşa söylediği bir cümle varki efsane :)

Birgün yine üstte bahsettiğim bankacılık projesinde dev yapıyorum ve ekip yaklaşık olarak 10 kişi falan (.net-ios-android-pm-tester vs.). Yine o sihirli cümle geldi ve test müh arkadaş android yazan arkadaşa "droidci arkadaş PROD da rastlanan bir hata varmış ve müşteri ilgili maddeyi JIRA da açmış ve logları paylaşmış. Sorunu kısa sürede çözebilir misin.." vs şeklinde bir cümle kurdu. Developer arkadaş JIRA yı açtı madde yi okudu ve loglara baktıktan sonra test müh arkadaşa "maddeyi bulamadım, müşteriye geri assign ediyorum.." gibi birşey söyledi ve test müh arkadaş da "ama nasıl bulamazsın loglarıda yollamışlar ki.. -%&?!?.." Bu cümleye atarlanan developer arkadaş işte o anda o efsane atasözünü söyledi. "Log uğrunda ölen olmadıktan sonra LOG değildir arkadaşım..!" Sonuç olarak ne oldu sizce ? Log var madde var ama çözüm için elde hiçbir şey yok. Yazılım projelerinde çok büyük öneme sahip olan log yapıları emin olun hayat kurtarır.  Çeşitli loglama türleri vardır. DBLog, FileLog vs gibi. Bu örnekte hemen hemen herkesin en azından duymuş olduğu Log4Net kütüpanesini kullanarak orta çaplı bir FileLog yapısı nasıl kurulur ondan bahsedicem. Log4Net için öncelikle Nugetten ilgili dosyayı indirip projemize kuruyoruz Install-Package log4net    Daha sonra aşağıda olduğu gibi ILog adında bir interface tanımlıyorum. Parametre olarak LogModel türünde bir obje alıyor olacak. Bu obje içerisinde loglamak istediğimiz alanlar mevcut olacak.

public class LogModel
    {
        public string ExcMessage{get;set;}
        public Exception ExceptionModel{get;set;}
      }

  public interface ILog
    {
        void Log(LogModel entry);
     }

Log4Net için WebConfig de birtakım ayarlar yapmamız gerekiyor. Bu ayarların kapsamı çok fazla ancak kısaca özetlemek gerekirse LogDosyalarının adlandırılması, LogDosyalarının bulunacağı path bilgisi, max kaç MB lık dosyalar tutulacak ve enfazla kaç dosya olmasına izin verilecek gibi bir çok LogConfig Settings diyebileceğimiz şey içermekte. Bu projedeki WebConfig dosyası asağıda olduğu şekildedir.

 

 <appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">

      <file value="LogFiles\\" />

      <appendToFile value="true" />

      <DatePattern value="yyyy\\\\MM\\\\dd'.day.logs.txt'" />

      <rollingStyle value="Date" />

      <maxSizeRollBackups value="7" />

      <param name="StaticLogFileName" value="false" />

      <layout type="log4net.Layout.PatternLayout">

        <conversionPattern value="%newline%date %newline%logger [%property{NDC}] %newline>> %message%newline" />

      </layout>

     </appender>

 

Şimdi sırada Loglama işlemini yönetecek bir Log4Manager' ihtiyacımız var. Burda yarın bir gün XManager YManager da tanımlayabiliriz. Bunun bir çok implementasyonu olabilir. Ben böyle birşey kullanmayı tercih ettim. ILog, IDisposable interfacelerinden türeyen Log4NetLogManager adında bir class yazıyoruz. Class ın içeriği aşağıdaki gibi olacaktır. 

 

 

public class Log4NetLogManager : ILog, IDisposable
    {
         private log4net.ILog log4Net;
         public Log4NetLogManager()
        {
            log4net.Config.DOMConfigurator.Configure();
            log4Net = log4net.LogManager.GetLogger(typeof(Log4NetLogManager));
        }
         public void Log(LogModel entry)
        {
            log4Net.Error(entry.ExpMessage, entry.ExceptionModel);
        }
         public void Dispose()
        {
            if (log4Net != null)
                log4Net.Logger.Repository.Shutdown();
        }
    }

 

İşlemlerimiz bu kadar. Bu saatten sonra tek yapmamız gereken bu manager'ı kullanan logic ler yazmaktır. Bir sonraki örneğimizde ASP.Net WebApi tarafında ExceptionHandling olaylarına girip yukarıda yazmış olduğumuz Manager'ı kullanan bir app yazacağız.

 

 

Post two

This post is just an example using Post List widget with image in the post body.

Post List widget should find first image in the post body and display it in the post list when this option selected in the widget admin.

Web Api Projesi Oluşturma

Şöyle bir örnek proje yapalım. TodoApi adında bir WebApi projemiz olsun ve bu Api'a bir tane controller tanımlayıp içerisine TodoList dönen bir metot yazalım.

İlk olarak Visual Studio açıyoruz ve File => New => Project diyoruz ve aşağıdaki görselde olduğu gibi Web kategorisine tıklayıp sonrada ASP.Net Web Application'ı seçiyoruz ve projenize bir isim verdikten sonra OK'e tıklıyoruz.

 

Daha sonra açılam ekrandan Empty kategorisini tıklayıp ekranın orta kısmında bulunan checkbox'lardan WebApi'ı seçiyoruz ve OK'e tıklıyoruz.

 

Artık solution'da projemiz hazır. Şimdi ilk olarak Models klasörü içerisine TodoItem isminde aşağıdaki gibi bir class tanımlayalım.

namespace TodoApi.Models
{
    public class TodoItem
    {
        public string Key { get; set; }
        public string Name { get; set; }
        public bool IsComplete { get; set; }
    }
}

Şimdi ise TodoController adında controller'ımızı tanımlayalım. Bunun için solution'da bulunan Controller klasörünün üzerine gelip sağ tıklayıp Add diyelim sonrasında açılan ekrandan Controller'ı seçelim ve Controller'a TodoController ismini verip OK'e tıklayalım.

Artık TodoController'ınıda oluşturduk şimdi sırada GetAllTodoItems isminde HTTPGET request'i atılabilen ve geriye List<TodoItem> dönen bir metot yazalım.

public class TodoController : ApiController
    {
        [HttpGet]
        public List<TodoItem> GetAllTodoItems()
        {
            var responseList = new List<TodoItem>
            {
                new TodoItem{
                    Key="SD2",
                    Name="Görev1",
                    IsComplete=true,
                },
                new TodoItem{
                    Key="SD11",
                    Name="Görev2",
                    IsComplete=true,
                },
                new TodoItem{
                    Key="SD251",
                    Name="Görev3",
                    IsComplete=true,
                },
                new TodoItem{
                    Key="SD8",
                    Name="Görev4",
                    IsComplete=true,
                },
                new TodoItem{
                    Key="SD01",
                    Name="Görev5",
                    IsComplete=true,
                },
                new TodoItem{
                    Key="SD42",
                    Name="Görev6",
                    IsComplete=true,
                },
            };

            return responseList;
        }

Api projemiz var. Şimdi gelin bu Api'a requestte bulunup ne response dönüyor onu görelim. Projeyi çalıştıralım ve her hangi bir rest client tool kullanarak aşağıdaki link'e requestte bulunalım.

  • http://localhost/api/Todo/GetAllTodoItems

Ben genelde Postman'i kullanıyorum ve aşağıdaki gibi Postman ile GET request'i atıyoruz.

 

Dönen response'a baktığımızda aşağıdaki gibi json formatında response geldiğini göreceğiz.

 

WebApi candır arkadaşlar ve servis tarafında geliştirme yapıyorsanız da çok ama çok önemlidir. Bir Microsoft yetkilisi bir konferansta şuna benzer bir cümle kurdu "Biz daha iyisini yapana kadar en iyisi bu !".

 

Web Api Projesine Cache Ekleme - OutPutCache

Server-side tarafında geliştirme yapan arkadaşlar bilirler ki request-response time ları çok büyük önem sarf etmektedir. Örnek olarak, bir muhasebe DB niz var ve her gün gece 23:59 da günlük raporlar giriliyor veya 3 dkka yeni kayıt girilen bir yapıda olabilir. İlgili tabloda şuana kadar 200 bin kayıt girilmiş diyelim. select * from Raporlar dediniz ve size 200 bin kayıtı ortalama 15 sn de getirdi (DB nin serve edildiği Pc nin özellikleri ve network hızı gibi çeşitli etkenlere göre değişir). Aslında bu gibi yapılarda ilgili  StoraProcedure e paging parametreleri vererek daha performanslı ve hızlı bir sorgu yazabiliriz ancak bu durum için bile Cache yapısını uygulamamıza entegre edebiliriz. Şimdi bu raporları feed eden bir mobil uygulamamız, Web Sayfanız veya bir masaüstü uygulamanız olabilir ve iş ilgili uygulamanın kullanım oranına göre dakikada 100-200 request te bulunuyor olabilirsiniz. Bu uygulamalara db ile connection'ı sağlayan bir Web Api uygulamamız olsun. Şimdi yukarıda ki case de uygulamada bulunan RaporlarController 'ına dakikada 100-200 arası istek bulunmakta. Ne yapacağız peki sürekli olarak DB ye git select * from Raporlar deyip 200 bin kayıtı almak için 15 sn sürsün network vs işlemlerinden dolayı 2 sn de ordan olsun artı birde kullanıcının internet hızı kotalımı dersin :) ttnet sağolsun hızı düşürmüş 2 mb'e ve oda yaklaşık olarak 5 sn sürsün diyelim (değerler tamamiyle dummy dir). Şimdi küçük bir matematik işlemiyle ortalama kaç sn de veriyi getiriyoruz;

DB QueryResult : 15 sn

WebApi projesinin Download Süresi : 2 sn

Client uygulamasının Download Süresi : 5 sn

Client uygulamasının bu veriyi ekrana çizmeside yaklaşık 2 sn diyelim (Performanslı cihazlarda)

int[] arr = { 15 , 2, 5, 2 };
 
int sumResult = arr.Sum();
Console.WriteLine(sumResult); 
24 saniye 

Adama "Oh My God" dediğinde "Yes your god" diye cevap verirler. 

Tamı tamına 24 saniye  (biraz abartmışta olabiliriz ). Günde ortalama 100 kişinin kullandığını düşündüğümüzde her gelen 24 sn beklicek. Böyle bir projeyi al çöpe at derler adama. Benzer bir olay daha önce çalıştığım bir şirkette başıma geldi ve page_load da db ye gidip 4.600.000 kayıt için sorgu atılıyordu ve bazı işlemler yapılmaya çalışılıyordu. Projeyi bana assign edip müşteri sayfayı açamıyormuş bi bakarmısın dediler ve sorunu anladığımda yazılımdan soğmuştum diyebilirim. 

Her neyse gelelim konumuza. Projeyi bu halde bırakmayalım ama çöpede atmayalım. Projemize sağ tıklayıp nugetten WebApi OutPutCache.Core ve OutPutCache.v2 dll lerini ekleyelim.

Senaryomuz şu şekilde olacak;

1-Controller bazında hem server side için hemde client için iki tarafta da cache sağlayacak bir yapı yapıyoruz. Kullanacağımız kütüphaneye 2 parametre vericez  ServerTimeSpan  ve ClientTimeSpan .  Biri ServerSide için cache'i sağlicak yani kendine gelen ilk request'in response'ını alıp set edilen süre boyunca aynı requestle başka bir kullanıcı geldiğinde cache den alıp o yeni gelen kullanıcıya verecek. Diğeri ise ClientSide için clienta dönen response'un header'ına Cache-Control →max-age=60 eklicek. Bu şu anlama geliyor; Client'a diyor ki arkadaş ben bu respons'u 60 sn cache ledim ve sende istersen gelen response'u biyerde tutup bana 60 sn boyunca gelmeyebilirsin. 60  sn sonra tekrardan gel. Şimdi gelelim code tarafına ilk olarak WebApiCacheAttribute adında CacheOutputAttribute inherıt olan bir attribute tanımlıyoruz. Alında direkt olarak CacheOutputAttribute  controllerımızın içerisindeki metodların başına ekleyebilirdik ancak yönetimi daha kolay olur düşüncesiyle araya işimi daha kolaylaştıran WebApiCacheAttribute  adında CustomAttribute ünü yazdım.

public class WebApiCacheAttribute : CacheOutputAttribute
    {
        public ApiCacheAttribute()
            : this(WebConfigApiClientCacheDuration,WEebConfigApiServerCacheDuration){ }

          public ApiCacheAttribute(int clientCacheDuration = 0,
                                 int serverCacheDuration = 0)
        {
            base.ServerTimeSpan = serverCacheDuration;
            base.ClientTimeSpan = clientCacheDuration;
        }
    }

Üstte görüldüğü gibi bu Cache attribute'ü 2 parametre alıyor. Bu parametreleri attribute implementasyon sırasında aşağıda olduğu gibi biz değer atamaz isek WebConfig dosyasında tanımlı olan değerli alarak Cache sürelerini set ediyor.

WebConfig de appsettings de kayıtlı olan değerleri okuyarak cache işlemini yapar

[WebApiCacheAttribute]
public IHttpActionResult GetItems()
{
      var products= new IkiYuzBinKayit(); //db den 200 bin kayit getirmişiz varsayalım
      return Ok(product);
}

Ama istersek aşağıda olduğu gibi bizde bu değerleri set edebiliriz

[WebApiCacheAttribute(ApiServerCacheDuration=120,ClientCacheDuration=120)]
public IHttpActionResult GetItems()
{
     var products= new IkiYuzBinKayit(); //db den 200 bin kayit getirmişiz varsayalım
     return Ok(product);
}
 

Sonuç olarak web api projemizde cache attribute eklediğimiz metoda yapılan sorgular set edilen sürelere göre cache lenir ve o süre boyunca aynı parametrelerle gelen bütün kullanıcılara cachelenmiş olan response döner. Süreç server cache süresi bittiğinde sonlanır ve yeniden gelecek olan ilk sorgu beklenir ve döngü şeklinde devam eder.

 

Not ! Cache Cache'dir ancak hangi datanın cachelenip cachelenmemesi konusu çok ama çok önemlidir !! İyice düşünüp karar verilmesi gereken bir konudur.