Caner Tosuner

Leave your code better than you found it

Fluent NHibernate Nedir ve Kullanımı

ORM Nedir ?

ORM (object relational mappers) uygulamalarınızda CRUD (Create, Read, Update, and Delete) işlemleri için basit bir şekilde data'ya erişimlerimizi sağlayan yapılardır. Çeşitli ORM framework'leri uzun zamandan beri data-model arasındaki veri alış verişini sağlamak için kullanılmakta ve ORM ler sql scriptleri execute etmeden CRUD işlemlerini doğrudan yapabilmemiz için kod yazmamıza olanak sağlarlar. Özetle, ORM kullanarak uygulamalarımızdaki data-model ilişkisini(database modellemesini) object-model ilişkisine dönüştürebiliriz.

Neden Fluent NHibernate ?

Klasık NHinernate, database de bulunan her bir tablo için class mapping bilgilerini .hbm uzantılı  XML formatındaki dosyalarda saklıyordu ve aslına bakılırsa biraz zahmetli bir işlemdi. Fluent NHibernate'de ise artık .hbm uzantılı mapping bilgilerini içeren XML formatındaki dosyalar kaldırıldı ve bu işlem code-behind'a taşındı ve diğer ORM'lere göre daha hızlı olduğu kabul edilir.

Kısaca özelliklerini sıralamak gerekirse;

  • Easy data-access and data-mapping,
  • Compile-time name- and type-safety,
  • IntelliSense to show you which fluent methods are available at any point,
  • Automapper impl.

Fluent NHibernate ile kolayca Linq sorguları yapabilir ve böylelikle Fluent bir Api oluşturmada kullanabiliriz. Şimdi ise projemize nasıl Fluent NHibernate entegre edip geliştirme yapabiliriz bunu incelicez.

Fluent NHibernate Kurulum

İlk olarak Visual Studio üzerinde yeni bir Console Application projesi açalım. Daha sonra Nuget üzerinden Fluent NHibernate referanslarını projemize indirip kuralım. Install-Package FluentNHibernate

Database Tablo Oluşturulması

Fluent NHibernate kullanabilmek için öncelikle bir database'e ihtiyacımız var. SQL üzerinde FNH_Db adında adında bir database'imiz olsun ve bu db'ye ait aşağıda bulunduğu gibi User adında bir tablo oluşturalım.

CREATE TABLE dbo.Users  
(
    Id int PRIMARY KEY NOT NULL,  
    Name varchar(25) NOT NULL,  
    SurName varchar(25) NOT NULL
)  

User Tablosuna Karşılık Gelen User.cs Oluşturulması

Yukarıda tanımlamış olduğumuz User tablosuna karşılık gelen User.cs aşağıdaki gibi oluşturuyoruz.

public class User
   {
       public virtual int Id { get; set; }
       public virtual string Name { get; set; }
       public virtual string SurName { get; set; }
   }

Mapping Class'ının Oluşturulması

Database tablomuz ve buna karşılık gelen class'ımız hazır ancak halen User objesinde bulunan hangi alan tabloda bulunan hangi alana karşılık geliyor bunu belirtmedik. Bunun için Fluent NHibernate'e ait olan ClassMap adında bir class bulunmakta ve gerekli mapping işlemlerini yapacağımız class ClassMap<T>'den inherit olmuş şekilde oluşturulacak. Burda ki T bizim User class'ımız oluyor.

UserMap.cs adındaki mapping class'ı aşağıdaki gibi olacak şekilde tanımlıyoruz.

   public class UserMap : ClassMap<User>
   {
       public UserMap()
       {
           Id(x => x.Id);
           Map(x => x.Name);
           Map(x => x.SurName);
           Table("User");
       }
   }

DB Connection Sağlanması

Şimdiki işlem ise Fluent Nhibernate kullanarak yukarıda tanımlamış olduğumuz FNH_Db isimli database'e bağlanmak. Bunun için OpenSession adında bir metot oluşturalım.

       public static ISession OpenSession()
       {
           string connectionString = "FNH_Db conn string";
           var sessionFactory = Fluently.Configure()
               .Database(MsSqlConfiguration.MsSql2012
                .ConnectionString(connectionString).ShowSql()
               )
               .Mappings(m =>m.FluentMappings.AddFromAssemblyOf<User>())
               .ExposeConfiguration(cfg => new SchemaExport(cfg)
               .Create(false, false))
               .BuildSessionFactory();
           return sessionFactory.OpenSession();
       }

Tablo'da Bulunan Verileri Alma

Aşağıda yer alan kod bloğu ise db de bulunan tablodaki verileri query etmemizi sağlar.

using (var session = OpenSession())
     {
          var users = session.Query<User>().ToList();
     }

Yukarıda bulunan geliştirmeleri yaptığınızda db bulunan bir proje geliştirmede basitçe connection sağlayarak data-model ilişkisini kurabilirsiniz.  

Not: Yazıyı basit tutmak adına herhangi bir design pattern den bahsetmedim ancak Db olan yerde Crud işlemleri vardır, Crud olan yerde Ado.Net veya ORM vardır, ORM varsa Fluent NHibernate vardır ve Fluent NHibernate de Repository Design Pattern ile birlikte fıstıklı baklava gibi gider :) 

Fluent NHibernate ile ilgili daha detaylı bilgi almak ve geliştirmeleri takip edebilmek için bu linke göz atabilirsiniz.

IComparable Interface'ini Kullanarak Sıralama İşlemi

Primitive type'lar için sıralama işlemini bir şekilde list.Sort() vs gibi metotlar kullanarak yapabiliyoruz. Peki kendi yazdığımız objeler için bunu nasıl yaparız ? .Net tarafında iki objeyi belli field&property'lere göre sıralamamızı veya compare etmemizi sağlayan IComparable<T> interface'i bulunmakta. Bu interface ile List<T> tipidne tanımladığımız değişkenimize bizim belirttiğimiz kritere göre sıralama özelliği kazandırmış oluyoruz. Örnek bir case üzerinden gidelim; Rectangle adında bir objemiz olsun içerisinde Rectangle objeleri bulunan bir array'imiz olsun bu arrayde bulunan objeleri alanı en küçük olandan büyük olana doğru sıralayalım. İlk olarak Rectangle objemizi aşağıdaki gibi tanımlayalım.

    public class Rectangle 
    {
        public double Length { get; set; }
        public double Breadth { get; set; }

        public double Area
        {
            get
            {
                return Length * Breadth;
            }
        }
    }

Şimdi ise üsttede belirtiğimiz gibi dikdörtgen objelerini küçük olandan büyük olana doğru array'de sıralayalım. Bu işlem yapabilmenin bir yolu IComparable interface'ini kullanmak. Rectangle objemizi bu interface'den implement etmek. Implementasyon sonucunda Rectangle class'ı CompareTo adında bir metota sahip olur. Bu metot aracılığıyla compare işlemi yaparak array'imizi sıralayabiliriz. Rectangle class'ımızın implementasyon sonrasındaki görünümü aşağıdaki gibi olacaktır.

    public class Rectangle : IComparable<Rectangle>
    {
        public double Length { get; set; }
        public double Breadth { get; set; }

        public double Area
        {
            get
            {
                return Length * Breadth;
            }
        }

        public int CompareTo(Rectangle other)
        {
            if (this.Area == other.Area)
            {
                return this.Area.CompareTo(other.Area);
            }
            return other.Area.CompareTo(this.Area);
        }
        public override string ToString()
        {
            return this.Area.ToString();
        }
    }

 Şimdi ise sırada yazdığımız bu kodu test etme işlemi var. 

        public static void Main()
        {
            var list = new List<Rectangle>
            {
                new Rectangle() {Length = 7, Breadth = 3},
                new Rectangle() {Length = 5, Breadth = 1},
                new Rectangle() {Length = 9, Breadth = 4},
                new Rectangle() {Length = 2, Breadth = 7},
                new Rectangle() {Length = 3, Breadth = 5}
            };

            list.Sort();//Sort metodu ile array'i belirtmiş olduğumuz kritere göre sıralıyoruz

            foreach (var element in list)
            {
                Console.WriteLine(element);
            }
        }

Projeyi çalıştırdığımızda aşağıdaki gibi bir ekran elde etmiş olacağız.

36
21
15
14
5

 

ConcurrentStack ve ConcurrentQueue

Thread-safe collection ilk olarak .Net framework 4.0 ile System.Collections.Concurrent namespace'i altında hayatımıza girdi. System.Collections.Concurrent namespace'i altında thread-safe geliştirmeler yapmada kullanabilmek için ConcurrentStack ve ConcurrentQueue adında 2 tane tip bulunmaktadır.

ConcurrentStack

ConcurrentStack LIFO (last in first out) prensibine göre çalışan bir data stracture dır. Generic ConcurrentStack<T> thread safe olan Stack<T>'nin karşılığı olarak düşünebiliriz ve .Net Framework 4.0 ile hayatımıza girmiştir.

Bu class ile kullanılabilecek metotlar ; 

  1. Push(T element) T tipindeki objeyi stack'e eklemek için kullanılır,
  2. PushRange T tipinde ki array'leri stack'e eklemek için kullanılır,
  3. TryPop(out T) stack'te bulunan ilk item'i döner, başarılı ise true değilse false döner
  4. TryPeek(out T) stack'te bulunan bir sonraki item'ı almak için kullanılır ancak bu item'ı stack'ten silmez, başarılı ise true değilse false döner,
  5. TryPopRange TryPop metodu ile aynı çalışır sadece geriye tek bir obje değilde bir array döner.

 ConcurrentStack<T>'nin instance oluşturma ve Push metodu kullanımı aşağıdaki gibidir.

            var concurrentStack = new ConcurrentStack<int>();

            for (var i = 0; i < 10; i++)
            {
                concurrentStack.Push(i);
            }

Concurrent stack'te bulunan bir obje için get işlemi yapmak istediğimizde ise TryPop(out T) metodunu kullanıyoruz. 

            int outData;
            bool isSuccess = concurrentStack.TryPop(out outData);

Full kullanım örneği olarak aşağıda bulunan  metot 1'den başlayıp 100'e kadar olan sayıları Concurrent stack'e atıp sonrasında stack'ten okuma işlemini yapıp ekrana sayıları display etmektedir..

        public static void Main()
        {
            var concurrentStack = new ConcurrentStack<int>();

            for (var i = 1; i < 100; i++)
            {
                concurrentStack.Push(i);
            }

            while (concurrentStack.Count > 0)
            {
                int stackData;

                bool success = concurrentStack.TryPop(out stackData);

                if (success)
                {
                    Console.WriteLine(stackData);
                }
            }
        }

Projeyi çalıştırdığınızda 99'dan başlayıp 0'a kadar olan sayıları display edecektir.

ConcurrentStack aynı zamanda ToArray() property'sini destekler ve stack'i array'a convert etmemize olanak sağlar.

var integerArray = concurrentStack.ToArray();

Stack'in içerisinde item var mı yok mu diye kontrol etmek istediğimizde ise IsEmpty property'sini kullanarabiliriz.

if(!concurrentQueue.IsEmpty)
{
    ;//todo
}

 

ConcurrentQueue

ConcurrentQueue FIFO (first in first out) prensibine göre çalışan bir data stracture dır. Generic ConcurrentQueue<T> thread safe olan Queue<T>'nin karşılığı olarak düşünebiliriz ve yinde ConcurrentQueue de ConcurrentStack gibi .Net Framework 4.0 ile hayatımıza girmiştir.

 ConcurrentQueue<T>'nin bazı önemli metotlarını aşağıdakiler olarak sıralayabiliriz.

  1. Enqueue(T element)  T tipindeki objeyi queue'ye eklemek için kullanılır,
  2. TryPeek(out T) queue'da yada kuyrukta bulunan bir sonraki item'ı almak için kullanılır ancak bu item'ı queue'dan silmez, başarılı ise true değilse false döner,
  3. TryDequeue(out T) bu metot kuyruktaki ilk objeyi almak için kullanılır. TryPeek(out T) metodunun tersine objeyi aldıktan sonra kuyruktan siler, başarılı ise true değilse false döner,

Aşağıda bulunan code-snippet'ı nasıl integer değerler saklamak için ConcurrentQueue instance oluşturulur göstermektedir.

var concurrentQueue = new ConcurrentQueue<int>();

Kuyruğa integer değer atmak için ise aşağıdaki gibi Enqueue metodu kullanılabilir.

concurrentQueue.Enqueue(100);

Full kulalnıma bakacak olursak aşağıda bulunan  metot 1'den başlayıp 100'e kadar olan sayıları ConcurrentQueue'ye atıp sonrasında ConcurrentQueue'den okuma işlemini yapıp ekrana sayıları display etmektedir..

	public static void Main()
	{
		    var concurrentQueue = new ConcurrentQueue<int>();

            for (var i = 1; i < 100; i++)
            {
                concurrentQueue.Enqueue(i);
            }

            int queueData;

            while (concurrentQueue.TryDequeue(out queueData))
            {
                Console.WriteLine(queueData);
            }
	}

Projeyi çalıştırdığınızda 1'den başlayıp 100'e kadar olan sayıları ekrana display edecektir.

Hem ConcurrentStack hem de ConcurrentQueue class'ları thread safe'dirler ve internal olarak locking ve synchronization konularını yönetebilirler.

ConcurrentQueue aynı zamanda ToArray() property'sini destekler ve queue'yu array'a convert etmemize olanak sağlar.

var integerArray = concurrentQueue.ToArray();

Queue'nun içerisi boş mu dolu mu diye kontrol etmek istediğimizde ise IsEmpty property'sini kullanarabiliriz.

while(!concurrentQueue.IsEmpty)
{
     int result;

     concurrentQueue.TryDequeue(out result);

     Console.WriteLine(result);
}

Thread safe programlama ile uğraşıyorsanız ConcurrentStack ve ConcurrentQueue oldukça faydalı sınıflardır. Özellikle mesajlaşma yapıları & web service vs. gibi geliştirmeler gerektiren yerlerde biz developer'lara oldukça kolaylıklar sunmaktadır, unutmamakta fayda var :)

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.

 

Quartz .Net Nedir Nasıl Kullanılır ?

Quartz.Net, geliştirdiğiniz web tabanlı projelerde scheduled task yani zamana dayalı planlı görevler çalıştırmak istediğinizde çok kolay bir şekilde implement edip kullanabileceğiniz open source bir kütüphanedir. Aslında bir çok kişi bu tarz işlemleri yapmak için bir console uygulaması geliştirir ve windows server'da task schedular'a bu uygulamayı ne zaman çalıştıracağını söyler ve iş biter. Ancak gerek sunucuya erişimin kısıtlı olduğu durumlarda, gerekse ikinci bir uygulama geliştirmenin yol açtığı efor düşündüğünde quartz.net daha avantajlı gibi görünüyor.

Kullanım olarak iki aşamadan oluşuyor diyebiliriz,

  1. Çalışacak olan Job'ı belirleme,
  2. Hangi zaman aralıklarında çalışacağı bilgisi set edilir.

Quartz 3 ana bileşenden oluşur, job, trigger, schedular. Job yapılacak olan iştir. Trigger job'ın ne zaman, ne şekilde tetikleneceği emrini veren & tetikleyen yapıdır. Job ve Trigger ikisi birlikte schedular'a register olurlar ve kısaca çalışma şekli olarak; job schedular üzerinden trigger tarafından çalıştırılır diyebiliriz.

Örnek bir case üzerinden ilerleyelim; bir banka web service projemiz olsun ve müşterilere her gün saat 13:30 da push notification yollayan bir kampanyalar job'ı geliştirelim.

1. Quartz.Net Kurulumu

Kurulum için projenize sağ tıklayıp Manage Nuget Packages dedikten sonra aşağıdaki gibi nuget'ten projeyi install edebiliriz.

 

2. Job'ı Oluşturma

Bunun için ilk olarak IJob interface'inden implement olan CampaignPushJob adında bir class oluşturalım. IJob ile birlikte class'ımız Execute adında bir metoda sahip olur ve job çalışırken aslında bu metot içerisindeki akış çalışır diyebiliriz.

    public class CampaignPushJob : IJob
    {
        public void Execute(IJobExecutionContext context)
        {
            var userRepository = new UserRepository();
            var userList = userRepository.GetPNUserList();

            var pushNotText = "Size özel konut kredisi teklifleri.";

            var pnService = new PnService();
            pnService.SendPush(pushNotText, userList);
        }
    }

Örnekte PnService adında bir service'imiz olduğunu varsayalım ve SendPush metodu ile kullanıcılara notification gönderiliyor olsun.

3. Schedular Oluşturma

Bu adımda CampaignPushJobScheduler adında içerisinde ne zaman ve neyi çalıştıracağı bilgisini verdiğimiz schedular'ımızı yazıyoruz. JobBuilder.Create<CampaignPushJob>().Build() satırında da görüldüğü gibi bizden bir adet IJob interface'ini implement etmiş class istemekte. Bizde bir üstte yazdığımız CampaignPushJob'ını buraya set ediyoruz. Ne zaman çalıştıracağı bilgisini ise TriggerBuilder'ı initialize ederken set edeceğiz. Örneğimizde her gün saat 13:30 da çalışmasını istediğimiz bir job çalıştırmak istediğimiz için TriggerBuilder içerisinde aşağıdaki gibi bir tanımlama yaptık.

    public class CampaignPushJobScheduler
    {
        public static void Start()
        {
            IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
            scheduler.Start();

            IJobDetail job = JobBuilder.Create<CampaignPushJob>().Build();

            ITrigger trigger = TriggerBuilder.Create()
                .WithDailyTimeIntervalSchedule
                  (s =>
                     s
                    .OnEveryDay() //hergün çalışacağı bilgisi
                    .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(13, 30)) //hergün hangi saatte çalışacağı bilgisi
                  )
                .Build();

            scheduler.ScheduleJob(job, trigger);
        }
    }

4. Schedular Start Etme

Bu adıma kadar job, schedular ve trigger bilgilerimizi oluşturduk ve artık uygulama ayağa kalktıktan sonra schedular'ı start etmemiz gerekiyor. Geliştirmekte olduğumuz proje bir Asp.Net projesi olduğundan bu işlemi kolayca Global.asax dosyası içerisinde bulunan Application_Start metodunda yapabiliriz. Tek yapmamız gereken CampaignPushJobScheduler'ın içerisinde bulunan Start metodunu çağırmak.

    public class Global : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            CampaignPushJobScheduler.Start();
        }
    }

Quartz.Net implement etme işlemimiz bu kadar. Artık web projemiz bir kere ayağa kalktıktan sonra set ettiğimiz değerler doğrultusunda çalışacaktır.

Quartz schedular işlemler için .Net tarafında kullanılabilecek en yaygın kütüphanelerden bir tanesidir. Örnekte her ne kadar az kullanmış olsak da trigger time özelliğiyle ilgili kullanıcılara bir çok seçenek sunmakta. Bunlarla ilgili ayrıntılı bilgiyi bu linkte bulabilirsiniz.

Web Api Projelerinde Versiyonlama

Yazmış olduğunuz Api projelerinde versiyonlama yapmak oldukça önemlidir. Düzenli bir versiyonlama yaparak hem mevcut hemde yeni versiyonu kullanacak olan client'lar için tutarlı response'lar sağlayabilirsiniz. Örneğin v1.0 için çalışan listesi dönen bir api metodu yazdınız ve mevcutta kullanan kullanıcılar var. production'da. Sonra v2.0 de dediler ki çalışan isimlerinin yanında departman kodu da yazsın (Caner Tosuner, IT gibi). Her ne kadar kullanıcılar v2.0 geliştirmesini yapacak olsalarda belli bir süre daha eski versiyon için destek veriyor olmak gerekir çünkü bütün herkes aynı anda v2.0'a geçemeyebilir veya v1.0 da kalmak isteyebilir. İşte bu gibi durumları en iyi şekilde yönetilmemiz için versiyonlamayı son derece iyi yapmamız gerekir.

Web Api için versiyonlama yapmanın ortak kabul görmüş bir kaç yol bulunmakta;

  • URL de versiyon bilgisini query string olarak geçebiliriz,
  • Request Header'a "appVersion" gibi bir key/value ekleyebiliriz,
  • Genelde media type için kullanılsada AcceptHeader'a versiyon bilgisini ekleyebiliriz,
  • Her request'te BaseRequest'te içerisinde bir versiyon bilgisini alma.

Yukarıda sıralanan 4 yol da versiyonlama için çözüm ancak WebApi'ın son sürümüyle birlikte tercih edilmemeye başlandı hatta ve hatta yanlış çözüm olarak söyleyenler bile mevcut.

WebApi için versiyonlamayı Api metodlarının başına attribute olarak ekleyeceğimiz Route class'ı bulunmakta. Bu class'ı kullanarak kısaca hangi url bilgisi ile o metod çağrılabilir onu belirtmiş oluyoruz.

Burda dikkat etmemiz gereken konuların başında elimizden geldiğince mevcut URL'i bozmamaız gerekiyor.

Şimdi gelelim örneğimize.

Yukarıda bahsettiğimiz örnek üzerinden ilerleyelim ve EmployeeController adında bir ApiController'ımız var ve içerisinde GetEmployeeList() adında bir metod olsun. Bu metod öncelikle v1.0 için geriye çalışan adı ve soyadı bilgilerini içeren bir string array dönsün.

        [HttpGet]
        [Route("api/v1/Employee/{GetEmployeeList}")]
        public IHttpActionResult GetEmployeeList()
        {
            var list = new string[] { "İlhan Mansız", "Tümer Metin" };

            return Ok(list);
        }

Yukarıda ki gibi v1.0 için kullanıcılar bu metodu kullanmakta. GetEmployeeList() metoduna request'te bulunmak için url "http://localhost/api/v1/Employee/GetEmployeeList".

Ancak v2.0 da ad soyad bilgisinin yanında bir de departman adı istenmekte İlhan Mansız, Pazarlama gibi.

Şimdide v2.0 metodunu yazalım.

        [HttpGet]
        [Route("api/v2/Employee/{GetEmployeeList}")]
        public IHttpActionResult GetEmployeeListV2()
        {
            var list = new string[] { "İlhan Mansız, Pazarlama", "Tümer Metin, Satış" };

            return Ok(list);
        }

v2.0 metodunuda yazdık. Bu metoda request'te bulunmak için url "http://localhost/api/v2/Employee/GetEmployeeList".

Yukarıda da bahsettiğmiz gibi en dikkat etmemiz gerekn konuların başında client'ın yapacağı request'i ve api url'ini elimizden geldiğinde aynı tutmak ve bütün versiyonlarda url için değişen tek şeyin versiyon numarası olduğunu belirtmek. Böylelikle service tarafı için ayrımı url de bulunan versiyon numarasından alarak yapabiliriz.

Route attribute'ü kullanmadan versiyonlama işlemini farklı controller'lar kullanarak ta yapabilirsiniz. Üstteki örneğimizde olduğu gibi GetEmployeeList() meetodlarını içeren 2 farklı EmployeeV1Controller ve EmployeeV2Controller adında controller'lar tanımlayabiliriz. Url route işini de WebApiConfig.cs class'ı içerisine yine yukarıdaki url'leri kullanarak gerekli controller'lar da bulunan metodlara yönlendirmeleri yapabilirsiniz. Örneğin şu şekilde;

config.Routes.MapHttpRoute(
            name: "ApiV1_Route",
            routeTemplate: "api/v1/{controller}/{id}",
            defaults: new { controller = "EmployeeV1Controller", action = "GetEmployeeList", id = RouteParameter.Optional }
            );

config.Routes.MapHttpRoute(
            name: "Apiv2_Route",
            routeTemplate: "api/v2/{controller}/{id}",
            defaults: new { controller = "EmployeeV2Controller", action = "GetEmployeeList", id = RouteParameter.Optional }
            );

Yine "http://localhost/api/v1/Employee/GetEmployeeList" metoduna istekte bulunduğumuzda yukarıda yaptığımız config'den dolayı EmployeeV1Controller'ında bulunan GetEmployeeList metoduna yönlendirecektir. v2 için ise EmployeeV2Controller'ında bulunan GetEmployeeList metoduna yönlendirecektir.

 

Versiyonlama büyük çapta olan projeler için oldukça önemlidir. Production'a uygulamanızı çıktıktan sonraki zaman içerisinde tekrardan update'ler çıkmaya devam edeceksinizdir ve versiyonlama yaparak bu işlemleri yürütüyor olmak hem kolaylıklar sağlar hem de projenizi daha yönetilebilir hale getirir.

C# Custom Exception Oluşturma

Exception oluşturmak.. kulağa saçma geliyor dimi :) Heralde yazmış olduğumuz uygulamada bir exception meydana gelmesi en istemediğimiz şey dir. Ama bazen bilinçli olarak uygulamamızda exception fırlatmak isteyebiliriz (tabi handle edilmiş exception).

Exception uygulama çalışırken runtime da meydana gelen hatadır. Exception handling ise runtime da meydana gelen bu exception'ları yazılım tarafında ele alma tekniğidir diyebiliriz. Uygulamanızda bir exception meydana geldiğinde .Net kütüphanesinin direkt olarak fırlattığı stack trace açıklaması veya tuhaf exception mesajını kulanıcıya/client'a göndermek istemeyiz. Bu gibi durumlar için Custom Exception kullanımı devreye giriyor. Custom Exception kullanarak daha yönetilebilir ve daha anlamlı hata mesajları tanımlayabilir client'a dönebiliriz veya Log işlemlerinde handle ederken exception türüne göre daha farklı loglama işlemleri yapmak isteyebiliriz.

.Net tarafında bütün exception'ların base'i System.Exception class'ı dır ve custom tanımlanan exception class'larıda direkt veya dolaylı olarak bu class'tan inherit olmaktadır. Bizde yazacağımız custom exception class'ını System.Exception class'ından inherit edicez.

 

Custom Exception Tanımlama

Login işlemlerinde hata exception fırlatmak için kullanılacak LoginException adında bir custom exception tanımlayalım.

public class LoginException : System.Exception
    {
       //todo
    }

Şimdi uygulama içerisinde kullanımı için gerekli constructor tanımlamalarını yapalım.

   public class LoginException : System.Exception
    {
        public LoginException()
            : base()
        { }

        public LoginException(String message)
            : base(message)

        { }

        public LoginException(String message, Exception innerException)
            : base(message, innerException)
        { }

        protected LoginException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        { }
    }

Yukarıda görüldüğü üzre LoginException'nın constructor'larını tanımaldık ve System.Exception class'ı için yapılacak constructor yönlendirmelerini yaptık, böylece LoginException class'ını kullanıma hazır hale getirdik.

 

Custom Exception Kullanımı

Yazmış olduğumuz LoginException'ı kullanırken aşağıdaki gibi try/catch içerisinde handle edip kullanabilir veya Postsharp Exception Handling, Logging yazısında da olduğu gibi üst veya ayrı katmanlarda exception'ı handle edebiliriz.

 void Login(string userName, string password)
        {
            try
            {
                if (userName != "canert" && password != "qwerty123")
                    throw new LoginException("Invalid login operation");
            }
            catch (LoginException loginException)
            {
                Console.WriteLine(loginException.Message);
            }
        }

İhtiyaç dahilinde çok daha farklı case'lerde kullanılmak üzre Custom Exception'lar tanımlayabilirsiniz. Exception fırlatmak düz mantıkla düşünüldüğünde tuhaf gelebilir ancak çoğu projelerde hayat kurtarır ve yazmış olduğunuz kodları tek bir yerden yönetebilme veya reusability gibi konuları projelerinizde uygulayabilmek için tercih edebilirsiniz ancak bu exception'ları handle etmeyi unutmamalıyız.

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.  

yield nedir nasıl kullanılır

Bilineceği üzere bir class ın foreach iterasyonuna sahip olabilmesi için IEnumerable interfacesini implement etmesi gerekmekte. IEnumerable 'ı implemente eden class bu implementle birlikte override edilmesi gereken GetEnumerator metoduna sahip olur ve bu metodun içerisini doldurduktan sonra artik bu class da foreach ile gezebilecek duruma gelir. GetEnumerator metodunu override ederken, MoveNext(), Reset() metotlarini ve Current isimli propertyleri ile ilgili logic'i handle etmemiz gerekiyordu. İşte yield sayesinde bunları yapmaktan kurtuluyoruz. 

Mantigi oldukca basit ama bazi projelerde uygulamaya entegrasyonu sıkıntı çıkarabiliyor. Iste bu  sıkıntıları gidermek için C# 2.0 ile birlikte gelen yield keywordu sayesinde bu işlemlerin tumu bizim için arka planda yapilmis olacak.

Senaryomuz şu şekilde olsun; geriye db de kayıtlı olan ürünleri dönen bir metodumuz var ve bu metodu yield kullanmadan ve yield kullanarak yazmaya çalışalım.

Yield kullanmadan

     public static IEnumerable<Product> GetAllProducts()
            {
                using (var db = new DbEntity())
                {
                    var productList = from product in db.Product
                                      select product;
                    return productList.ToList();
                }
            }

 

Bu normal şartlarda kullandığımız yöntem. İlgile metodu aşağıdaki gibi çağırıp dönen listeye direk olarak erişebilirsiniz.

var productList = GetAllProducts();

yield kullanarak ise şu şekilde yazabiliriz 

Yield Kullanarak

public static IEnumerable<Product> GetAllProducts()
            {
                using (var db = new DbEntity())
                {
                    var productList = from product in db.Product
                                   select product;
                    foreach (var item in productList)
                    {
                        yield return product;
                    }
                }
            }

yield da ise şu şekilde çalışır;

döngüye her girdiğinde yield return product satırında fonksiyonun çağrıldığı yere ilgili product item'ını döner yani return'ü gördü diye foreach den ve metoddan direkt çıkmaz ve listenin içindeki tüm elemanlar bitinceye kadar bu işlemi yapmaya devam eder.

IL Disassambler(ILDASM) acarak code tarafında MoveNext,Reset ve Current gibi uyeler yazmamamiza ragmen yield keywordu sayesinde arka planda bunlarin yazildigini goruyoruz.

 Bir diğer örnek olarak ise şunu verebiliriz; 

public void Consumer()
{
    foreach(int i in Integers())
    {
        Console.WriteLine(i.ToString());
    }
}

public IEnumerable<int> Integers()
{
    yield return 1;
    yield return 2;
    yield return 4;
    yield return 8;
    yield return 16;
    yield return 45;
}

Foreach de dönerken her bir int i için IEnumerable<int> Integers() metoduna giderek değerleri bize teker teker döndürebiliyor.

C# "params" Kullanımı

C# dilinde yazmış olduğumuz bir metoda params keyword'ü ile parametre tanımlaması yaptığımızda bu o metodun çok sayıda parametresi olduğuna işaret eder ve bu parametreleri teker teker metodu çağırdığımız yerde ayrı ayrı "," ile ayırıp set etmek yerine bir sefer de parametre olarak verebiliriz. Parametre olarak bir array alan metod olmalı ve tanımlarken tip belirtecinden önce "params" keyword'ü eklenmelidir. Göndereceğimiz array tek boyutlu olmalı ve bir metodda tek bir params anahtar sözcüğü kullanmalıyız. Params keyword'ü alan ilgili metoda yanında başka parametrelerde gönderebiliriz. ancak dikkat etmemiz gereken şey params tanımlamasını tüm parametrelerden sonra yazmamız gerektiği.

Örnek olarak bir tane int array alan bir metod olsun ve metod bu aldığı array de bulunan sayıların toplamını geriye return etsin.

Params kullanmadan

int SayilariTopla(int a, int b, int c, int d, int e)
{
    return a+b+c+d+e;
}

static void Main(string[] args)
{
     int result = SayilarTopla(2,1,4,5,7);
}

Yukarıda ki örnekte görüldüğü gibi 5 tane int parametre alan bir metod ve geriye bu sayıların toplamını dönüyor. Peki bu metod 6-7 veya 1-2 parametre alarakta çalışmasını istersek ne yapacağız ? Ayrı ayrı birkaç metod daha yazacak değilizdir. İşte burda params'ın gücü devreye giriyor

 

Params kullanarak 

int SayilariTopla(params int[] sayilar)
{
  int toplam= 0;
  foreach (int i in sayilar) 
  {
     toplam+= i; 
  }
  return toplam;
}

static void Main(string[] args)
{
    int result1= SayilariTopla(5);
    int result2= SayilariTopla(1, 2,-3);
    int result3= SayilariTopla(-4,4,3,7,-7,1,8,0);
}

Params ile birlikte yukarıda ki gibi SayilariTopla metoduna artık istediğimiz kadar parametre geçip kullanabiliriz.