.Net Core Register All Dependencies at Once (Scrutor)

.Net Core projelerinde dependency injection uygularken eğer built-in container'ı kullanıyorsanız ve onlarca Service,Repository veyahut manager vb. interface/implementasyon'u varsa bunları container'a register etmek biraz uğraştırıyor. Sebebi ise castleWindsor veya Autofac gibi DI Tool'larından alışkın olduğumuz register-all-at-once gibi bir özelliği şuan için bulunmamakta. Yani aşağıdaki gibi her bir isterface ve onun implementasyonu için ayrı ayrı registration tanımlanması gerekmekte.

services.AddSingleton<IUserService, UserService>();
services.AddSingleton<ICustomerService, CustomerService>();
services.AddSingleton<IProductService, ProductService>();
services.AddSingleton<IEmployeeService, EmployeeService>();
services.AddTransient<IUserManager, UserManager>();
services.AddTransient<ICustomerManager, CustomerManager>();
services.AddTransient<IProductManager, ProductManager>();
services.AddTransient<IEmployeeManager, EmployeeManager>();
...

Ancak kısa süre önce github'da paylaşılan ve nuget üzerinden de install edilebilen bir kütüphane yayınlandı.

Scrutor

Scrutor "Assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection" şeklinde tanımlanmakta ve bu kütüphane ile aynısı olmasada CastleWindsor ve Autofac de olduğu gibi registration işlemleri biraz olsun basitleşiyor. Scrutor'u projenize install ettikten sonra registration tanımlarken belirttiğiniz interface'in bulunduğu bütün assembly'leri scan edip bağımlılıkları inject etmekte.

Yukarıda built-in container kullanarak register ettiğimiz bağımlılıkları scrutor kullanarak aşağıdaki gibi de yapabiliriz.

services.Scan(scan => scan  
  .FromAssemblyOf<IAssemblyMarker>()
  
   // ISingletonLifetime adında bir interface tanımlayıp container'a single instance olarak register olmasını istediğimiz dependency'leri bu interface'den inherit ederek doğrudan register edebilmekteyiz.
  .AddClasses(classes => classes.AssignableTo<ISingletonLifetime>())
  .AsImplementedInterfaces()
  .WithSingletonLifetime()
  
   // ITransientLifetime adında bir interface tanımlayıp container'a transientLifeTime'ına sahip olmasını istediğimiz dependency'leri bu interface'den inherit ederek doğrudan register edebilmekteyiz.
  .AddClasses(classes => classes.AssignableTo<ITransientLifetime>())
  .AsImplementedInterfaces()
  .WithTransientLifetime());
  
  public interface ISingletonLifetime
  { }
  public interface ITransientLifetime
  { }
  
  public interface ICustomerService : ISingletonLifetime
  { 
     //todo
  }
  
  public interface ICustomerManager : ITransientLifetime
  { 
    //todo
  }

Scrutor startUp anında assembly'i scan edip ITransientLifetime ve ISingletonLifetime interface'lerini ve onların implementedInterface'lerini bularak implementasyonlarını doğrudan container'a register eder.

Scrutor doğru kullanıldığı takdirde oldukça başarılı bir kütüphanedir ve bizleri dependecny injection konusunda belli başlı bazı zorluklardan kurtarabilmektedir.

Asp.Net Core Dependency Injection

Asp.Net Core basitçe nedir ne değildir bahsetmiştik daha önceki yazılarımızda. Bugünkü yazıda ise Asp.Net Core için Dependency Injection konusuna değinip framework içerisinde default gelen built-in DI Container'ını inceleyeceğiz.

.Net core için 3rd party DI container'lar kullanılabildiği gibi küçük basit uygulamalar yada microservice'ler için kullanılabilen microsoft'un geliştirmiş olduğu basit kullanıma sahip bir built-in default DI tool'u da framework ile sunulmuştur. Castle, Autofac yada Unity framework'leri kadar çeşitli özelliklere sahip olmasa da oldukça kullanması basit ve performanslı bir framework olarak karşımıza çıkmakta. Örnek bir proje üzerinden nedir, ne değildir, nasıl kullanılır anlatmaya başlayalım.

Creating a Sample Project

Aşağıdaki gibi Asp.Net Core'da Web Api olarak yazılmış bir projemiz olsun ve proje içerisinde Repository ve Service katmanları için kullandığımız interface'leri container'a register edelim ve kullanalım. İlk olarak domain objemizi aşağıdaki gibi yaratalım.

namespace AspCoreDIContainerSample.Domain
{
    public class City
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
    }
}

Sonrasında yukarıda oluşturduğumuz domain model için repsoitory inteface ve sınıfını oluşturalım. 

namespace AspCoreDIContainerSample.Repository
{
    public interface ICityRepository
    {
        IEnumerable<City> GetAll();
    }
}
namespace AspCoreDIContainerSample.Repository
{
    public class CityRepository : ICityRepository
    {
        public IEnumerable<City> GetAll()
        {
            // send back some hard-coded data
            return new List<City>
            {
                new City { Id = "3938ca15-cba2-44a5-bd53-b5d6a5e306f5", Name = "Ankara" },
                new City { Id = "cfd61cbf-a161-440f-9d1d-fdef32379b71", Name = "Samsun" }
            };
        }
    }
}

Yukarıda oluşturduğumuz ICityRepository interface'i aşağıda tanımlayacağımız CityService sınıfına constructer-injected parameter olarak vereceğiz. 

namespace AspCoreDIContainerSample.Service
{
    public interface ICityService
    {
        List<City> GetAllCities();
    }
}
namespace AspCoreDIContainerSample.Application
{
    public class CityService : ICityService
    {
        private readonly ICityRepository cityRepository;
 
        public CityService(ICityRepository cityRepository)
        {
            this.cityRepository = cityRepository;
        }
 
        public List<City> GetAllCities()
        {
            return this.cityRepository.GetAll().ToList();
        }
    }
}

Register All Used Components

Yukarıda kullandığımız repository ve service objelerini container'a implementasyonlarını belirterek register etmemiz gerekiyor. Bunu için aşağıdaki gibi proje solution'ında bulunan Startup.cs sınıfından faydalanacağız. Startup.cs proje ilk run edildiğinde çalışan kod satırlarının bulunduğu sınıftır. Bizde proje ilk run edildiğinde ilgili kullanılan bağımlılıkları register edeceğiz. Startup.cs de bulunan ConfigureServices adlı metot register işlemlerini yapmak için default gelen bir metottur. Aşağıdaki gibi tanımlamalarımızı yapalım.

    public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<ICityRepository, CityRepository>();
            services.AddScoped<ICityService, CityService>();
        }

 Yukarıda görüldüğü üzre ICityRepository ve ICityService interface'lerini implementasyonlarını belirterek AddScoped metodunu kullanarak register ettik. Peki ne bu AddScoped ?

 

Dependency Injection Lifetimes - Scoped, Singleton, Transient

Dependency injection lifetime container'dan istenen objenin ne zaman instance create edileceğini yada ne zaman yeniden create edilmesi gerektiğini sağlar. Container'da lifetime tanımı yapabilmemizi sağlayan 3 state vardır. Sırasıyla bu üçlüye bakacak olursak;

Scoped : Her scope için tek bir instance yaratılmasını sağlayan lifetime adı dır. Örneğin web projesi için projenize gelen her bir HttpRequest için ilgili instance'ı yaratıp container'da tutar ve o http lifecycle'ı boyunca hep aynı instance'ı kullanır.

Singleton : İsminden de anlaşılacağı üzre singleton yani uygulama ilk çalıştığında tek bir instance yaratır ve sonrasından uygulama stop olana kadar bu instance'ı kullanır.

Transient : Bu lifetime ise ilgili obje her istendiğinde yeni bir instance yaratır ve özellikle stateless servisler için best-practice olarak kullanılır.

Biz yukarıda kullandığımız interface'lerin lifetime'larını Scoped olarak tanımladık ancak ihtiyaca göre Singleton yada Transient olarak da tanımlayabilirdik.

  services.AddSingleton<ICityRepository, CityRepository>();
  services.AddSingleton<ICityService, CityService>();

 

Using Registered Components

Son adım olarak register ettiğimiz bu service'leri kullanmak kaldı. Aşağıdaki gibi CityController içerisinde container'da register edilen instance'ları kullanarak geriye cityList return edelim.

namespace AspCoreDIContainerSample.Api
{
    public class CityController : Controller
    {
        private readonly ICityService _cityService;
 
        public CityService(ICityService cityService)
        {
            this._cityService= cityService;
        }

        [HttpGet]
        public List<City> GetCityList()
        {
            return this.cityService.GetAllCities();
        }
    }
}

Asp.Net Core için Dependency Injection kullanım örneğimiz bu kadardı. Yukarıdaki end-point'e call yaparak geriye Samsun ve Ankara illerinin bulunduğu array'i return ettiğini göreceğiz.

Built-in dependency injection castle, ninject veya autofac kadar zenginliğe sahip bir IoC container değildir ancak oldukça basit kullanıma sahip hızlı ve implementasyonu kolay bir container dır. Eğer isterseniz bu container'ın asp.net core için bir kütüphanesi geliştirildiyse default built-in container ile replace de edebilirsiniz. Sonraki Asp.Net Core DI yazımızda 3rd party asp.net core DI container'larından birini nasıl built-in container yerine kullanabiliriz inceleyeceğiz.