Ninject – Bağlama göre Şekillenen Tür Bağlamaları

   Ninject konusunda hızla ilerlerken bir önceki makalemizde ileri düzey konulara da giriş yapmıştık. Geldiğimiz noktada artık tür bağlamalarını da otomatize edebildik. Bu makalemde ise Ninject’in bir başka yönüne, güçlü olduğu bir yöne göz atacağız; Türlerin içinde bulundukları bağlama göre ilişkilendirilmesi. Dikkat edecek olursanız şimdiye kadar geldiğimiz noktada türlerin bağlanması tamamen statikti. Örneğin; IGunlukDeposu uygulamanın her yerinde, kim talep ederse etsin hep TextGunlukDeposu türü verilmekte. Aynı 1 ya da 0 gibi… Oysa gerçek hayatta durum bundan biraz farklıdır, günlüklerin pek çok yerde text dosyasına yazılması beklenirken kritik işlemlerin yapıldığı/exception’ların anlamlı mesajlar dönüştürüldüğü sınıflarda SMS ya da mail tercih edilebilir. Kimi durumlarda geliştirici varsayılan tür bağlamasından farklı olarak, örneğin “kritik” loglama yöntemlerini talep edebilir. Bu noktada geliştiricinin sadece “kritik” bir bağlama girildiğinin belirtilmesi yeterli olmalıdır. Bir başka senaryoda ise bu karar geliştiriciden ziyade tür bağlamasının yapıldığı modülde verilmesi istenebilir.

   Yukarıda sıraladığım senaryolar aslında bir tür için birden fazla bağlama yapılması anlamına geliyor. Dolayısıyla Ninject’e bir şekilde hangi bağlamanın ne zaman kullanılacağına dair bilgi vermemiz gerekiyor. Şimdi gelin, farklı senaryolar için farklı yöntemlerin takip edileceği bu “ipucu” verme işlemlerini basitten karışığa inceleyelim.

İsimlendirilmiş Bağlamalar

   Adından da tahmin edebileceğiniz gibi bu yaklaşımda standart tür bağlaması yapılırken, yapılan işleme daha sonradan işaret edebilmek adına bir takma ad/alias/isim verilmekte. İsimlendirme işlemi için standart bağlama yöntemi ardından Named fonksiyonunun istenilen isim parametresi ile birlikte aşağıdaki şekilde çağırılması yeterli olacaktır;

public class AnaModul : NinjectModule {
    public override void Load() {
        Bind<IVeriDeposu>().To<XmlDosyaVeriDesposu>();

        Bind<IGunlukDeposu>().To<TextGunlukDeposu>().Named("Standart");
        Bind<IGunlukDeposu>().To<MailGunlukDeposu>().Named("Kritik");
    }
}

   Örneğimizde IGunlukDeposu için Standart ve Kritik isimleri ile iki farklı bağlama yapılmakta. Devamında geliştirici olarak kod içerisinde hangi isme dair bağlama yapıldığını belirtmek istersek NamedAttribute’ünü kullanmamız yeterli olacaktır;

internal class OgrenciIslemleri {
    public readonly IVeriDeposu veriDeposu;
    private readonly IGunlukDeposu gunlukDeposu;

    public OgrenciIslemleri(IVeriDeposu veriDeposu, [Named("Kritik")] IGunlukDeposu gunlukDeposu) {
        this.veriDeposu = veriDeposu;
        this.gunlukDeposu = gunlukDeposu;
    }

    //…..
}

   Örneğimizde geliştirici olarak OgrenciIslemleri sınıfında kritik işlemler yaptığımızı kabul ederek burada “kritik” günlük deposunun verilmesini talep etmekteyiz. Aynı örnek için standart günlük deposu ise şu şekilde talep edilecektir;

internal class OgrenciIslemleri {
    public readonly IVeriDeposu veriDeposu;
    private readonly IGunlukDeposu gunlukDeposu;

    public OgrenciIslemleri(IVeriDeposu veriDeposu, [Named("Standart")] IGunlukDeposu gunlukDeposu) {
        this.veriDeposu = veriDeposu;
        this.gunlukDeposu = gunlukDeposu;
    }

    //…
}

   Alternatif olarak aşağıdaki şekilde kritik günlük deposunu talep etmemiz de mümkün;

var kritikGunlukDeposu = kernel.Get<IGunlukDeposu>("Kritik");

Püf Noktası: Bu noktada durup önemli bir yerin altını çizmem gerekiyor. Tür bağlamalarımızı isimlendirmemiz ardından bu bağlamalar için varsayılan tür atanmamaktadır. Konuyu netleştirme için aşağıdaki kodu inceleyelim;

public class AnaModul : NinjectModule {
    public override void Load() {
        Bind<IVeriDeposu>().To<XmlDosyaVeriDesposu>();

        Bind<IGunlukDeposu>().To<TextGunlukDeposu>();
        Bind<IGunlukDeposu>().To<MailGunlukDeposu>().Named("Kritik");
    }
}
internal class OgrenciIslemleri {
    public readonly IVeriDeposu veriDeposu;
    private readonly IGunlukDeposu gunlukDeposu;

    public OgrenciIslemleri(IVeriDeposu veriDeposu, IGunlukDeposu gunlukDeposu) {
        this.veriDeposu = veriDeposu;
        this.gunlukDeposu = gunlukDeposu;
    }

    //…
}

   Buradaki varsayımımız, aksi belirtilmez ise Ninject’in IGunlukDeposu taleplerinde TextGunlukDeposu türünü döneceği ve OgrenciIslemleri sınıfı için elimizde TextGunlukDeposu günlük deposunun olacağıdır. Öte yandan, uygulamayı çalıştırdığımızda aşağıdaki hatayı alacağız;

Alınan ActivationException hatasına dair dialog

An unhandled exception of type 'Ninject.ActivationException' occurred in Ninject.dll
     Additional information: Error activating IGunlukDeposu
     More than one matching bindings are available.
	 
     Matching bindings:
     1) binding from IGunlukDeposu to TextGunlukDeposu
     2) binding from IGunlukDeposu to MailGunlukDeposu
	 
     Activation path:
     2) Injection of dependency IGunlukDeposu into parameter gunlukDeposu of constructor of type OgrenciIslemleri
     1) Request for OgrenciIslemleri
	 
     Suggestions:
     1) Ensure that you have defined a binding for IGunlukDeposu only once.

   Hata mesajında, talep edilen IGunlukDeposu için TextGunlukDeposu ve MailGunlukDeposu tür bağlamalarının tanımlı olduğu belirtilerek hangisinin kullanılacağına karar verilemediği söylenmekte. Bu durumunda IGunlukDeposu için olan tür bağlamalarının hepsini ilk örneğimizdeki şekilde isimlendirmeliyiz; fakat bu durumda da tüm kodu gözden geçirerek kullanıldığı her yerde NamedAttribute’ünü kullanmamız gerekecektir. Küçük uygulamalarda bu durum bir sıkıntı olmasada büyük ölçekli uygulamalarda baş ağrısı yaratacağı kesin…

   Şanslıyız ki yine Ninject içerisinde bu durum için bir çözüm bulunuyor. Aslında yapılması gereken varsayılan olmasını istediğimiz bağlama için bir koşul oluşturma. İlgili tür bağlamasında isimlendirmeye dair bir koşul olmaması durumunda bu tür bağlaması kullanılmalıdır demeliyiz. Bunun için aşağıdaki şekilde When fonksiyonunu kullanabiliriz;

Bind<IGunlukDeposu>().To<TextGunlukDeposu>().When(talepDetayi => talepDetayi.ParentContext != null && !talepDetayi.ParentContext.Binding.IsConditional);

   Örneğimizde Ninject’e IGunlukDeposu türü örneği talep edildiğinde içinde bulunduğu bağlam için bir koşul yoksa TextGunlukDeposu türünü ver diyoruz. Bu durumda kodumuzun son hali şu şekilde olacaktır;

public class AnaModul : NinjectModule {
    public override void Load() {
        Bind<IVeriDeposu>().To<XmlDosyaVeriDesposu>();

        Bind<IGunlukDeposu>().To<TextGunlukDeposu>().When(talepDetayi => talepDetayi.ParentContext != null && !talepDetayi.ParentContext.Binding.IsConditional);
        Bind<IGunlukDeposu>().To<MailGunlukDeposu>().Named("Kritik");
    }
}
internal class OgrenciIslemleri {
    public readonly IVeriDeposu veriDeposu;
    private readonly IGunlukDeposu gunlukDeposu;

    public OgrenciIslemleri(IVeriDeposu veriDeposu, IGunlukDeposu gunlukDeposu) {
        this.veriDeposu = veriDeposu;
        this.gunlukDeposu = gunlukDeposu;
    }

    //…
}

Pro Tip: Modülleriniz içerisinde isimlendirilmiş bağlamaları çok kullanıyor ve yukarıdaki gibi varsayılan bağlama tanımı yapıyorsanız size tavsiye öncelikle varsayılan koşulu için aşağıdaki şekilde bir delegate oluşturmanız;

Func<IRequest, bool> kosulYoksa = talepDetayi => talepDetayi.ParentContext != null && !talepDetayi.ParentContext.Binding.IsConditional;

   Devamında When fonksiyonlarına kosulYoksa delegate’ini geçebilirsiniz;

public class AnaModul : NinjectModule {
    private readonly Func<IRequest, bool> kosulYoksa = talepDetayi => talepDetayi.ParentContext != null && !talepDetayi.ParentContext.Binding.IsConditional;

    public override void Load() {
        Bind<IVeriDeposu>().To<XmlDosyaVeriDesposu>();

        Bind<IGunlukDeposu>().To<TextGunlukDeposu>().When(kosulYoksa);
        Bind<IGunlukDeposu>().To<MailGunlukDeposu>().Named("Kritik");
    }
}

   Bu şekilde kodunuz daha okunaklı, anlaşılır olacaktır.

Kısıtlama ile Tür çözümü;

   Bağlama göre tür bağlaması yapmak için Ninject’in bize sunduğu tek yöntem isimlendirilmiş bağlamalar değildir. Kullanabileceğiniz bir diğer yöntem ise özniteliklerle tür bağlamalarını kısıtlamaktır. Bu yöntem için yapmanız gereken ConstraintAttribute’ten türeteceğiniz bir öznitelik içerisin kısıtınızı tanımlayarak ilgili bağlama noktasında kullanmak olmalıdır. Soyut bir sınıf olarak ConstraintAttribute sınıfı Ninject tarafından özel olarak tanınan bir özniteliktir ve bağlama hakkında önceden tanımlanmış olan meta veriler üzerinden işlem yapabilmenizi sağlamaktadır.

   Konunun netleşmesi adına yukarıdaki örneğimizi bu defa kısıtlama ile tür çözümü ile gerçekleştirelim. Bunun için ConstraintAttribute’ten türeyecek şekilde KritikAttribute adıyla yeni bir öznitelik oluşturalım;

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class KritikAttribute : ConstraintAttribute {
    public override bool Matches(IBindingMetadata metadata) {
        return metadata.Has("Kritik") && metadata.Get<bool>("Kritik");
    }
}

   İkinci adımda MailGunlukDeposu için yaptığımız tür bağlamasını meta veri geçecek şekilde güncelleyelim;

Bind<IGunlukDeposu>().To<MailGunlukDeposu>().WithMetadata("Kritik", true);

   Son olarak da, isimlendirilmiş bağlamaya benzer şekilde Kritik tür bağlamasının yapılmasını istediğimiz noktada Kritik özniteliğini kullanalım;

internal class OgrenciIslemleri {
    public readonly IVeriDeposu veriDeposu;
    private readonly IGunlukDeposu gunlukDeposu;

    public OgrenciIslemleri(IVeriDeposu veriDeposu, [Kritik]IGunlukDeposu gunlukDeposu) {
        this.veriDeposu = veriDeposu;
        this.gunlukDeposu = gunlukDeposu;
    }

    //…
}

   Bu bilgiler ardından, isimlendirilmiş bağlaya bir defa daha göz atacak olursak aslında öznitelik üzerinden kısıtlama ile tür çözümünden pek de farklı olmadığını görebiliriz. Hatta, NamedAttribute sınıfı incelendiğinde aynı yöntemi kullandığını fark edeceksinizdir;

/// <summary>
/// Indicates that the decorated member should only be injected using binding(s) registered
/// with the specified name.
/// </summary>
public class NamedAttribute : ConstraintAttribute {
    /// <summary>
    /// Gets the binding name.
    /// </summary>
    public string Name { get; private set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="NamedAttribute"/> class.
    /// </summary>
    /// <param name="name">The name of the binding(s) to use.</param>
    public NamedAttribute(string name) {
        Ensure.ArgumentNotNullOrEmpty(name, "name");
        Name = name;
    }

    /// <summary>
    /// Determines whether the specified binding metadata matches the constraint.
    /// </summary>
    /// <param name="metadata">The metadata in question.</param>
    /// <returns><c>True</c> if the metadata matches; otherwise <c>false</c>.</returns>
    public override bool Matches(IBindingMetadata metadata) {
        Ensure.ArgumentNotNull(metadata, "metadata");
        return metadata.Name == Name;
    }
}

Bağlama Zamanı Kısıtlama;

   Şimdiye kadar incelediğimiz iki yöntemin de en önemli ortak yanı geliştiricinin bağlama karar vermesiydi. Kodun belirli yerlerine öznitelikler vasıtasıyla konulan işaretler Ninject tarafından okunarak bağlam belirleniyordu. Bu yöntem, statik tür bağlamasına göre esnek olmakla birlikte yine de uygulamanın ana koduna bağımlılığımız bulunuyor. Herhangi bir modülde tam anlamıyla bağlamı değiştirebilmek yerine ana uygulama geliştiricinin belirlediği sınırlar içerisinde kalıyoruz. Öte yandan Ninject’in bize sunduğu bir diğer yöntem var ki; bu yöntemle tam olarak esnekliğe sahip olabiliyoruz: Bağlama zamanı kısıtlamalar.

   Bu yöntemde tür bağlamaları modüller içerisinde belirtilebiliyor;

Bind<IGunlukDeposu>().To<MailGunlukDeposu>().WhenInjectedExactlyInto<OgrenciIslemleri>();

Ya da

Bind<IGunlukDeposu>().To<MailGunlukDeposu>().WhenInjectedExactlyInto(typeof(OgrenciIslemleri));

   Örneğimizde OgrenciIslemleri sınıfına IGunlukDeposu arayüzü enjekte edilirken MailGunlukDeposu sınıfının kullanılması gerektiği harici olarak belirtiliyor. Burada yapılan tür bağlaması uygulamanın ana kodundan tamamen dışarıda, modül içerisinde olduğu için farklı bir modül tanımında uygulamanın tamamen farklı davranış sergilemesi rahatlıkla sağlanabilecektir. Bu da doğal olarak uygulamaya üst düzeyde bir esneklik sağlayacaktır.

   Bağlama zamanı kısıt ekleme yönteminin bize sağladığı esneklikle bununla da sınırlı değildir. Önceki yöntemlerde bahsettiğim öznitelik tabanlı kısıt eklemeyi bu yöntemde de yapabilmeniz, hem de çok daha basit şekliyle yapabilmeniz mümkün;

public class KritikAttribute : Attribute { }
public class OgrenciIslemleri {
    public readonly IVeriDeposu veriDeposu;
    private readonly IGunlukDeposu gunlukDeposu;

    public OgrenciIslemleri(IVeriDeposu veriDeposu, [Kritik] GunlukDeposu gunlukDeposu) {
        this.veriDeposu = veriDeposu;
        this.gunlukDeposu = gunlukDeposu;
    }

    //…
}
public class AnaModul : NinjectModule {
    public override void Load() {
        Bind<IVeriDeposu>().To<XmlDosyaVeriDesposu>();

        Bind<IGunlukDeposu>().To<TextGunlukDeposu>();
        Bind<IGunlukDeposu>().To<MailGunlukDeposu>().WhenTargetHas<KritikAttribute>();
    }
}

   Bu haliyle örneğimizde boş bir Kritik özniteliği oluşturup OgrenciIslemleri sınıfı constructer parametremizi kritik olarak işaretliyoruz. Devamında modülümüzde sınıf kritik işaretlenmişse (kritik özniteliğine sahipse) IGunlukDeposu türünü MailGunlukDeposu türüne bağla diyoruz. Bu sayede işaretleme ana uygulama kodunda ve derleme zamanında yapılmış olsa da bağlanacak türün kararı uygulama dışında, modülümüzde alınıyor. Aşağıda alternatif bir yaklaşımla parametre bazında değil, WhenClassHas fonksiyonu yardımıyla sınıf bazında bir işaretleme söz konusu;

[Kritik]
public class OgrenciIslemleri {
    public readonly IVeriDeposu veriDeposu;
    private readonly IGunlukDeposu gunlukDeposu;

    public OgrenciIslemleri(IVeriDeposu veriDeposu, IGunlukDeposu gunlukDeposu) {
        this.veriDeposu = veriDeposu;
        this.gunlukDeposu = gunlukDeposu;
    }

    //…
}
public class AnaModul : NinjectModule {
    public override void Load() {
        Bind<IVeriDeposu>().To<XmlDosyaVeriDesposu>();

        Bind<IGunlukDeposu>().To<TextGunlukDeposu>();
        Bind<IGunlukDeposu>().To<MailGunlukDeposu>().WhenClassHas<KritikAttribute>();
    }
}

   Bu fonksiyonlar yanında, daha önceki makalelerde de yer yer kullandığım When fonksiyonu da bağlamdan yola çıkarak tür bağlaması yapabilmemize imkan vermektedir;

Bind<IGunlukDeposu>().To<MailGunlukDeposu>().When(talep => talep.Target.Member.DeclaringType.Name.EndsWith("Islemleri"));

   Örneğimizde adı “Islemleri” ile biten tüm sınıflar için IGunlukDeposu arayüzünün MailGunlukDeposu türüne bağlanması isteniyor.

Fabrika fonksiyonları ile Tür Bağlama;

   Bağlama göre tür bağlamaya ilişkin şimdiye kadar paylaştığım yöntemleri incelediğimizde hepsinde öncelikle tür bağlamayı tanımladığımızı ardından da bu bağlama için bir koşul ortaya koyduğumuzu görebilirsiniz. Bu yöntem her ne kadar pek çok senaryoda işimizi görüyor olsa da kimi yazılımlarda daha da esnek hareket edilmek istenebilir. Ninject tasarımında bu gibi senaryolarda göz önüne alınarak her tür bağlama talebinde geliştiricinin yazdığı karar verici bir fonksiyonun çalıştırılmasına imkan verilmiştir. Bu işlem için aşağıdaki şekilde bir delegate kabul eden ToMethod fonksiyonu kullanılabilir;

Func<IContext, T1> fonksiyon

   Burada T1 ile bağlanacak tür örneği temsil edilmektedir. ToMethod’un kullanımı bağlanacak türün dinamik olarak değişebilmesine izin vermek amacıyla öncekilere göre biraz daha farklıdır. Bind ifadesinden sonra To fonksiyon çağrısı yer almamaktadır.

   ToMethod’un kullanımına örnek vermek gerekirse. Son örneğimizden yola çıkalım;

public class AnaModul : NinjectModule {
    public override void Load() {
        Bind<IVeriDeposu>().To<XmlDosyaVeriDesposu>();

        Bind<IGunlukDeposu>().To<TextGunlukDeposu>();
        Bind<IGunlukDeposu>().To<MailGunlukDeposu>().When(request => request.Target.Member.DeclaringType.Name.EndsWith("Islemleri"));
    }
}

   Aynı tür bağlamasını ToMethod ile şu şekilde de yapabilmemiz mümkün;

public class AnaModul : NinjectModule {
    public override void Load() {
        Bind<IVeriDeposu>().To<XmlDosyaVeriDesposu>();

        Bind<IGunlukDeposu>().ToMethod(baglam => baglam.Request.Target.Member.DeclaringType.Name.EndsWith("Islemleri") ?
            (IGunlukDeposu)new MailGunlukDeposu() : new TextGunlukDeposu());
    }
}

   Bu kodun ise daha Ninject hali ise şu olacaktır;

public class AnaModul : NinjectModule {
    public override void Load() {
        Bind<IVeriDeposu>().To<XmlDosyaVeriDesposu>();

        Bind<IGunlukDeposu>().ToMethod(baglam => baglam.Request.Target.Member.DeclaringType.Name.EndsWith("Islemleri") ?
            (IGunlukDeposu)baglam.Kernel.Get<MailGunlukDeposu>() : baglam.Kernel.Get<TextGunlukDeposu>());
    }
}

   Son örnekte türlerin bir örneğini doğrudan new ile oluşturmak yerine işlem yaptığımız bağlamın çekirdeğinden talep ettiğimizin altını çiziyorum. Bu sayede ilgili sınıfların örneklerinin oluşturulması sırasında da Ninject’in esnekliğini kullanabiliriz.

Pro Tip: Burada tür kararının her defasında fonksiyondan sorulması nedeniyle eşleştirmelerin ön belleğe atılması mümkün olamayacaktır. Dolayısıyla aynı sonuç alınmakla birlikte yukarıdaki örneklerden işinize en uygunu seçmeniz beraberinde önemli performans kazanımları da getirecektir.

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