Dependency Injection Container

Önceki yazıda uygulamaları oluşturan bileşenlerin birbiri ile gevşek bağlı haberleşmesi gerektiğini ve bunun için interface kullanılabileceğini örneklemiştik. Bu yazıda bağımlılıkları daha esnek nasıl yönetebileceğimizi inceliyor olacağız.

Dependency Injection Design Pattern
Bağımlılıkların tasarım zamanı yerine çalışma zamanında yüklenmesini tarifleyen tasarım desendir. Bu disipline uygun şekilde tasarlanan uygulamalar da tüm uygulamanın yeniden derlenmesine gerek kalmadan yalnızca değişen kısımları derlenerek uygulama güncellenebilir.

Dependency Injection Design Pattern’in kolayca uygulanabilmesi için geliştirilmiş pek çok hazır kütüphane bulunmaktadır. Dependency Injection Container olarak bilinen bu kütüphaneler konfigürasyon dosyaları veya kod üzerinden tanımlanan bağımlılıkları talep edildiğinde nesnelerimize bağlayarak (inject) kullanıma sunarlar.

Castle Windsor, Spring.Net, Ninject ve Microsoft tarafından desteklenen açık kaynak kodlu Unity Container kullanılabilecek DI kütüphaneleridir. Daha geniş bir liste için burayı ziyaret edebilirsiniz. Biz bu makalemizde Unity Container kütüphanesini kullanacağız.

Kütüphaneyi nuget üzerinden yükleyebiliriz. Yeni bir Console Uygulaması açalım ve References klasörü üzerinde Manage Nuget Packages menusüne tıklayalım.

unity-nuget-1

Browse sekmesinde unity yazıp gelen listede en üstte yer alan 5.5.2 sürümünü seçip install düğmesine tıklayalım

unity-nuget-2

Unity Application Block’u kullanabilmek için
Unity.Configuration, Unity.Abstractions, Unity.RegistrationByConvention ve Unity.Container dosyalarını projemize eklememiz gerekiyor.

unity-nuget-3

Kütüphanenin temel iki işlemi vardır. Registration ve Resolution

REGISTRATION
Bağımlılıkların kütüphaneye kaydedilmesi işlemidir. Fluent arayüz ile kod seviyesinde veya deklaratif olarak konfigürasyon dosyaları (web.config/app.conifg) üzerinden yapılabilir. Tanımlama işlemi yönetimsel kolaylığı sağlamak için tek bir noktadan (Main, App_Start) yapılması önerilmektedir. Bağımlılıkların container’a kaydedilmesi farklı şekillerde olabilir;

  • Type Registration
    RegisterType metodu ile yapılır. Container’a doğrudan bir tip kaydı yapılabileceği gibi. Bir interface’i implement etmiş veya base sınıftan türemiş bir tipin kaydı da yapılabilir.

    //Unity Container Olusturulur
    IUnityContainer container = new UnityContainer();
    //Arabirim üzerinden tanımlama
    container.RegisterType<ILogger, FileLogger>();
    //Dogrudan tanımlama
    container.RegisterType<DbLogger>();
    
  • Named Registration
    Aynı tipten türemiş birden fazla tip container’a kaydedilebilir. Bu durumda container, talep edildiğinde en son eklenen nesne örneğini döndürür. Tiplerin container’a kaydı isimlendirilebilir. Böylelik çalışma zamanında istenen tip verilen isim üzerinden yüklenebilir. Başka bir çözüm de child container’oluşturup tipleri farklı container’lara kaydedebiliriz. Bunun avantajı eğer child container’de aranan tip bulunmaz ise otomatik olarak parent container üzerindeki eşleşme döndürülür.

    IUnityContainer container = new UnityContainer();
    container.RegisterType<ILogger, FileLogger>("FileLogger");
    container.RegisterType<ILogger, DbLogger>("DbLogger");
    // ya da child container kullanabiliriz
    var childContainer = container.CreateChildContainer();
    childContainer.RegisterType<ILogger, DbLogger>();
    
  • Instance Registration
    RegisterInstance metodu ile var olan bir nesne örneği de container’a kaydedilebilir. Instance kaydı Singleton olarak gerçekleştirilir.

    IUnityContainer container = new UnityContainer();
    var constants = new Constants()
    container.RegisterInstance(constans);
    
  • Registration by Convention (Auto Registration)
    RegisterTypes metodu ile yapılan otomatik kayıt işlemi, belirlenen kurallara göre uygulamadaki bileşenleri tarar ve kurala uygun olanları container’a yükler. Böylece kayıt işlemleri ile daha az zaman harcanır. Bu tarz kayıt işlemi çok fazla tanımlamanın olduğu durumda anlamlıdır. Yalnızca birkaç tip kaydı yapılacak ise manuel tanımlamak daha doğru olur.

    //uygulama domaininde yuklu soyut olmayan tum tipler
    //icerisinde hedefnamespace altında bulunan belirtilen
    //interface'i implement etmis tipleri container'a kaydeder
    
    container.RegisterTypes(
     AllClasses.FromLoadedAssemblies().Where(
     t => t.Namespace == "HedefNamespace"),
     WithMappings.MatchingInterface);
    

LifeTimeManager
Varsayılan olarak container tip oluşturma işlemlerinde her talepte yeni bir instance oluşturur. Bu davranış LifeTimeManager nesneleri ile yönetilir. Bağımlılık singleton olarak oluşturulacaksa ContainerControlledLifetimeManager, her ihtiyaçta tekrar oluşturulacaksa ExternallyControlledLifetimeManager sınıfları kullanılır.

var singletonLifeTime = new ContainerControlledLifetimeManager();
var externallyLifetime = new ExternallyControlledLifetimeManager();
IUnityContainer container = new UnityContainer();
container.RegisterType<ILogger, FileLogger>(singletonLifeTime);
container.RegisterType<ILogger, DbLogger>(externallyLifetime);

Web projelerinde request bazında nesne oluşturmak için PerRequestLifetimeManager, Multi Thread Projelerde thread bazında nesne oluşturmak için PerThreadLifetimeManager nesneleri kullanılabilir.

Konfigürasyon Dosyası İle Bağımlılıkların Kaydedilmesi
Kod ile bağımlılık tanımlama işlemi kolay ve kullanışlı olsa da kayıt işleminde veya bileşenlerde bir değişiklik olduğunda yeniden derleme gerektirir. Bağımlılıkları ana projeden ayırıp, app.config dosyasında aşağıda belirtildiği şekilde tanımlamaları yaparak bu problemi aşabiliriz.

<configuration>
 <configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.    UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
 </configSections>
 <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
 <namespace name="Unity.Test.Types" />
 <container>
   <register type="ILogger" mapTo="FileLogger" name="fileLogger" />
   <register type="ILogger" mapTo="DbLoger" name="dbLogger" />
 </container>
 </unity>
</configuration>

Tanımlanan bağımlılıklar app.config dosyası ile aynı dizinde olmalı aksi takdirde “The given assembly name or codebase was invalid” hatası alırız

Konfigürasyon dosyasını üzerinden bağımlılıkları yüklemek için Unity.Configuration isim alanında yer alan tipler kullanılır.

IUnityContainer container = new UnityContainer();
UnityConfigurationSection unityConfig =(UnityConfigurationSection)ConfigurationManager.GetSection("unity");
unityConfig.Configure(container);

Tanımlı bağımlılıkların listelenmesi
Container nesnesinin Registrations özelliği ile tanımlar hakkında bilgi alabiliriz.

Console.WriteLine("Kayıt sayısı = {0}:",container.Registrations.Count());
foreach (ContainerRegistration item in container.Registrations)
{
 Console.WriteLine(item.GetMappingAsString());
}

RESOLUTION
Container’a eklenen bağımlılıklar’ın çalışma zamanında Constructor, Property ve metototlara enjekte edilmesi işlemidir.

  • Constructor Injection
    Bağımlılıkların yapıcı metot aracılığı ile nesneye enjekte edilmesidir. Nesnenin birden fazla yapıcı metodu varsa [InjectionConstructor] attributu ile cntainer’a hangi yapıcı metodu kullanacağı söylenir.

    public interface ILogger
    {
        void Write(string Exception);
    }
    public class FileLogger : ILogger
    {
        public void Write(string message)
        {
           Console.WriteLine("File Log = " + message);
        }
    }
    public class DbLogger : ILoger
    {
        public void Write(string message)
        {
            Console.WriteLine("Db Log = " + message);
        }
    }
    
    public class LogManager
    {
        private ILogger _logger;
    
        [InjectionConstructor]
        public LogManager(ILogger logger)
        {
            _logger = logger;
        }
    
        public LogManager(string logerName)
        {
    
        }
    
        public void Write(string message)
        {
            _loger.Write(message);
        }
    }
    
    var container = new UnityContainer();
    container.RegisterType<ILogger, FileLogger>();
    
    //container LogManager nesne ornegini olustururken
    //constructor üzerinden bağımlılık sağlanır
    var logManager = container.Resolve<LogManager>();
    logManager.Write("Test");
    

    Çalışma zamanında dinamik olarak da constructor injection yapılabilir. Bunun için InjectionConstructor nesnesi kullanılır.

    container.RegisterType<LogManager>(new InjectionConstructor(new FileLogger()));
    
    //veya
    
    container.RegisterType<ILogger, FileLogger>();
    container.RegisterType<LogManager>(new InjectionConstructor(container.Resolve<ILogger>()));
    
  • Property Injection
    Bağımlılıkların property aracılığı ile nesneye enjekte edilmesidir. Bunun için [Dependency] attribute kullanılır. Attribut’a name degeri parametre olarak geçirilerek istenilen bağımlılık enjekte edilebilir.

    var container = new UnityContainer();
    container.RegisterType<ILoger, FileLoger>("filelogger");
    container.RegisterType<ILoger, DbLoger>("dblogger");
    public class LogManager
    {
       [Dependency("filelogger")]
       public ILogManager Logger {get; set;}
    }
    //Container LogManager nesnesini olusturdugunda Logger property'sine
    //FileLogger nesnesinin bir örneğini oluşturarak atar.
    var logManager = container.Resolve<LogManager>();
    

    Çalışma zamanında dinamik olarak InjectionProperty nesnesini kullanarak da Property Injection yapılabilir.

    var container = new UnityContainer();
    //Calisma zamanında Property Injection
    container.RegisterType<LogManager>(new InjectionProperty("Logger", new FileLoger()));
    
    var logManager = container.Resolve<LogManager>();
    logManager.Write();
    
  • Method Injection
    Bağımlılıkların metot parametreleri aracılığı ile enjekte edilmesidir. Bunun için InjectionMethod attribute kullanılır.

    public class LogManager
    {
        public LogManager() 
        {
        }
        [InjectionMethod]
        public void WriteLog(ILogger logger) {
            logger.Write("Test");
        }
    }
    

    Çalışma zamanında InjectionMethod nesnesini kullanarak dinamik Method Injection yapılabilir.

    var container = new UnityContainer();
    
    //Calisma zamanında Method Injection
    container.RegisterType<LogManager>(new InjectionMethod("WriteLog", new FileLogger()));
    
    var logManager = container.Resolve<LogManager>();
    logManager.WriteLog();
    

Deferred Resolution

Bazen, bir nesneyi container’dan alıp instance oluşturma işlemini ihtiyaç duyulduğunda gerçekleştirmek gerekebilir. Bunun için Lazy kullanılır

var defaultValue = container.Resolve<Lazy<ILogger>>();
var InstanceObject = defaultValue.Value;

Sağlıklı ve huzurlu günler dilerim


Bir Cevap Yazın

Aşağıya bilgilerinizi girin veya oturum açmak için bir simgeye tıklayın:

WordPress.com Logosu

WordPress.com hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Google+ fotoğrafı

Google+ hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Twitter resmi

Twitter hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Facebook fotoğrafı

Facebook hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Connecting to %s