Asp.NET Core – Çok Katmanlı Mimaride İstenilen Katmanda Dependency Injection Kullanımı

Merhaba,

Önceki içeriklerimizden Asp.NET Core 3.0 – Çok Katmanlı Mimaride Migration İşlemleri başlıklı makalemizde çok katmanlı mimari yapılanmasının migration işlemleriyle birlikte yapısal inceliklerini ele almıştık. İlgili makaleye göz atarsanız eğer, katmanlar arasında DAL -> BL -> PL olması gereken ilişkiyi DAL -> PL && (BL -> PL) şeklinde kurduğumuzu göreceksiniz. Bunun nedeni DAL katmanı aracılığıyla veritabanından gelen verileri BL katmanında işleyebilmek için BL katmanında DAL katmanındaki repository, context vs. nesnelerinin dependency injection ile talep edilebiliyor olması gerekmektedir. Bunun içinde PL katmanında talep edilen bu nesneler BL’ye gönderilmekte ve dolayısıyla PL’de DAL katmanını referans göstermek mecburiyetinde kalınmaktadır. Bu mecburiyet esasında çok katmanlı mimari dediğimiz stratejik yaklaşımı zedelemekte ve stratejide taktiksel hataya sebebiyet vermektedir. İşte bizler bu makalemizde BL katmanında PL’ye gereksinim duymaksızın dependency injection stratejisinin nasıl uygulaması gerektiğini ele alacak ve ortaya mimarisel açıdan ideal yaklaşımın nasıl olması gerektiğini irdeleyerek, doğru yapılanmanın somut örneğini geliştireceğiz.

Yani uzun lafın kısası; PL katmanında DAL katmanını referans etmeksizin(mesela “Startup.cs” dosyasına context nesnesini AddDbContext ile eklemeksizin) tüm veritabanı işlemlerini BL katmanında ihtiyaca dönük nasıl yapılandırılacağını inceleyeceğiz.

Şimdi adım adım çok katmanlı mimari yaklaşımını benimsediğimiz bir proje geliştirelim. Herşeyden önce en temel katmanımız olan DAL katmanını ele alarak işe başlayalım.

  • DAL
    Veritabanı(context) nesnesi ve migration vs. gibi yapılanmaların bulunduğu katmandır.
    Asp.NET Core - Çok Katmanlı Mimaride İstenilen Katmanda Dependency Injection Kullanımı
    Kaynak kodlar oldukça standart yapıda olduğu için ekstradan göstermeye lüzum görmemekteyim.
  • Entities
    DAL katmanıyla yüksek oranda ilişkisel nesneleri barındıracak olan Entities katmanı içerisinde uygulamada kullanılacak tüm entity yapılanlarını barındırmaktadır. Entityler; DAL’ın dışında BL, PL ve hatta çoğu custom katmanda ihtiyaca dönük kullanıldığından dolayı gönül rahatlığıyla istenilen katman tarafından referans edilebilmesi ve çok katmanlı mimari yapılanmasını zedelememek için DAL’dan ayrılmıştır.
    Asp.NET Core - Çok Katmanlı Mimaride İstenilen Katmanda Dependency Injection Kullanımı
  • BL
    Bilindiği gibi her türlü iş, eylem, fiiliyat, algoritma, istatistik vs. yani operasyonel tüm işlevleri kapsayan katmandır. Bizler örneğimizde Repository yapılanmasını kullanacağımızdan dolayı BL katmanımızda ilgili yapılanmaya dair çalışmalar bulunmaktadır.
    Asp.NET Core - Çok Katmanlı Mimaride İstenilen Katmanda Dependency Injection Kullanımı

    BL katmanındaki bu çalışmalar dependency injection gerektirdiğinde PL üzerinden beslenmeyeceğinden dolayı buradaki dependency injection providerına ilgili nesneleri atabilmeli ve yeri geldiğinde kullanabilmeliyiz. Bu nokta kritik önem arz etmekte olduğundan dolayı odaklanmamız gereken merkezlerden birisidir.

    Şimdi gelin BL katmanındaki tüm inşa sürecini tek tek ele alıp, bu işlemin nasıl gerçekleştirildiğini konuşalım.

    • IRepository Interface’i
      Repository imzasını sunan arayüzdür.

          public interface IRepository<T> where T : class
          {
              List<T> Get();
              List<A> Get<A>() where A : class;
              List<T> GetWhere(Expression<Func<T, bool>> metot);
              List<A> GetWhere<A>(Expression<Func<A, bool>> metot) where A : class;
              T GetSingle(Func<T, bool> metot);
              A GetSingle<A>(Func<A, bool> metot) where A : class;
              T GetById(int id);
              A GetById<A>(int id) where A : class;
              bool Add(T model);
              bool Add<A>(A model) where A : class;
              bool Remove(T model);
              bool Remove<A>(A model) where A : class;
              bool Remove(int id);
              bool Remove<A>(int id) where A : class;
              bool Update(T model, int id);
              bool Update<A>(A model, int id) where A : class;
              int Save();
          }
      
    • Repository Sınıfı
      Repository yapılanmasında base class olarak kullanacağımız sınıftır.

          public class Repository<Type> : IRepository<Type> where Type : class
          {
              protected SirketContext _sirketContext;
              public Repository()
              {
                  IServiceCollection services = new ServiceCollection();
                  services.AddDbContextService();
                  ServiceProvider provider = services.BuildServiceProvider();
      
                  _sirketContext = provider.GetService<SirketContext>();
              }
              [NonAction]
              public DbSet<Type> Table()
              {
                  return Table<Type>();
              }
              [NonAction]
              public DbSet<A> Table<A>() where A : class
              {
                  return _sirketContext.Set<A>();
              }
              [NonAction]
              public bool Add(Type model)
              {
                  return Add<Type>(model);
              }
              [NonAction]
              public bool Add<A>(A model) where A : class
              {
                  Table<A>().Add(model);
                  Save();
                  return true;
              }
              [NonAction]
              public List<Type> Get()
              {
                  return Get<Type>();
              }
              [NonAction]
              public List<A> Get<A>() where A : class
              {
                  return Table<A>().ToList();
              }
              [NonAction]
              public Type GetById(int id)
              {
                  return GetById<Type>(id);
              }
              [NonAction]
              public A GetById<A>(int id) where A : class
              {
                  return GetSingle<A>(t => typeof(A).GetProperty("Id").GetValue(t).ToString() == id.ToString());
              }
              [NonAction]
              public bool Remove(Type model)
              {
                  return Remove<Type>(model);
              }
              [NonAction]
              public bool Remove<A>(A model) where A : class
              {
                  Table<A>().Remove(model);
                  return true;
              }
              [NonAction]
              public bool Remove(int id)
              {
                  return Remove<Type>(id);
              }
              [NonAction]
              public bool Remove<A>(int id) where A : class
              {
                  A silinecekData = GetSingle<A>(x => (int)typeof(A).GetProperty("Id").GetValue(x) == id);
                  Remove<A>(silinecekData);
                  Save();
                  return true;
              }
              [NonAction]
              public int Save()
              {
                  return _sirketContext.SaveChanges();
              }
              [NonAction]
              public bool Update(Type model, int id)
              {
                  return Update<Type>(model, id);
              }
              [NonAction]
              public bool Update<A>(A model, int id) where A : class
              {
                  A guncellenecekNesne = GetById<A>(id);
                  var tumPropertyler = typeof(A).GetProperties();
                  foreach (var property in tumPropertyler)
                      if (property.Name != "Id")
                          property.SetValue(guncellenecekNesne, property.GetValue(model));
                  Save();
                  return true;
              }
              [NonAction]
              public List<Type> GetWhere(Expression<Func<Type, bool>> metot)
              {
                  return GetWhere<Type>(metot);
              }
              [NonAction]
              public List<A> GetWhere<A>(Expression<Func<A, bool>> metot) where A : class
              {
                  return Table<A>().Where(metot).ToList();
              }
              [NonAction]
              public Type GetSingle(Func<Type, bool> metot)
              {
                  return GetSingle<Type>(metot);
              }
              [NonAction]
              public A GetSingle<A>(Func<A, bool> metot) where A : class
              {
                  return Table<A>().FirstOrDefault(metot);
              }
          }
      
    • Concreate Repository Sınıfları
      Entity bazlı somut repository sınıflarımızdır.

          public class PersonelRepository : Repository<Personel>
          {
          }
      
          public class SatisRepository : Repository<Satis>
          {
          }
      
    • DbContextService.cs Dosyası
      DAL katmanındaki context nesnesini dependency injectiondan talep edilebilir hale getirmek için ilgili nesneyi providera eklememiz gerekmektedir. Bunun için class librariy’de dependency injection’ın nasıl yapıldığını ele almamız gerekmektedir. Esasında evvelinde bu konuya dair şuradaki makaleyi kaleme almış bulunmaktayım. Burada biraz soluklanıp ilgili makaleye göz atabilirsiniz. Kısaca özetlemek gerekirse bu işlem için IServiceCollection arayüzü kullanılmaktadır.

          static class DbContextService
          {
              public static IServiceCollection AddDbContextService(this IServiceCollection services)
              {
                  ServiceProvider provider = services.BuildServiceProvider();
      
                  services.AddDbContext<SirketContext>(x => x.UseSqlServer("Server=.;Database=SirketDB;Trusted_Connection=True;"));
                  return services;
              }
          }
      

      Yukarıdaki kod bloğunu incelerseniz eğer IServiceCollection arayüzüne özel bir extension metot geliştirilmiştir. İlgili metot, services parametresi üzerinden provider inşa etmekte ve üretilen bu providera “AddDbContext” metodu ile DAL katmanındaki context nesnemizi gerekli konfigürasyonlarıyla birlikte eklemektedir.

      Ve yukarıda incelediğimiz repository sınıfınasatıra gitmek için tıklayınız göz atarsanız eğer ilgili sınıfın 4 ile 11. satır aralığında tanımlanmış olan constructer metodu içerisinde üretilen ServiceCollection nesnesi üzerinden bu oluşturduğumuz “AddDbContextService” isimli metot çağrılmakta ve oluşturulan provider üzerinden de context nesnemiz talep edilmektedir. Böylece uygulama, PL katmanında DAL referansına ihtiyaç duymaksızın direkt olarak BL katmanında veritabanı yeteneğini kazanmaktadır.

    • RepositoryService.cs Dosyası
      Yukarıda oluşturulan repository iskeletinide benzer mantıkla dependency injection providerına ekleyebilme sorumluluğunu üstlenmesi için oluşturduğumuz dosyadır.

          public static class RepositoryService
          {
              public static IServiceCollection AddRepositoryService(this IServiceCollection services)
              {
                  services.AddTransient<IRepository<Personel>>(x => new PersonelRepository());
                  services.AddTransient<IRepository<Satis>>(x => new SatisRepository());
                  return services;
              }
          }
      

      Görüldüğü üzere generic parametresine göre repository nesneleri üretilmekte ve Transient ile dependency injection providerına eklenmektedir.

Üretilen Servislerin Enjekte Edilmesi

Bu işlem için yapılması gereken tek şey PL katmanında Startup.cs dosyasının “ConfigureServices” metodunda “AddRepositoryService” isimli metodu çağırmaktır. Böylece tüm repository nesnelerimiz uygulamaya inject edilmiş olacaktır. Peki hoca, context nesnesini nasıl inject edeceğiz? sorunuzu duyar gibiyim… Hatırlarsanız eğer onu zaten BL katmanındaki oluşturduğumuz Repository sınıfında inject edip kullanmıştık. O yüzden PL’de context nesnesine dair inject işlemine gerek yoktur.

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            .
            .
            services.AddRepositoryService();
            .
            .
        }
    }

Bu işlemin ardından artık controller sınıflarınızı, context nesnelerini inject etmeye ihtiyaç duymaksızın oluşturabilir ve endpointler yahut actionlarınızı repository ile gönül rahatlığıyla kullanabilirsiniz.

Örnek bir controller;

    public class PersonelController : Controller
    {
        readonly PersonelRepository _personelRepository;
        public PersonelController(IRepository<Personel> personelRepository)
        {
            _personelRepository = (PersonelRepository)personelRepository;
        }
        public IActionResult Index()
        {
            return View(_personelRepository.Get());
        }

        public IActionResult Create()
        {
            return View();
        }

        [HttpPost]
        public IActionResult Create(Personel model)
        {
            _personelRepository.Add(model);
            _personelRepository.Save();
            return RedirectToAction("Index");
        }
    }

Nihai olarak;
Genellikle DAL katmanında oluşturulan context sınıfının PL’de kullanılarak işlevsel olarak problem yaratmayan ama çok katmanlı mimari stratejisinin etiğine aykırı olan bu davranışı irdelediğimiz yöntemde olduğu gibi ortadan kaldırmış ve stratejiyi olması gereken en doğru yöntemlerle, prensibe uygun bir şekle getirip oturtturmuş bulunmaktayız.

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Not : Örnek projeyi indirebilmek için buraya tıklayınız.

Bunlar da hoşunuza gidebilir...

2 Cevaplar

  1. Samet dedi ki:

    Hocam ellerinize sağlık, biraz geç gördüm ama hala çözüme kavuşmamış halde projemde o kısmı bırakmıştım. Sayenizde hemen düzenleyeceğim. Yardımlarınız ve bizlerle paylaştığınız değerli bilgiler için çok çok teşekkür ederim.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

*