.Net Castle Windsor

23.4.2019

Castle Windsor nedir, ne gibi durumlarda ihtiyaç duyarız sorularının cevabına geçmeden ufak "Dependency Injection" nedir, neden kullanılırı açıkmalıyız. Dependency Injection kullanarak developerların proje hangi ORM, Mapper, Serializer, Logger kullanıldığı bilmeden, arka tarafta bu tool'ların değişkenlik gösterdiğinde development süreçlerinin hiç bir şekilde değişmemesini sağlıyoruz.

Bir proje mimarısında ORM olarak "Ado.NET" kullanılırken, "EntityFramework" kullanılmak istenilebilir. AutoMapper kullanılırken, Mapster kullanılmak istenilebilir. Bu durumlar değişkenlik gösterebileceği için developer interface'ler üzerinden geliştirme yapmalı. Concrete yani somut nesneler hiç bir şekilde development süreçlerinde kullanılmamalıdır.

Tüm işlemlerimizi interface üzerinden yaparak projemizi hiç bir şeye bağımlı kılmadan geliştirme yapabiliriz. Ancak bizim bu interface'lere bir şekilde bağlanmamız, somut nesnesini bir şekilde belirtmemiz gerekiyor. Bu duruma "Dependency Injection" diyoruz. Inject ettiğimizde bu somut sınıfların yaşam süreçlerini de biz belirleriz.

Bunlar "Singleton,Scoped,Transient" olarak üç farklı yaşam süreçidir.

Singleton :

- Uygulama başlamasından bitişine kadar tek bir instance üzerinden işlem yapmak istediğimizde kullanırız.

Scoped :

- Bir request oluştuğunda yeni bir instance oluşur, bu request sonlana kadar aynı instance referans alınmaya devam eder.

Transient :

- Nesneye yapılan her cağrıda yeni bir instance oluşturur. .Net core üzerinde dependency injection kullanım şansımız var tabi ki. Ancak ben bir container yapıda kullanımdan yanayım, böylece rahat bir şekilde intercept yapabiliyor olacağız.

Startup.cs içerisinde service collection'ı confige ederken bu tanımlamalarımızı yapabiliyoruz.

services.AddTransient<ICustomerRepository, CustomerRepository>();
services.AddScoped<ICustomerRepository, CustomerRepository>();
services.AddSingleton<ICustomerRepository, CustomerRepository>();

Bu şekilde Ado.net kullanan "CustomerRepository" kayıtı burada belirterek "ICustomerRepository" cağırıldığında sen bunu kullanacaksın diyebiliyorum. MVC projemizde "CustomerController" açtık diyelim, bu interface inject ederek kullanmak istiyorum.

	ICustomerRepository _customerRepository;
        public CustomerController(ICustomerRepository customerRepository)
        {
            _customerRepository = customerRepository;
        }

Görüldüğü gibi concrete nesneye her hangi bir bağımlılık olmadan işlemleri yapabiliyor duruma geliyorum.

Dependency Injection yönetimi için "Castle, Ninject, AutoFac, Simple Injector v.b" IoC yapıları kullanabiliriz. Ben bu yazımda "Castle Windsor" anlatacağım. Castle Windsor bize ne sağlayacak dersek eğer öncelikle en basitinden bize constructerdan inject etme zorunda bırakmayacak.

Biz bir container içerisine interface'lerimizi belirlediğimiz yaşam süreçleri ile atacağız. İşimize lazım olduğunda çekeceğiz, gerektiğinde işlemlerin arasına girip loglama yapabileceğiz. Bu loglama işlemi için "Interceptor" yazmamız gerekecek, bunu da bir sonraki yazımda anlatacağım. Öncelikle nuget paketimiz olarak "Castle.Windsor" yüklüyoruz.

Bize "WindsorContainer" adında bir class oluşturmamızı sağlıyor. Bizde bu class içerisinde interface'lerimizi register edeceğiz. Daha sonra resolve ederek cağırağız. Register etme işlemini satır satır el ile yazamayız. Örneğin yüzlerce repositoryimiz olabilir, bunlar bir şekilde reflection yaparak register etmemiz gerekiyor.

Bunun için "IWindsorInstaller" kullanacağız. DependencyInstaller adında bir class oluşturalım ve buna "IWindsorInstaller" implemente edelim. Bizden "Install" adında bir method isteyecektir.

public class RepositoryDependecyInstaller : IWindsorInstaller
 {
        public const string _mask = "SC.*";
	public const string _assemblyDirectoryName = "";
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            var assemblyFilter = new AssemblyFilter(_assemblyDirectoryName, mask: _mask);
            container.Register(Classes.FromAssemblyInDirectory(assemblyFilter).BasedOn<IRepository>()
                    .Configure(p => p.LifestyleScoped()
                    ));	
        }  
}

Burada "Castle.MicroKernel.Registration" kullanıyoruz. Burada dikkat etmemiz gereken yerler var, fark ettiyseniz bir _mask belirledim, projemizde hangi DLL'leri kapmasını istiyorsak bunun için _mask belirtelim. Benim projemin adı "SC" SC.Repository, SC.Web ama içerisinde Core.Repository gibi DLL'lerde var. Bunları göz ardı etmek istediğim için _mask belirtiyorum.

BasedOn<IRepository> dediğim bölüm tüm ise IRepository'den türüyenleri yakalamak için burada tüm repositoryleri scoped yaşam süreçi belirleyerek container'ın içerisine atıyorum. Burada dilersek buna interceptors ekleyerek işlemlerin arasına gidebiliriz. IWindsorInstaller'dan türüyen birden fazla installer olabilir. Bunları ortak bir yerde toplayıp container içerisinde dolduralım. Bunun için adını "IoCManager" koyacağım static bir classa ihtiyaçım var. Bunun static ve singleton olmasını istiyorum.

  public static class IocManager
    {
        private static WindsorContainer container;

        public static void Install()
        {
   
            container = new WindsorContainer();
            container.Register(Classes.FromAssemblyInDirectory(new AssemblyFilter("", mask: "SC.*"))
                    .BasedOn<IWindsorInstaller>()
                    .WithServiceBase()
                    .LifestyleTransient());
            foreach (var item in container.ResolveAll<IWindsorInstaller>())
            {
                container.Install(item);
            }
        } 
        public static T Resolve<T>()
        {
            using (BeginScope())
            {
                return container.Resolve<T>();
            }
        }
        public static IDisposable BeginScope()
        {
            return container.BeginScope();
        }
        public static object Resolve(Type service)
        {
            return container.Resolve(service);
        }
        public static void Dispose()
        {
            container.Dispose();
        }
        public static T[] ResolveAll<T>()
        {
            return container.ResolveAll<T>();
        }
      
    }

İlk olarak "IWindsorInstaller" dan türüyenleri yakalıyorum. Bu birden fazla olabilir, bunları each ile dönüp, "IWindsorInstaller"ın "Install" methodunu cağırıyorum. Burada bulunan "Install" methodu ise class ilk cağırıldığında singleton object olarak kendi instance alabiliriz.

Lazy sınıfı v.b bir kullanımdan ya da startup.cs'de bunu cağırabiliriz. İçerisinde InstallCompleted diye bir değişken ekleyip doldurma bittiğinde bunu true olarak set edip, bir daha doldurmaması için kontrol edebiliriz. Projemizde IoCManager artık içerisi dolu, bunu artık kullanmak için resolve methounu kullanamız gerekiyor.

 ICustomerRepository customerRepository = IocManager.Resolve<ICustomerRepository>();

Böylece repositorylerimizi bir container mimarısı altında toplarak yönetebiliyor hale geldik. Bir sonraki yazımızda bu repositoryler için transaction, exception loglama gibi örnekler yapabiliriz.


Yazıyı visual studio vs gibi bir ide kullanmadan yazdım scopelarda eksiklik, derleme problemi olabilir.
Her hangi bir sorun, sıkıntı yaşayan bana ulaşabilir.

 

Samet ÇINAR Hakkında

2010 senesinden bu yana hem tam zamanlı hemde freelance olarak yazılım projelerinde görev almaktayım.
Her gün daha güzel geliştirmeler yapmak için araştırıp öğrenmeyi, öğrendiklerimi aktarmayı çok seviyorum.

İLGİLİ YAZILAR

YORUMLAR

gezgindev

4.5.2019

samet merhaba, bende şimdi hesaplama yapan bir class var diyelim bunun hangi tipte olması gerekiyor scoped, signletion vs

Samet ÇINAR

6.5.2019

Hesaplama yapan sınıf kendi içerisinde her hangi bir değer barındırmıyorsa, sadece dışarıdan aldığı parametrelerde işlem yapıyorsa singleton olarak kullanılabilir. Örnek kodunuz varsa Linkedin üzerinden ulaşabilirsiniz.

Yorum Yap