İlkay İlknur

just a developer...

C# - IoC Containers ve Castle Windsor IoC Container Kullanımı

Merhaba Arkadaşlar,

Önceki yazılarımızdan birinde sizlerle Dependency Injection konusunu incelemiştik. O yazıdan sonra gerek bloga yorum olarak gerekse mail yoluyla sizlerden pek çok olumlu feedbackler aldım. Bu nedenle IoC konusunda gerek kurumsal tarafta kullandığımız pratik kütüphanelerden olsun gerekse kişisel olarak kullandığım bazı kütüphanelerden bu yazıda ve ilerleyen yazılarda bahsetmeye karar verdim. Umarım sizler için faydalı olur.

Önceki yazılarımızdan da hatırlayacağımız üzere Dependency Injection, bizim genel olarak Inversion of Control adını verdiğimiz uygulama akışının çalışma zamanında değişmesini sağlayan ve uygulama içerisindeki bağımlılıkları minimuma indirgemeyi de amaçlayan prensibin özelleşmiş hatta bir pattern halini almış bir biçimiydi.

Önceki yazımızı hatırlarsak uygulama içerisindeki tüm yapıları kendimiz manuel olarak geliştirmiştik. Özellikle uygulama akışının değişmesini sağlayacak olan kısımlarda factory metotları kullanıp ilgili tiplerini çalışma zamanında otomatik olarak yaratılmasını sağlamıştık.

static IMessageSender CreateMessageSender()
{ 
 string messageSender = ConfigurationManager.AppSettings["message"].ToString(); 
 if (messageSender == "SMS") 
 return new SMSSender(); 
 return new MailSender(); 
} 
static ILogger CreateLogger() 
{ 
 string logger = ConfigurationManager.AppSettings["logger"].ToString(); 
 if (logger == "DB") 
 return new DBLogger(); 
 return new FileLogger(); 
}

Dependency Injection prensibini incelediğimizde bu prensip içerisindeki önemli noktalardan biri uygulama içerisindeki bileşenlerin birbirlerine sıkı sıkıya bağlı olmaması(loosely coupled) iken diğeri ise çalışma zamanında doğru tipin yaratılarak uygulama akışının sağlanması olduğunu görmekteyiz. Yukarıda gördüğümüz gibi factory metotlar kullanmak özellikle daha ufak projelerde işimizi görebilirken projeler büyüdükçe bu yöntem bizler için biraz sıkıntılı olabilmekte. Özellikle metotların uzaması, bakımının zorlaşması bizler için sorun çıkarmakta. İşte bu noktada Inversion of Control Containers (IoC Containers) dediğimiz kütüphaneler bizlerin yardımına koşmakta.

IoC Container’ların temel amaçlarını kısaca özetlememiz gerekirse, IoC Container’lar belirli konfigürasyonlara göre içerlerinde Dependency Injection sırasında uygulamanın akışını değiştirmek üzere kullanacağımız belirli Interface’leri implemente eden tipleri barındırırlar. Böylece bizler her Dependency Injection uyguladığımız bölümde yeniden factory metoda başvurup tip yaratma yerine doğrudan IoC Container’a başvuruda bulunuruz. Böylece IoC Container da içerisinde daha önceden bizim yaptığımız tanımlamayı kullanarak ilgili tipin üretilerek bize verilmesi görevini gerçekleştirir. Böylece yukarıda gördüğümüz gibi factory metotlar yazmamıza gerek kalmaz ve her yeni tip eklemesi olduğunda veya bir bölüme daha dependency Injection mekanizması kurmaya çalıştığımızda yeni bir factory metot yazmamıza gerek kalmaz.

Internette hemen kısa bir araştırma yaparsanız geliştirilmiş olan pek çok Ioc Container kütüphanesi görüyor olacaksınız. Microsoft Enterprise Library içerisinde bulunan Unity bloğu, Castle Windsor, Ninject projeleri benim hemen bir çırpıda sayabileceğim çözümler. Biz bu yazımızda Castle Windsor kütüphanesi üzerinden IoC container mekanizmasını inceliyor olacağız.

Castle Windsor

Castle Windsor kütüphanesini yaklaşık olarak 6 aydır kurumsal projelerimizde kullanmaktayız. Özellikle sağladığı basit IoC Container tanımlama yapısıyla ve yine IoC Container içerisinden tiplerin üretimi konusunda oldukça başarılı ve basit bir çözüm olduğunu sizlere söyleyebilirim.

Castle Windsor kütüphanesini kullanabilmek için öncellikle gerekli kütüphaneleri buradan indirmeniz gerekmekte. İndirdiğimiz zip klasörünü açarsak pek çok farklı runtime(Silverlight, .NET Framework) ve Framework (NET 3.5, .NET 4.0, .NET 4.0 Client Profiler) için geliştirilmiş olan kütüphaneleri görüyor olacağız.

Şimdi ilk olarak bir .NET 4.0 Console Uygulaması yaratalım ve indirdiğimiz kütüphanelerden dotNET40 klasöründeki Castle.Core ve Castle.Windsor kütüphanelerini projemize referans olarak ekleyelim.

Projemizin temel yapısı önceki yazımızdaki gibi olacak. Bu nedenle o yazıda kullanmış olduğumuz interface’leri ve bu interface’leri implemente eden tipleri aşağıda bulabilirsiniz.

interface IMessageSender     
{         
void SendMessage(string Message);
} 
interface ILogger     
{         
void WriteLog(string message);
}
 
classDBLogger:ILogger { 
publicvoid WriteLog(string message)
 {
Console.WriteLine(String.Format("DBLogger : {0}", message));
 }
}
 
classMailSender:IMessageSender 
{
publicvoid SendMessage(string message)
 {
Console.WriteLine(String.Format("MailSender : {0}", message));
 }
}
 
classFileLogger:ILogger 
{ 
publicvoid WriteLog(string message)
 {
Console.WriteLine(String.Format("FileLogger : {0}", message));
 }
}
 
classSMSSender:IMessageSender 
{
publicvoid SendMessage(string message)
 {
Console.WriteLine(String.Format("SMSSender : {0}", message));
 }
}

Yine de kısaca uygulamanın nasıl çalıştığından bahsetmemiz gerekirse, uygulamamız temel olarak loglama ve mesaj gönderme işlerini gerçekleştirmekte. Ancak bir takım ihtiyaçlara göre örneğin loglama işlemi Database ve File olarak 2 farklı şekilde yapılabilirken yine mesaj gönderme işlemi de Mail ve SMS yoluyla yapılabilmektedir. Bu gibi değişen ihtiyaçları karşılamak için bir önceki yazımızda Dependency Injection kullanarak hem uygulamamız içerisindeki bağımlılıkları en aza indirmiş hem de uygulamanın daha genişletilebilir ve esnek olmasını sağlamıştık. Şimdi yapacağımız ise IoC Container kullanarak ilgili tiplerin üretimini IoC Container tarafına bırakmak.

Castle Windsor’ın yapısına baktığımızda Container mekanizmasını konfigürasyonunun 2 şekilde yapılabildiğini görmekteyiz.

Bunlar,

  • Fluent arayüz ile kod tarafından
  • Config dosyası kullanılarak

Kod Tarafından IoC Container’ın Konfigürasyonu

Castle Windsor’ın bizlere sağlamış olduğu IoC Container yapısını konfigüre etmek için kullanacağımız yollardan biri de kütüphanenin bize sağlamış olduğu fluent arayüzü kullanmak. Bunun için tüm IoC Container işlemlerini yönetmek için bir basit bir sınıf yazıyor olacağız. Bu sınıf temel olarak içerisinde IoC Container’ı yaratacak ve daha sonra da bizim parametre olarak vereceğimiz interface’I implemente eden ve konfigürasyon sırasında tanımlamasını yaptığımız tipi döndürüyor olacak. İlk olarak IoC Container’ı verilen konfigürasyonlara göre yaratan metodumuzu yazalım.

private static IWindsorContainer BootstrapContainer()         
{             
 return new WindsorContainer().Register(                     
 Component.For<IMessageSender>().ImplementedBy<SMSSender>(),                     
 Component.For<ILogger>().ImplementedBy<DBLogger>());
}

Yukarıdaki metoda baktığımızda aslında basit bir factory metot olduğunu görmekteyiz. Metodumuz IWindsorContainer interface’ini implemente eden bir IoC Container döndürmekte. Metodun içerisinde ise  IWindsorContainer interface’ini implemente eden WindsorContainer tipinden bir nesne yaratarak daha sonra bu tipin içerisinde bulunan Register metodunu kullanarak Dependency Injection sırasında kullanacağımız tiplerin konfigürasyonunu yapmaktayız. Windsor developerlara fluent bir arayüz sunduğu için aslında Register metodu içerisini okuyarak bile nasıl bir tanımlama yapıldığını anlamamız mümkün. Türkçe olarak metodun içerisinde yapılan tanımı özetlersek,

  • Ben senden IMessageSender interface'ini implemente eden tipi istediğimde bana bir SMSSender nesnesi ver.
  • Ben senden ILogger interface'ini implemente eden tipi istediğimde bana bir DBLogger nesnesi ver.

Gördüğünüz gibi aslında oldukça basit bir tanımlama yaparak hızlı bir şekilde IoC Container’ı yarattık. Şimdi ise yapmamız gereken ise ihtiyacımız olduğunda bize ilgili tipi döndürecek olan basit bir metot. Hemen bu metodu da yazalım ve ilerleyelim. :)

public static T Resolve<T>()         
{             
 return Container.Resolve<T>();         
}

Gördüğünüz gibi oldukça basit bir metot yazdık. Generic olarak geliştirdiğimiz bu metoda generic olarak IoC Container içerisinden almak istediğimiz tipin implemente ettiği interface’i veriyor olacağız. Daha sonra metot içerisinde ise Windsor’ın bize sağlamış olduğu Resolve metodu ile gereken tipi geri dönüyor olacağız.

Bir de son olarak tüm bağımlılık tanımlamalarımızı kod içerisinden yaptığımız için uygulama akışının çalışma zamanında değişmesi mümkün değil. Bu yüzden IoC Container’ımızı Singleton yapmamız doğru olacaktır. Singleton tanımlamamızı yaptıktan sonra tüm IoC Container işlemlerini yönettiğimiz IoCUtil isimli sınıfımızın tanımı şu şekilde olacaktır.

public static class IoCUtil     
{         
 private static IWindsorContainer _container = null;         
 private static IWindsorContainer Container          
 {              
 get             
 {                 
 if (_container == null)                 
 {                     
 _container = BootstrapContainer();                 
 }                 
 return _container;             
 }         
 }         

 private static IWindsorContainer BootstrapContainer()         
 {             
 return new WindsorContainer().Register(                     
 Component.For<IMessageSender>().ImplementedBy<SMSSender>(),
               Component.For<ILogger>().ImplementedBy<DBLogger>());
      }         

 public static T Resolve<T>()         
 {             
 return Container.Resolve<T>();         
 }     
}

Şu ana kadar baktığımızda aslında IoC Container entegrasyonunun %90’lık kısmını bitirmiş bulunmaktayız. Şimdi son olarak geriye kalan kısım ise Dependency Injection uyguladığımız kısımlarda factory metotların kullanımını kaldırmak ve IoCUtil sınıfının Resolve metodu ile ihtiyacımız olan nesneleri almak.

class Processor     
{         
 ILogger logger = null;         
 IMessageSender messageSender;         

 public Processor()         
 {             
 logger = IoCUtil.Resolve<ILogger>();             
 messageSender =IoCUtil.Resolve<IMessageSender>();         
 }         
 public void Process()         
 {             
 logger.WriteLog("Log Text");             
 messageSender.SendMessage("Message Text");         
 }
}

Evet uygulamamız içerisinde IoC Container entagrasyonunu da tamamladık. Şimdi uygulamamızı çalıştıralım ve ürettiği çıktıya bir bakalım. ;)

Gördüğünüz gibi IoCUtil sınıfı içerisindeki BootstrapContainer metodu içerisinde yaptığımız tanımlamalara uygun çıktıyı elde ettik. Dilerseniz bu tanımlamaları değiştirerek uygulama akışının nasıl değiştiğini gözlemleyebilirsiniz.

Bölümün başında Windsor’ın Container tanımlaması yapmak için fluent arayüz dışında bir de config dosyası desteği olduğundan bahsetmiştim. Şimdi gelin bir de son olarak IoC Container içerisindeki tanımlamaları konfigürasyon dosyaları içerisinden nasıl yapabiliriz bu konuyu inceleyelim.

Config Dosyası İle IoC Container’ın Konfigürasyonu

Konfigürasyon tanımlaması kısmına baktığımızda aslında fluent interface’ten çokta fazla bir farkın olmadığını görüyoruz. İlk olarak config section tanımlamasını yaptıktan sonra component elementleri içerisindeki service ve type attributeleri ile ilgili IoC Container tanımlamasını yapabilmekteyiz. Service attribute’ü bizim ilgili interface’imizi belirtirken type attribute’ü ise IoC Container’a ilgili interface ile başvuruda bulunduktan sonra geri alacağımız tipi göstermekte. Bir de son olarak component elementinin unique bir id’si olması gerekmekte.

<?xml version="1.0"?> 
<configuration>   
<startup>     
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>   
</startup>   
<configSections>     
<section  name="castle" 
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, 
 Castle.Windsor" />   
</configSections>   
<castle>     
<components>       
<component id="logger" service="Windsor.Project.Interfaces.ILogger, Windsor.Project"      
type="Windsor.Project.Loggers.DBLogger, Windsor.Project"/>       
<component id="repository" service="Windsor.Project.Interfaces.IMessageSender, Windsor.Project"      
type="Windsor.Project.MessageSenders.SMSSender, Windsor.Project"/>     
</components>   
</castle> 
</configuration>

Konfigürasyon dosyası içerisinde tanımlamalarımızı yaptıktan sonra son olarak IoC Container yaratma sırasında konfigürasyon dosyası içerisindeki castle elementinin içerisindeki tanımlamalarının kullanılmasını belirtmemiz için IoC Container yaratan BootstrapContainer isimli factory metodunda şu şekilde bir değişiklik yapmamız gerekmekte.

private static IWindsorContainer BootstrapContainer()         
{             
 return new WindsorContainer(new XmlInterpreter(new ConfigResource("castle")));
}

Konfigürasyon dosyası kullanarak IoC Container tanımlamak için yapmamız gerekenler bunlar. Son olarak yine uygulamamızı çalıştıralım ve oluşan çıktıya bakalım.

Evet arkadaşlar bu yazımızda Dependency Injection pattern’I içerisinde işlerinizi oldukça kolaylaştıracak olan IoC Container yapısını Castle.Windsor kütüphanesi ile beraber inceledik. Sizlerde projelerinizde bu kütüphaneyi rahatlıkla kullanabilirsiniz. Tabi biz yazımız boyunca kütüphanenin çok küçük bir kısmını inceledik. Buradaki adresten çok daha detaylı bilgileri elde edebilirsiniz. Aşağıdaki linkten ise yazı boyunca işlediğimiz örnekleri indirebilirsiniz.

Umarım yazım sizler içn faydalı olmuştur.

Hoşçakalın,

Windsor.Project Not : Blog yazıları haricinde sosyal medya üzerinden anlık paylaşımlarımı Twitter ve  Facebook'tan takip edebilirsiniz.


Yorum Gönder


Yorumlar

  • profile

    Recep Şerit

    • 9
    • 8
    • 2017

    Çok faydalu bir makale olmuş. Elinize sağlık.

  • profile

    Tarık Özgün GÜNER

    • 26
    • 6
    • 2014

    Merhaba İlkay Bey, “C# – IoC Containers ve Castle Windsor IoC Container Kullanımı” konulu yazınızı büyük bir zevkle okudum. Diğer Türk yazarlar copy-paste yaptıkları makaleleri rezil bir şekilde çevirmişken siz çok kolayca anlaşılır bir dilde açıklamışsınız. Yine de, IoC sınıflarından Ninject benim gibi başlangıç seviyesinde olanlar için sanki bana daha basit gibi geldi. Yazınız için elinize sağlık.

  • profile

    volkan

    • 20
    • 11
    • 2013

    Sanırım çözüm 2 farklı container tanımlamak :)

  • profile

    volkan

    • 20
    • 11
    • 2013

    Yukarıda 2 logger ve 2 sender sınıfı tanımlı ve bunlar ilgili interface'leri implement ediyorlar. Ben aynı uygulama içerisinde hem SmsSender hem de MailSender kullanmak istediğimde bunu bootstrapper içerisinde nasıl tanımlarım ve nasıl çağırırım. Sonuç olarak görüyorum ki siz yalnızca birer tane register etmişsiniz. Ben ikisini de register edip, farklı servislerde kullanabilmek istediğimde akışı nasıl sağlarım?

  • profile

    kaan

    • 4
    • 8
    • 2012

    yanlış anlamışım; IWindsorContainer Container; şeklinde tanımlı

  • profile

    kaan

    • 4
    • 8
    • 2012

    public static T Resolve() { return Container.Resolve(); } Buradaki "Container", system.companentmodel.container namespacei altında anladığım kadarı ile; Resolve() gibi bir metodu görünmüyor. Altını çiziyor yani. yada Container adında başka bir makalede class mı oluşturmuştunuz?

  • profile

    Serdar

    • 2
    • 12
    • 2011

    Çok güzel bir yazı olmuş.Elinize saglık.

  • profile

    ilkayilknur

    • 29
    • 11
    • 2011

    @Ahmet Merhaba, Aslında IoC Container'ların faydasını uygulamanız büyüdükçe ve karmaşıklaştıkça daha da fazla görmektesiniz. Ufak projelerde veya sadece 1-2 yerde dependency injection kullanılacaksa IoC Container kullanmanın çokta büyük bir kazancı olmayacaktır size. Aksine efor kaybına bile neden olur eğer kullanmayı bilmiyorsanız. Ancak uygulamanızda çok çeşitli bölümlerinde Dependency Injection kullanıyorsanız belirli bir noktadan sonra bu işlerin yönetimi zorlaşmakta. İşte tam da bu noktada IoC Container'lar size büyük zaman kazandırmakta. Ayrıca yukarıda yazıda yazmış olduğum kütüphaneler sadece IoC Container görevi yapmamakta. Bunun yanında örneğin IoC Interceptor gibi yapılarıda sağlamakta.

  • profile

    ahmet

    • 29
    • 11
    • 2011

    Merhaba; Benim sormak istediğim burda loC kütüphanesinin ne gibi bir rolü olduğu. Sonuçta interface ile class eşleştirmesini ve buna göre nesne türetimini yapmak birkaç basit koddan ibaret. Unity ve Windsor gibi kütüphaneler fazladan ne yapıyor ki onları kullanıyoruz. Birşeyi kaçırıyorum ama neyi bulamadım.

  • profile

    Mesut

    • 25
    • 11
    • 2011

    Teşekkürler paylaşımın için. Akşam pratiğini yapacağım :)