SelamünAleyküm, bu yazımızda Dependency Injection konusunu ele alacağız. Bu yazıda Dependency Injection nedir ve Singleton, Scoped ve Transient kavramlarını incelemiş olacağız.
Dependency Injection, Uygulamalarımızı nesnelere bağımlılıktan kurtarma prensiplerinren biridir. Uygulamamız neslnelere bağımlı olarak çalışırsa daha sonra uygulamamızda yeni bir geliştirme yapmak istediğimizde epeyce bir zorlanırız. Uygulamaya nesneye bağımlıldır ve bu nesne dışında başka bir nesne kullanmak istediğimizde uygulamamızın bir çok yerinde dğişiklik yapmamız gerekir.
Mesela, uygulamamızda oluşan hataları bir metin belgesine yazdırdığımızı düşünelim. Bu şekilde kullandığımız uygulamayı bir kaç sene geçtikten değiştirip hataları veritabanına kaydetmek istedik. Üzeriden yıllar geçmiş ve çalışan bir uygulamada değişiklik yapmak her zaman zordur. Uygulamamız metin belgesine hatalaları yazan sınıfa bağımlı ise bu iş daha da zor olacaktır. Ama Dependency Injection prepsibi ile uygulamamız yazılmışsa bu işi daha kolay hale getirebiliriz. Asp .Net Core ile uygulama yazılmış ise uygulamamıza veritabanı kaydetme kodları yazıldıktan sonra, Startup.cs sınıfında ufak bir değişiklik ile uygulamamızda artık hatalar veritabanına yazılabilecektir.
Asp .Net Core'dan evvel Dependecy Injection için uygulamamızın içine dâhil edilmemiş harici başka kütüphaneleri kullanmamız gerekiyordu. Ama Asp .Net Core ile birlikte Dependency Injection için gereken kütüphaneler, varsayılan olarak uygulamamıza dâhil edilmiş şekilde karşımıza geldi.
Core.dll içine dâhil edilmiş olan kütüphanemizi ConfigureServices metodumuza tanımlamamızı yaparak kullanmamız mümkündür.
İlk olarak, Dependency Injection kullanmadığımız bir uygulama yazacağız. Daha sonra aynı uygulamayaı Dependency Injection kullanarak yazacağız ve böylece nesnelere bağımlı bir uygulamada değişiklikle nesnelere bağımlı olmayan bir uygulamada yapılan değişikliğin arasındaki farkı görmüş olacağız.
Misalen, News isimli bir Modelimiz olsun. Bu modelimiz olsun. Bu modelimizin Id ve Title isminde iki porperty'si olsun.
public class News
{
public int Id { get; set; }
public string Title { get; set; }
}
Örneğimizde SQL Server'dan veri çektiğimizi varsayalım. Bunun için SqlNewsDataProvider isimli bir sınıfımız olsun. Bu sınıf içerisinde Get ve GetList isimli iki metodumuz olsun (SQL Server'dan veri çekmek için gerekli kodları bu yazımızda yer almayacaktır, daha ileriki yazlılarda yer alacaktır. Şimdilik yazmışız gibi varsayacağız.)
public class SqlNewsDataProvider
{
public News Get()
{
return new News();
}
public List<News> GetList()
{
return new List<News>();
}
}
Sql Server'dan veri çeken metotlarımızı Controller'den cağıralım. Bunun için CategoryController, NewsController ve HomeController isimli üç adet Controllerimiz olsun. Bunların içresinide bazı Aciton metotları olsun.
public class HomeController : Controller
{
SqlNewsDataProvider _dataProvider;
public HomeController()
{
_dataProvider = new SqlNewsDataProvider();
}
public IActionResult Index()
{
return View(_dataProvider.GetList());
}
}
public class NewsController : Controller
{
SqlNewsDataProvider _dataProvider;
public NewsController ()
{
_dataProvider = new SqlNewsDataProvider();
}
public IActionResult Index()
{
return View(_dataProvider.GetList());
}
public IActionResult Detail()
{
return View(_dataProvider.Get());
}
}
public class CategoryController : Controller
{
SqlNewsDataProvider _dataProvider;
public CategoryController ()
{
_dataProvider = new SqlNewsDataProvider();
}
public IActionResult Index()
{
return View(_dataProvider.GetList());
}
}
Action metotlarımızdan, SQL Server'dan veri çekmek için gereken metotlarımızı çağırdık.
Diyelim ki projemizde köklü bir değişikliğe gitmek istiyoruz. Ve artık SQL Server ile değil de ElasticSearch'ten verileri çekeceğiz. Bunun için ElasticNewsDataProvider oluşturalım ve Elastic Search için gereken kodları yazdığımızı varsayalım.
public class ElasticNewsDataProvider
{
public News Get()
{
return new News();
}
public List<News> GetList()
{
return new List<News>();
}
}
Şimdi ise Contorller düzeyinde sınıflarımızdan SqlNewDataProvider yerine ElasticNewDataProvider'ı çağıralım.
public class HomeController : Controller
{
ElasticNewsDataProvider _dataProvider;
public HomeController()
{
_dataProvider = new SqlNewsDataProvider();
}
public IActionResult Index()
{
return View(_dataProvider.GetList());
}
}
public class NewsController : Controller
{
ElasticNewsDataProvider _dataProvider;
public NewsController ()
{
_dataProvider = new SqlNewsDataProvider();
}
public IActionResult Index()
{
return View(_dataProvider.GetList());
}
public IActionResult Detail()
{
return View(_dataProvider.Get());
}
}
public class CategoryController : Controller
{
ElasticNewsDataProvider _dataProvider;
public CategoryController ()
{
_dataProvider = new SqlNewsDataProvider();
}
public IActionResult Index()
{
return View(_dataProvider.GetList());
}
}
Görüldüğü üzere üç Contorller sınıfımızda da ayrı ayrı değişiklik yapmamız gerekti. Bu şekilde köklü bir değişikliği daha karmaşık büyük boyutta bir projede yaptığımızı düşünelim. Birçok Controller'den, kendi oluşturduğumuz katmanlardan vs. bir sürü değişiklik yapmamız gerekecek. Bir de üzerinden zaman geçmiş eski bir proje olduğunu da düşünürsek eğer böyle bir değişiklik yerine belki de projeyi en başından yeniden yazmak daha basit olacaktır. Bunun sebebi uygulmamızda bir sınıfın diğer bir sınıfa bağımlı olmasıdır. Eğer ki ortadan bağımlılığı kaldırırsak uygulamamızda sadece Elastic Search için gereken kodları yazmak ve projemize artık biz SQL Server sınıflarını değil de Elastic Search sınıflarını kullanıyoruz, dememiz yeterli olur.
Yukarıdaki örneği Dependency Injection prensibi ile tekrardan yazmayı deneyelim.
Misalen, Projemizdeki modelimiz aynı kalsın. Uygulamamıza bir tane interface ekleyelim. Adı da INewsDataProvider olsun.(AddNewItem → Interface)
public interface INewsDataProvider
{
News Get();
List<News> GetList();
}
Interface'mize veri çekmede kullandğımız metotların ismini veri geri dönüş tipini yazdık. Metotlarımızın sanal bir kopyasını oluşturmuş gibi düşünebiliriz.
SQL Server'dan veri çektiğimiz sınıfı INewsDataProvider'dan türetelim.
public class SqlNewsDataProvider : INewsDataProvider
{
pubilc News Get()
{
return new News();
}
pubilc List<News> GetList()
{
return new List<News>();
}
}
Şimdi de projemizde oluşturduğumuz Interface'leri, Controller sınıflarımızdan çağıralım.
public class HomeController : Controller
{
private readonly INewsDataProvider _newsDataProvider;
public HomeController(INewsDataProvider newsDataProvider)
{
_dataProvider = newsDataProvider;
}
public IActionResult Index()
{
return View(_newsDataProvider.GetList());
}
}
public class NewsController : Controller
{
private readonly INewsDataProvider _newsDataProvider;
public NewsController(INewsDataProvider newsDataProvider)
{
_dataProvider = newsDataProvider;
}
public IActionResult Index()
{
return View(_newsDataProvider.GetList());
}
public IActionResult Detail()
{
return View(_newsDataProvider.Get());
}
}
public class CategoryController : Controller
{
private readonly INewsDataProvider _newsDataProvider;
public CategoryController(INewsDataProvider newsDataProvider)
{
_dataProvider = newsDataProvider;
}
public IActionResult Index()
{
return View(_newsDataProvider.GetList());
}
}
Şimdi Startup.cs sınıfının ConfigureServices metodunda interface'mizi veri çekerken kullandğımız sınıfı tanımlayalım. (AddTransient bir sonraki aşamada anlatılacak.)
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<INewsDataProvider, SqlNewsDataProvider>();
}
Şimdi önceki örnekte köklü değişim diyebileceğimiz değişikliği yapalım. ilk olarak ElasticNewsDataProvider sınıfımızı INewsDataProvider'dan türetelim.
public class ElasticNewsDataProvider : INewsDataProvider
{
pubilc News Get()
{
return new News();
}
pubilc List<News> GetList()
{
return new List<News>();
}
}
Elastic Search'ten veri çekme işlemini yazdığımızı varsaydık. Bu işlemlereden sonra tek yapmamız gereken Startup.cs sınıfının ConfigureServices metodunda SqlNewsDataProvider yerine ElasticNewsDataProvider'ı tanımlamaktır.
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<INewsDataProvider, ElasticNewsDataProvider>();
}
Görüldüğü gibi Startup.cs sınıfındaki değişiklik bizim için yeterli oldu. Tek tek Controller sınıflarımızda değişiklik yapmadık. Tanımladığımız nesneleri sınıfa bağımlılıktan kurtarırsak kullandığımız nesnelerin tanımlamasını tek tek değiştirmek zorunda kalmayız. Startup.cs sınıfındaki tanımlama, değişikliklerimiz için yeterli olur.
Projelerimizde Interface'leri kullanarak Dependency Injection kavramını kullanabiliriz. Bu şekilde projemizde yaptığımız değişikler eskisinden çok daha basit bir şekilde yapılabilmekte.
Oluşturduğumuz nesnelerin belirli bir yaşam süreleri mevcuttur. Yaşam sürleri, oluşturduğumuz nesnelerin ne zaman tekrardan oluşturacağının kararını verir.
Yaşam sürelerinin tanımını yapabileceğimiz üç adet durum vardır.
Singleton: Proje ilk başlatıldığında ilgili nesneden bir oluşturulur ve yapılan istek vs... fark etmeksizin bu oluşturulduğu nesneyi kullanır. Proje sonlandırılıp başlatılmadığı sürece, ilk instance (misal) edildiği nesneyi projemiz kullanır.
Scoped: Projemize gelen her bir istekte tekrardan nesneyi instance eder. Projeye bir istek gönderdiğimizde nesne oluşur ve istek bitene kadar bu nesneden bir tane daha oluşturulmaz. Diğer bir istekte tekraradan oluşur ve istek sonlanana kadar bu nesneyi kullanır. Yani her bir istekte nesneyi bir kere olşturur.
Transient: Nesneyi her çalıştırdğımızda yeni bir instance oluşturur. Her çağrıda nesneyi tekrardan oluşturur.
Misalen IPersonSingletor, IPersonScoped ve IPersonTransient isminde üç adet interface'imiz olsun. GUID tipinde ID property'leri olsun. (GUID benzersiz bir etiket numarasıdır.)
public interface IPersonSingleton
{
Guid ID { get; set; }
}
public interface IPersonScoped
{
Guid ID { get; set; }
}
public interface IPersonTransient
{
Guid ID { get; set; }
}
Bu interface'lerden türeyen sınıflarımızı da oluşturalım.
public class PersonSingleton : IPersonSingleton
{
public PerosnSingleton()
{
ID = Guid.NewGuid();
}
public Guid ID { get; set; }
}
public class PersonScoped : IPersonScoped
{
public PersonScoped ()
{
ID = Guid.NewGuid();
}
public Guid ID { get; set; }
}
public class PersonTransient : IPersonTransient
{
public PersonTransient ()
{
ID = Guid.NewGuid();
}
public Guid ID { get; set; }
}
Startup.cs sınıfının içindeki ConfigureServices metodunda bu interface'lerin tanımlamasını yapalım.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllerWithViews();
services.AddSingleton<IPersonSingleton, PersonSingleton>();
services.AddSingleton<IPersonScoped, PersonScoped>();
services.AddSingleton<IPersonTransient, PersonTransient>();
}
Şimdi HomeController sınıfımızda bu sınıflarımızı kullanalım ve ViewData'ya atama işlemlerini yapalım.
public class HomeController : Controller
{
private IPersonScoped _personScopedOne;
private IPersonScoped _personScopedTwo;
private IPersonTransient _personTransientOne;
private IPersonTransient _personTransientTwo;
private IPersonSingleton _personSingletonOne;
private IPersonSingleton _personSingletonTwo;
public HomeController(IPersonScoped personScopedOne,
IPersonScoped personScopedTwo,
IPersonScoped _personTransientOne, IPersonScoped _personTransientTwo,
IPersonScoped _personSingletonOne, IPersonScoped _personSingletonTwo)
{
_personScopedOne = personScopedOne;
_personScopedTwo= personScopedTwo;
_personTransientOne= _personTransientOne;
_personTransientTwo= _personTransientTwo;
_personSingletonOne= _personSingletonOne;
_personSingletonTwo= _personSingletonTwo;
}
public IActionResult Index()
{
ViewDAta["PersonSingletonOne"] = _personSingletonOne.ID;
ViewDAta["PersonSingletonTwo"] = _personSingletonTwo.ID;
ViewDAta["PersonScopedOne"] = _personScopedOne.ID;
ViewDAta["PersonScopedTwo"] = _personScopedTwo.ID;
ViewDAta["PersonTransientOne"] = _personTransientOne.ID;
ViewDAta["PersonTransientOne"] = _personTransientTwo.ID;
return View();
}
}
ViewData'ya atadığımız değerleri View'e aktaralım.
PersonSingletonOne: @ViewData["PerosnSingletonOne"].ToString()<br />
PersonSingletonTwo: @ViewData["PerosnSingletonTwo"].ToString()<br />
<hr />
PersonScopedOne: @ViewData["PerosnScopedOne"].ToString()<br />
PersonScopedTwo: @ViewData["PerosnScopedTwo"].ToString()<br />
<hr />
PersonTransientOne: @ViewData["PersonTransientOne"].ToString()<br />
PersonTransientTwo: @ViewData["PersonTransientTwo"].ToString()<br />
Şimdi projemizi çalıştıralım.
Sinleton ile oluşturduğumuz nesneyi iki kere çağırdık ve yenileme işlemi uyguladık. Yine de aynı GUID'i bize oluşturdu. Singleton ile proje ilk ayağa kalkarken bir nesne oluşturdu ve bu nesneyi her çağırdığımızda aynı sonucu döndürmüş oldu.
Scoped ile nesneyi kullandığımzıda, çağırdığımız her bir nesneden bir kere oluşturdu ve aynı GUID'i bize döndürdü. Yine bir istek yaptığımızda GUID yenilenmiş oldu.
Transient ile nesneyi çağırmaya kalktığımzda her bir çağırma işleminde tekrardan GUID oluşur.
Bu yazımızın sonuna gelmiş bulunuyoruz, bu şekilde Dependency Injection konusunu ele almış olduk. Bu yazıya ait projenin github linikne buradan ulaşabilirsiniz. İlgilenenlerin faydalanması ümidiyle.
Blog Listesi