Ninject, Otomatik Tür Bağlamaları

   Ninject konusunda, önceki makalelerim ardından temel düzeyde ve hızla projelerimizde uygulayabileceğimiz bilgi birikimine sahip olduğunuza inanıyorum. Artık sıra yavaş yavaş daha ileri konulardan bahsetmekte. İlk konumuz tür bağlamalarında otomasyona gitmek…

   Diyelim ki; önceki makalelerimde verdiğim örneklerden farklı olarak, modul projelerimizin içerisinde bağlanması gereken pek çok modülümüz bulunuyor. Bu durumda hepsini tek tek modül içerisinde bağlamaya çalışmak hem zaman kaybına neden olacaktır, hem de hataya/unutkanlığa çok açık olacaktır. Kafanızda canlandırabilmek adına, aşağıdaki örneğe bir göz atalım;

public class AnaModul : NinjectModule {
    public override void Load() {
        Bind<IVeriDeposu>().To<XmlDosyaVeriDesposu>();
        Bind<IGunlukDeposu>().To<TextGunlukDeposu>();
        Bind<IKullaniciDeposu>().To<KullaniciDeposu>();
        Bind<IGrupDeposu>().To<GrupDeposu>();
        Bind<IYetkilendirmeDeposu>().To<YetkilendirmeDeposu>();
        Bind<IYapilandirmaDeposu>().To<YapilandirmaDeposu>();
        Bind<IKurulumDeposu>().To<KurulumDeposu>();
        Bind<IKimlikDogrulamaDeposu>().To<KimlikDogrulamaDeposu>();
        Bind<IBirimDeposu>().To<BirimDeposu>();
        Bind<IHariciYetkilendirmeDeposu>().To<HariciYetkilendirmeDeposu>();

        //v.b.
        //v.b.
        //v.b.
    }
}

   Dolayısıyla bizim bir şekilde çoklu tür bağlama işini otomatize etmemiz gerekiyor. Bu noktada imdadımıza Ninject’in Ninject.Extensions.Conventions eklentisi yetişiyor.

Ninject.Extensions.Conventions

   Ninject.Extensions.Conventions NuGet paketini projemizi yüklememiz ardından IKernel arayüzü için yeni bir genişletme fonksiyonunun tanımlandığını göreceksiniz;

public static void Bind(this IBindingRoot kernel, Action<Ninject.Extensions.Conventions.Syntax.IFromSyntax> configure)

   Öncelikle, bu genişletme fonksiyonunun Ninject.Extensions.Conventions namespace’i altında tanımlanmış olması nedeniyle using’lerinizi arasına bunu da eklemelisiniz. Sonrasında aşağıdaki şekilde fonksiyon çağrıları yapabilmeniz mümkün;

IKernel kernel = new StandardKernel();

kernel.Bind(yapilandirma => {
    yapilandirma.FromThisAssembly()
         .SelectAllClasses()
         .BindSingleInterface();
});

  Yukarıdaki kod parçacığı sayesinde içinde bulunduğumuz assembly içerisinde yer alan tüm public sınıflar seçilerek implement’e ettikleri tek bir arayüz için otomatik olarak tür bağlamaları yapılacaktır. Bizim örneğimiz üzerinden gidersek, ayrı bir assembly içerisinde tanımladığımız modülümüzün aynı işlemi yapmasını aşağıdaki şekilde sağlayabiliriz;

public class AnaModul : NinjectModule {
    public override void Load() {
        this.Bind(yapilandirma => {
            yapilandirma.FromThisAssembly()
            .SelectAllClasses()
            .BindDefaultInterface();
        });
    }
}

Not : Örnekte Bind fonksiyonunu this üzerinden giderek kullandığımın altını çizmek isterim. Aksi takdir’de genişletme fonksiyonumuzu kullanamayız…

   Gerçek hayattaki projelerimizde modülleriniz içerisindeki sınıflarınızın dışarıdan doğrudan görülebilmesi (public tanımlanması) tavsiye etmediğim bir durum. Sizde benimle aynı düşünüyor ve hepsini internal olarak işaretlediyseniz yukarıdaki örneği IncludingNonePublicTypes çağrısınıda ekleyerek aşağıdaki şekilde kullanabilirsiniz;

public class AnaModul : NinjectModule {
    public override void Load() {
        this.Bind(yapilandirma => {
            yapilandirma.FromThisAssembly()
                .IncludingNonePublicTypes()
                .SelectAllClasses()
                .BindDefaultInterface();
        });
    }
}

   Örneğimizi geliştirmeye devam edelim. Bir sonraki adımda abstract bir sınıftan türeyen tüm türler için bağlama yaptığınızı varsayalım. Bu durumda kodumuzun son hali şu şekilde olacaktır;

public class AnaModul : NinjectModule {
    public override void Load() {
        this.Bind(yapilandirma => {
            yapilandirma.FromThisAssembly()
                .IncludingNonePublicTypes()
                .SelectAllClasses()
                .InheritedFrom<AbstractSinif>()
                .BindBase();
        });
    }
}

Örneğimizin bulduğu türleri temel sınıflarına bağladığının altını çiziyorum.

   Gerçek hayattaki kullanımlarda kimi zaman tür bağlamalarını daha detaylı belirtmek gerekebilir. Bu konuda aklıma gelen ilk örnek domain’imize ait arayüzleri implement’e eden sınıfların belirli bir namespace altında toplanması geliyor. Bu senaryoda Bind fonksiyonumuzu aşağıdaki şekilde namespace’de vererek daha detaylandırmamız mümkün;

public class AnaModul : NinjectModule {
    public override void Load() {
        this.Bind(yapilandirma => {
            yapilandirma.FromThisAssembly()
                .IncludingNonePublicTypes()
                .SelectAllClasses().InNamespaces("Com.Enterprisecoding.DependencyInjection.Imp")
                .InheritedFrom<IVeriDeposu>()
                .BindBase();
        });
    }
}

   Ninject.Extensions.Conventions sorgularının yapısını incelediğimizde 5 parçadan oluştuğunu görebiliriz;

  1. Assembly seçimi,
  2. Tür/Bileşen seçimi,
  3. İstisna seçimi,
  4. Bağlanacak servis seçimi,
  5. Bağlamaların yapılandırılması

Assembly Seçimi;

   Örneklerimizde varsayılan olarak içinde bulunduğumuz assembly’de tür seçimi yaptığımıza hatırlatmak isterim.  Ninject.Extensions.Conventions assembly seçimi konusunda gerçekten de çok esnektir.

   FromThisAssembly fonksiyonu ile yaptığımız bu seçime alternatif olarak From fonksiyonu ile assembly veya assembly adını vererek işlem yapabilmemiz mümkün. Bunun yanında; FromAssemblyContaining fonksiyonu ile belirtiğimiz türün dahil olduğu assembly’yi, FromAssembliesInPath fonksiyonu ile belirli bir klasör altındaki assembly’leri, FromAssembliesMatching fonksiyonu ile belirli bir kalıba göre adlandırılmış assembly’leri kabul etmemiz mümkün.

   Tüm bu saydıklarım yanında, Ninject.Extensions.Conventions esnekliğini bir adım daha ileri taşıyarak Join fonksiyonu yardımıyla birden fazla assembly bağlamasını tek kalemde yapabilmenize imkan vermektedir.

Tür/Bileşen Seçimi;

   İlk adımda analizi yapılacak tür havuzunu seçmenin ardından sırada bu havuz içerisindeki bağlama yapılacak türleri seçmeye. Tür/Bileşen seçimi, daha önceki makalelerimde gözdüğümüz standart bağlama cümleciğinde To<T> kısmına denk gelmektedir. Assembly seçiminde gördüğümüz esnekliğe benzer bir esneklik tür/bileşen seçiminde de bizlere sunulmakta.

   InNamespaces fonksiyonu bize belirli bir namespace altındaki türler için işlem yapma olanağı sunacaktır. Alternatif olarak; InNamespaceOf fonksiyonu ile bir tür vererek bu tür ile aynı namespace altındaki tüm türlerin işlenmesini de sağlayabiliriz. Bu iki fonksiyonun tam zıttı olarak seçim havuzunu belirli bir namespace dışındakiler olarak belirtmemiz de mümkün. Bunun için sırasıyla NotInNamespaces ve NotInNamespaceOf fonksiyonları kullanılabilir.

   Örneğini de paylaştığım InheritedFromAny fonksiyonu ile belirttiğimiz türden kalıtılanları seçebiliriz.

   Seçimimizi belirli bir öz niteliğe sahip olan ya da olmayanlar olarak da belirtebilmemiz mümkün. Bunun için WithAttribute ve WithoutAttribute fonksiyonları kullanılabilir.

   Uç bir örnekte StartingWith ve EndingWith fonksiyonlarıdır. Bu fonksiyonlar yardımıyla belirli bir ön ek ile başlayan ya da biten türlerin seçilebilmesi mümkün.

   Bu konudaki son uç örneğimiz ise WhichAreGeneric ve WhichAreNotGeneric fonksiyonlar. Adından da tahmin edebileceğiniz gibi bu iki fonksiyon generic olan ya da olmayan türlerin seçilebilmesini sağlamaktalar.

İstisna Seçimi;

   Kimi senaryolarda, seçtiğimiz tür/bileşen kümesi içisinde yer almasını istemediğiniz, harici olarak eklemek istediğiniz tür’ler de bulunabilir. Bu tarz senaryolar Ninject.Extensions.Conventions eklentisinin geliştirilmesi sırasında göz önüne alınarak istisna tanımlamaları yapılabilmesi sağlanmış.

   Bu kapsamda Including ve Excluding fonksiyonları ile yukarıda örneğini paylaştığım IncludingNonePublicTypes fonksiyonu kullanılabilir.

Bağlanacak Servis Seçimi;

   Yukarıda sıraladığım adımlar ardından elimizde işlem yapmak istediğimiz tür kümesi oluştu. Artık sırada bu kümeyi hangi türlere bağlayacağımızda. Bağlanacak servis seçimi, daha önceki makalelerimde gözdüğümüz standart bağlama cümleciğinde Bind<T> kısmına denk gelmektedir. Servis bağlama mantığı temel, ortak bir seçime dayalı olmalıdır.

   Bu konuda da yine elimizde oldukça geniş seçenekler yer alıyor. BindAllBaseClasses fonksiyonu türü tüm temel sınıflarına bağlayacaktır. Bu sayede temel sınıflarından birisi talep edildiğinde seçili tür sunulacaktır. Benzer şekilde türü tüm implemente ettiği arayüzlere bağlamak için BindAllInterfaces fonksiyonu kullanılabilir. Bunlara alternatif olarak BindBase fonksiyonu ile tür sadece bir üst temel sanıfına bağlanabilir.  BindDefaultInterface fonksiyonu türü sadece varsayılan arayüzüne bağlayacaktır. Birden fazla arayüze bağlamak için ise BindDefaultInterfaces fonksiyonu kullanılabilir.  Arayüzlerle ilgili kullanabileceğiniz bir diğer fonksiyon da BindSingleInterface fonksiyonudur. Bu fonksiyon türün yalnız ve yalnız bir interface’i implement ettiği durumlarda tür bağlaması yapacaktır. BindSingleInterface fonksiyonu hiç bir arayüzü implement’e etmeyen ya da birden fazla arayüzü implement’e eden sınıfları yok sayacaktır. BindToSelf fonksiyonu (size tanıdık gelecektir) türü kendine bağlayacaktır. BindUsingRegex fonksiyonu arayüzü verilen şablona uygun türlerin bağlanmasında kullanılabilir.

   Servis seçimi ile ilgili yukarıda sıraladığım fonksiyonların işinizi görmediği durumlarda size BindSelection fonksiyonunu kullanmayı tavsiye ederim. Bu fonksiyon tür seçimi için aşağıdaki delegate’e kullanacaktır;

delegate IEnumerable<Type> ServiceSelector(Type type, IEnumerable<Type> baseTypes)

  Bu delagate’ten dönen listeler Ninject tarafında tür bağlamasında kullanılacaktır.

   Her ne kadar yukarıda sıraladığım fonksiyonlardan birinin pek çok senaryoda işinizi göreceğini düşünsem de, aksi durumlarda IBindingGenerator arayüzü imdadınıza yetişecektir. IBindingGenerator arayüzünden türeyen kendi özel sınıfınızı BindWith<T> ya da BindWith(IBindingGenerator generator) fonksiyonlarına geçmeniz yeterli olacaktır.

 

Bağlamaların Yapılandırması;

   Şimdiye kadar paylaştığım adımlarla temel, genel geçer tür bağlamalarını otomatize etmiş oluyoruz. Bunun yanında bir önceki makalemde paylaştığım yaşam döngüsü seçimi v.b. konularda yapılandırma talep edilebilir. Bu detayda unutulmayarak bağlamaların yapılandırılması için  Configure fonksiyonu sunulmuştur.

   Aşağıdaki örnekte Com.Enterprisecoding.DependencyInjection.Imp.Singleton namespace’i altındaki tüm türlerin singleton yaşam döngüsüne sahip olması sağlanmıştır;

public class AnaModul : NinjectModule {
    public override void Load() {
        this.Bind(yapilandirma => {
            yapilandirma.FromThisAssembly()
                .IncludingNonePublicTypes()
                .SelectAllClasses().InNamespaces("Com.Enterprisecoding.DependencyInjection.Imp.Singleton")
                .InheritedFrom<IVeriDeposu>()
                .BindBase()
                .Configure(baglama => baglama.InSingletonScope());
        });
    }
}

   Benzer şekilde diğer yaşam döngüsü seçeneklerinin de belirtilebilmesi mümkündür.

   ConfigureFor fonksiyonu yardımıyla özelde bir sınıf için farklı yapılandırma verilebilmesi de mümkündür.

   Son olarak; Ninject.Extensions.Conventions eklentisinin gücünü anlatabilmek adına, aşağıdaki örnekte modülümüzün bulunduğu sınıftaki herhangi bir ata sınıftan türememiş, bir interface’i implemente etmemiş ve inner class olarak tanımlanmamış tüm sınıfları singleton olarak kendilerine bağlamaktayız;

public class AnaModul : NinjectModule {
    public override void Load() {
        this.Bind(yapilandirma => yapilandirma
            .FromThisAssembly()
            .SelectAllClasses()
            .Where(tur => tur.BaseType == typeof (object)
                    && tur.GetInterfaces().Length == 0 
                    && tur.DeclaringType == null)
            .BindToSelf()
            .Configure(b => b.InSingletonScope())
        );
    }
}

Fatih Boy

Ankara'da yaşayan Fatih, bir kamu kurumunda danışman olarak çalışmaktadır. ALM süreçleri, kurumsal veri yolu sistemleri, kurumsal altyapı ve yazılım geliştirme konularında destek vermektedir. Boş zamanlarında açık kaynak kodlu projeler geliştirmeyi ve bilgisini yazdığı makalelerle paylaşmayı seven Fatih, aynı zamanda Visual C# ve Visual Studio teknolojileri konusundan Microsoft tarafından altı yıl üst üste MVP (En Değerli Profesyonel) ödülüne layık görülmüştür. İş hayatı boyunca masaüstü uygulamaları, web teknolojileri, akıllı istemciler gibi konularda Asp.Net, Php, C#, Java programlama dilleri ve MySql, MsSql ve Oracle gibi veritabanı yönetim yazılımları ile çalışmıştır. İngilizce ve Türkçe olarak yayınlanan makalelerini gerek İngilizce bloğunda, gerekse de Türkçe bloğunda bulabileceğiniz gibi web sitesinden de açık kaynak kodlu geliştirdiği yazılımlarına ulaşabilirsiniz. vCard - Twitter - Facebook - Google+

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir