ASP.NET Core Inversion Of Control(IoC) / Dependency Injection Kavramları

Tolgahan Öztürk
KoçSistem
Published in
8 min readAug 31, 2024

Inversion Of Control (IoC)

Yazılım dünyasını takip eden herkes IoC, Dependency Inversion veya Dependency Injection bu kavramları illa ki duymuştur.

İlk olarak Inversion Of Control kavramına ve bu kavramla beraber kullanılan diğer kavramların ne olduğunu detaylı bir şekilde inceleyeceğiz. Bu kavramlar birbirine benzerlik gösteren arkadaş kavramlardır.

Bu yazımızda sadece teorik olarak değil, bir uygulama inşa ederek öğrendiğimiz teorik bilgiyi uygulayarak pratiğe dönüştürmüş olacağız. O halde gelin biraz daha yakından bakalım.

Inversion Of Control kavramına tabloda da gördüğünüz gibi Principle kavramı karşılık gelmektedir. Peki nedir bu Principle?

Principle; uygulama geliştirirken veya kodlama yaparken bize yol gösteren kavramdır.
Örneğin S.O.L.I.D. prensiplerinden olan Single Responsibility Principle; Bir Class’ın değişmesi için tek bir nedeni olmalıdır. Aslında bize bir yol gösteriyor fakat bu yolu nasıl gerçekleştireceğimizi bize bırakıyor. Burada nihai amaç bir Class’ın değiştirilmek için tek bir nedeni olmalı, her bir Class’ın bir amacı olmalıdır.

Bir Class aynı anda hem SMS gönderip hem de Email göndermemelidir.

Peki Inversion Of Control Principle’ın nasıl çalıştığını birkaç örnekle inceleyelim.

Inversion Of Control’ün karşılığını biri gerçek hayattan diğeri ise Object Oriented dünyasından örneklerle ele alalım.

Gerçek hayattan örneği verecek olursak, evinizden işinize kendi aracınızla gidiyorsunuz. Kendi aracınızla gittiğiniz zaman siz bu araç üzerinde hakimiyet sahibisiniz. Aracın lastiği patlasa, yağı azalsa bu gibi bakımlarını yapmanız gerekiyor.
Burada Inversion Of Control diyor ki; Siz evden işyerine giderken araç üzerinde hakimiyet tamamıyla sizde olmasın. Evden işe giderken bu işi başka birine devret.
Örneğin geliş gidişlerinizi taksiyle yapabilirsiniz. Yani nihai amacınız evden işe gitmek için kendi aracınızla değil de bir taksiyle bu işlemi çözmeniz. Taksiyle bu işi çözdüğünüzde öncelikle aracın bakımını siz düşünmek zorunda değilsiniz. Bu işleri düşünecek olan sizin sorumluluğu verdiğiniz şofördür.

Bir de yukarıdaki resimdeki alt görseli inceleyelim. Bizim bir A Class’ımız var, bu A Class’ın içinde bir de B Class’ı kullanıyoruz. Bu A Class’ı görevini yerine getirebilmek için B Class’ına mutlaka ihtiyaç duymaktadır.
Bu A Class’ı B Class’ını kullanmak için new anahtar sözcüğüyle beraber bir nesne örneği alıyor.
Nesne örneğini aldığı zaman yaşam döngüsü tamamen A Class’ında oluyor.

Burada Inversion Of Control diyor ki;

Bir A Class’ı B Class’ı üzerinde hakimiyet kurmasın. A Class’ı B Class’ına ihtiyaç duyuyorsa bunu başkasına delege etsin, bu B Class’ının oluşturulma işlemini ve delege etmiş olduğu yapı bana B Class’ında bir nesne örneği versin. Bu sayede A Class’ı sadece kendi işine odaklanabilir ve B Class’ının yaşam döngüsüne de müdahale etmeyecektir.

Aslında nihai amacı, bir uygulama geliştirirken birbirine sıkı sıkı bağlı kodlar (Tight Coupling) yazmak yerine birbirine gevşek bağlı (Loose Coupling) kodlar yazmamıza imkan vermesidir.
Bir uygulama geliştirirken yeni gelen Feature için yazdığımız kodları bozmayacaktır.

Dependency Inversion (DIP)

Inversion Of Control ve Dependency Inversion bu Principler kendilerini tamamlayan, omuz omuza çalışan prensiplerdir. Class’lar veya Object’ler arasındaki bağları kaldırmayı hedefleyen bir prensiptir.

Şöyle düşünelim, bir A ve B Class’ımız var. Yukarıda bahsedilen örnekteki gibi A ve B Class’ları birbirine sıkı sıkı bağlıdır. Burada B Class’ında bir değişiklik yapıldığında haliyle A Class’ında da değişiklik yapılması gerekmektedir. Dependency Inversion’a göre A Class’ı ile B Class’ı birbirini tanımasın veya birbirini tam olarak bilmesin. Bunun için Interface veya Abstract Class gibi bir ara katman kullanılması gerekir.

Dependency Injection (DI)

Dependency Injection bir tasarım kalıbıdır. ASP.Net Core tarafında geliştirme yaparken çoğunlukla duyarız. DI, bizim yazılım bileşenlerindeki sıkı bağı azaltmaya yardımcı olan bir tasarım kalıbıdır.

Tasarım Kalıpları: Kodlama yaparken karşılaştığımız problemlere tekrar tekrar kullanılabilir çözümler sunan yapılardır.

Uygulamalarımızın ileriki aşamalarında bir değişiklik yapmak istediğimiz zaman çok daha az zahmetle bu işi gerçekleştirebileceğiz.

Unit Test’lerimizi yazarken bize birçok kolaylık sağlayacak. İlgili Class’larımızı kolay bir şekilde Mock’layabilmemizi olanak sağlayacak.

Unit Test yazabilmek için projemizin bazı prensipleri ve bazı Tasarım Kalıplarını uygulamış olması gerekiyor. Her projeye kolay bir şekilde Unit Test yazamayabilirsiniz. Eğer ki projenizde DI gibi bir tasarım desenini kullanmadıysanız Mock’layamazsınız.

Inversion Of Control Container (IoC)

Bu terim aslında Framework’lerin genel isimleridir. Örneğin Autofac, Ninject, Unity, Scrutor vs.

Peki Framework’ler ne iş yapar ?
Yukarıda bahsetmiş olduğumuz Presip ve Tasarım Kalıplarını otomatikleştiren Framework’tür. Bu Framework’ler sayesinde daha az kod yazmış olup böylelikle Prensip ve Tasarım Kalıplarını kullanmış olursunuz.

IOC / DIP / DI / Tightly Coupled / Loosely Coupled

Bu yukarıda anlatmış olduğum kavramları adım adım pratiğe dönüştürme işlemini gerçekleştireceğiz. Basit bir uygulama inşa ederek bu uygulamamız içinde 2 tane Class’ımız olacak, bu Class’lar birbirine Tightly Coupled şeklinde bağlanmış olacak ve daha sonra adım adım implemantasyonları gerçekleştirerek Loosely Coupled Class’lar oluşturmaya çalışacağız. Burada basit bir Console uygulamasıyla örneğimizi gerçekleştireceğiz.

Öncelikle Product, DataAccessLayer ve BusinessLogic olmak üzere 3 tane Class’ımız olacak. Buradaki veriyi de Program.cs’den okuyor olacağız.

internal class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int Stock { get; set; }
}
    internal class DAL
{
public List<Product> GetProducts()
{
return new List<Product>
{
new Product { Id = 1, Name = "Product 1", Price = 10, Stock = 100 },
new Product { Id = 2, Name = "Product 2", Price = 20, Stock = 200 },
new Product { Id = 3, Name = "Product 3", Price = 30, Stock = 300 },
new Product { Id = 4, Name = "Product 4", Price = 40, Stock = 400 },
new Product { Id = 5, Name = "Product 5", Price = 50, Stock = 500 }
};
}
}
    internal class BL
{
private readonly DAL _dal;

public BL()
{
_dal = new DAL();
}

public List<Product> GetProducts()
{
return _dal.GetProducts();
}
}

İlgili dosyalarımızı oluşturup kodlarımızı yazdıktan sonra Program.cs tarafında kodlarımızı yazıp çalıştıralım.

// See https://aka.ms/new-console-template for more information
using MediumIOC.Console;

BL bL = new BL();
bL.GetProducts().ForEach(p => Console.WriteLine($"{p.Id} - {p.Name} - {p.Price} - {p.Stock}"));

Console.ReadLine();

Tightly Coupled kodumuzu yazdık, dikkat ettiyseniz burada herhangi bir değişiklik olduğunda birçok yerde düzenleme yapmamız gerekiyor. İlgili DAL (DataAccessLayer) Class’ımızda GetProducts methodunu GetProducts2 yaptığımız anda hata alacağız. Bizim normalde gidip BL (BusinessLogic) Class’ımızda da ilgili düzeltmeyi yapmamız gerekiyor. Gelin bu kodu Loosely Coupled koda dönüştürmeye başlayalım.

Factory Pattern ile IOC implementasyon işlemini gerçekleştirelim

Yukarıda zaten bu konuları anlattığım için direkt oluşturduğumuz projedeki örnekten gidecek olursak, ilk olarak bizim BusinessLogic’de new anahtar sözcüğüyle DAL (DataAccessLayer) yeniliyor. Bizim ilk olarak bunu düzeltmemiz gerek. Bunun için öncelikle DAL (DataAccessLayer) Class’ını BusinessLogic’e verecek bir Class oluşturmamız gerekecektir. Bu Class bizim için geriye DAL (DataAccessLayer) nesne örneği dönecek.

internal class DALFactory
{
public static DAL GetDAL()
{
return new DAL();
}
}

Bu Class’ı oluşturduğumuza göre BusinessLogic artık DAL (DataAccessLayer) Class’ını kendisi üretmeyecek, DALFactory Class’ından nesne örneğini üretip dışarıdan almış olacak.

internal class BL
{
private readonly DAL _dal;

public BL()
{
_dal = DALFactory.GetDAL();
}

public List<Product> GetProducts()
{
return _dal.GetProducts();
}
}

Şimdi gelin DIP (Dependency Inversion Principle) implemente ederek bir soyutlama işlemi gerçekleştirelim. BL (BusinessLogic) ve DAL (DataAccessLayer)’ı birbirini bilmeyecek şekilde Interface veya Abstract Class tanımlayalım.

interface IDAL
{
List<Product> GetProducts();
}

internal class DALFactory
{
public static IDAL GetDAL()
{
return new DAL();
}
}
internal class BL
{
private readonly IDAL _dal;

public BL()
{
_dal = DALFactory.GetDAL();
}

public List<Product> GetProducts()
{
return _dal.GetProducts();
}
}

Biz burada DAL (DataAccessLayer) classına bir method daha yazacak olsak BusinessLogic’in haberi olmaz.

Biz ancak ilgili interface tanımlarsak o zaman haberi olacaktır. Şimdi gelin BusinessLogic ile DataAccessLayer’ı Dependency Injection Tasarım Kalıbını kullanarak arasındaki bağı biraz daha zayıflatalım. Burada biz DI kullanarak DataAccessLayer instance’ını elde etme işlemini DALFactory’den değil, farklı bir şekilde elde edeceğiz. (constructor, method, property) Bir constructor üzerinden süreci yürüteceğiz.

internal class BL
{
private readonly IDAL _dal;

public BL(IDAL dal)
{
_dal = dal;
}

public List<Product> GetProducts()
{
return _dal.GetProducts();
}
}

Program.cs tarafında BusinessLogic’de DAL (DataAccessLayer)’ımız IDAL’ı implemente ettiğinden herhangi bir problem olmayacaktır.

// See https://aka.ms/new-console-template for more information
using MediumIOC.Console;

BL bL = new BL(new DAL());
bL.GetProducts().ForEach(p => Console.WriteLine($"{p.Id} - {p.Name} - {p.Price} - {p.Stock}"));

Console.ReadLine();

Biz burada baştan sona doğru gelirken farklı şekilde kodlama yaptık. Bu kolaylıklar bize neler kazandırıyor onlara bakalım. Örnek verecek olursak, DAL katmanımızda SQL veritabanını kullandığımızı düşünelim. Projemizi canlıya aldıktan sonra PostgreSQL veritabanına geçtiğimizi düşünelim. Biz burada herhangi bir değişiklik yapmadan PostgreDAL diye yeni bir Class’a IDAL’ımızı tanımlayıp veriyi alabiliriz. Program.cs tarafında DAL yerine PostgreDAL’ı çağırmamız yetecektir.

Asp.Net Core ile (IOC/DI) Container

Biz ASP.Net Core projesi oluşturduğumuzda gömülü olarak gelen Inversion Of Control Container Framework’ünü inceleyeceğiz. DI nesnelerinin ömrüne 3 şekilde müdahale edebiliyoruz:

Singleton: Uygulama ayakta olduğu müddetçe 1 kez nesne oluşur.

Scoped: Bağımlı olan nesnenin yaşam döngüsünün süresi 1 request süreci kadar olmaktadır. Request atıldığında bağımlılık 5 defa ise nesne 1 kere oluşur. Fakat 2. requestte tekrar o nesneyi oluşturur.

Transient: Her ihtiyaç duyulduğunda sıfırdan bir nesne örneği oluşturur.

Gelin şimdi bir örnek projeyle bu konuya bakalım. İlk olarak bir tane .NET Core Web MVC projesi oluşturalım. Burada DateService, IDateService Class’larımızı oluşturalım.

interface IDateService
{
DateTime GetDateTime { get; }
}

interface ISingletonDateService:IDateService { }
interface IScoopedDateService:IDateService { }
interface ITransientDateService:IDateService { }
    public class DateService : ISingletonDateService, IScoopedDateService, ITransientDateService
{
private readonly ILogger<DateService> _logger;
public DateService(ILogger<DateService> logger)
{
_logger = logger;
logger.LogWarning("DataService constructor'ına girdi");
}
public DateTime GetDateTime { get; } = DateTime.Now;
}

Yazmış olduğumuz bu Interface ve Service’i Framework’e bildirmek için Program.cs dosyamıza yaşam döngümüzü belirleyelim.

// Add services to the container.
builder.Services.AddSingleton<ISingletonDateService, DateService>();
builder.Services.AddScoped<IScoopedDateService, DateService>();
builder.Services.AddTransient<ITransientDateService, DateService>();

ISingletonDateService’imizi Controller ve Method’a inject etme işlemini yapalım. HomeController için yazılmıştır.

//constructor injection
private readonly ISingletonDateService _singletonDateService;
public HomeController(ISingletonDateService singletonDateService)
{
_singletonDateService = singletonDateService;
}

//method injection
public IActionResult Index([FromServices] ISingletonDateService _singletonDateServiceMethod)
{
ViewBag.ControllerTime = _singletonDateService.GetDateTime.TimeOfDay.ToString();
ViewBag.MethodTime = _singletonDateServiceMethod.GetDateTime.TimeOfDay.ToString();
return View();
}
<h1>@ViewBag.ControllerTime</h1>
<h2>@ViewBag.MethodTime</h2>

Index.cshtml dosyamıza ViewBagleri çağırıp uygulamayı ayağa kaldıralım.

Örnekte de gördüğünüz gibi bir kere çalıştı. Peki IScoopedDataService örneğine bakalım.

//constructor injection
private readonly IScoopedDateService _scoopedDateService;
public HomeController ( IScoopedDateService scoopedDateService )
{
_scoopedDateService = scoopedDateService;
}

//method injection
public IActionResult Index ( [FromServices] IScoopedDateService _scoopedDateServiceMethod )
{
ViewBag.ControllerTime = _scoopedDateService.GetDateTime.TimeOfDay.ToString();
ViewBag.MethodTime = _scoopedDateServiceMethod.GetDateTime.TimeOfDay.ToString();
return View();
}

Uygulamayı ayağa kaldırıp aynı sayfaya birden fazla istek attığımızda açılan terminalde oluşan nesnenin kaç kere çağrıldığı görülecektir.

Son olarak ITransientDateService örneğimizi inceleyelim.

//constructor injection
private readonly ITransientDateService _transientDateService;
public HomeController ( ITransientDateService transientDateService )
{
_transientDateService = transientDateService;
}

//method injection
public IActionResult Index ( [FromServices] ITransientDateService _transientDateServiceMethod )
{
ViewBag.ControllerTime = _transientDateService.GetDateTime.TimeOfDay.ToString();
ViewBag.MethodTime = _transientDateServiceMethod.GetDateTime.TimeOfDay.ToString();
return View();
}

Uygulamayı ayağa kaldırıp bir kaç kez istek atalım.

Burada da gördüğünüz gibi aslında her ihtiyaç olduğunda nesne yeniden çağrılıyor. Yazdığımız bu kodları incelemek isterseniz GitHub linkini aşağıda bırakıyorum.

Bir sonraki yazımızda görüşmek üzere..

--

--

Tolgahan Öztürk
KoçSistem

Hi! I‘m an developer passionate about Full Stack and .Net learning. I’m here because best way to learn something is to teach it.Hope you enjoy!