Basit bir IoC Container ve Loglama sistemi yapımı

Merhaba arkadaşlar,

Uzun zamandır makale yazamadığımı farkettim ve projelerimizde kullanım kolaylığı sağlayacak bir konu ile arayı pekiştirmek istedim. 🙂

Öncelikle nedir bu IoC Container?

Açılımını Inversion of Control’den alan IoC Container, uygulamanın akışı sırasında bize yaratılması gereken doğru tipi yaratarak, uygulamanın akışını doğru bir şekilde ilerleten özel sınıflardır.

Kısaca Dependency Injection ve Dependency Inversion‘dan bahsetmek gerekirse:

Dependency Injection prensibi, uygulama içerisindeki bileşenlerin birbirleri ile sıkı sıkıya bağlı(tightly coupled) olmaması yani gevşek bağlı(loosely coupled) olmasıdır diyebiliriz.

Dependency Injection ile uygulamanın çalışacağı bileşenleri dışarıdan enjekte ederek, ileride oluşabilecek herhangi bir değişiklikten minimum seviyede etkinlenmesini sağlamış oluruz.

——–

Dependency Inversion ise, “bağımlılıkların tersine çevrilmesi” anlamına gelmektedir. Yani somut sınıflara olan bağımlılıkları, soyutlayarak ortadan kaldırılmasıdır.

Somut sınıflarımız sık sık değişikliğe uğrayabilecekleri için buna bağımlı olan diğer sınıflarımızda etkilenecektir.

Hemen örnek kodumuz ile basit bir IoC Container yapalım ve bir Loglama sistemi geliştirelim.

GokFramework.IoCContainer isminde bir yeni proje ekleyip içine IoCResolver.cs isminde bir class oluşturuyorum.

using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;

namespace GokFramework.IoCContainer
{
    public class IoCResolver
    {
        #region Constructor
        private static object objLock = new object();
        private static IoCResolver m_IoCResolver;
        public static IoCResolver getInstance
        {
            get
            {
                if (m_IoCResolver == null)
                {
                    lock (objLock)
                    {
                        if (m_IoCResolver == null)
                            m_IoCResolver = new IoCResolver();
                    }
                }

                return m_IoCResolver;
            }
            set { m_IoCResolver = value; }
        }

        #endregion

        /// <summary>
        /// Injection of control
        /// </summary>
        /// <typeparam name="TSource">Injection yapılacak class type.</typeparam>
        /// <typeparam name="TDestination">Injection edilecek class type.</typeparam>
        /// <returns></returns>
        public void Resolve<TSource, TDestination>()
        {
            object dependencyForInstance, implementedByInstance;
            dependencyForInstance = Activator.CreateInstance(typeof(TSource));
            implementedByInstance = Activator.CreateInstance(typeof(TDestination));

            foreach (var pi in dependencyForInstance.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Static))
            {
                var piList = implementedByInstance.GetType().GetInterfaces().Where(x => x.Name.Equals(pi.FieldType.Name)).ToList();
                if (piList.Count > 0)
                { pi.SetValue(dependencyForInstance, implementedByInstance); break; }
            }
        }
    }
}

Class’ımıza baktığımızda, öncelikle constructor‘ımızda Singleton Patternini uyguladığımızı görüyoruz tekilliğini sağlamak için.

Resolve isminde bir method oluşturarak iki tip alıyoruz TSource ve TDestination olarak. TSource burada enjekte yapılacak class’ımızı temsil ederken, TDestination ise enjekte edilecek concrete class’ımızı temsil ediyor.

Runtime’da instancelerini alarak, dependencyForInstance objemizin yani TSource‘umuzun field’larında (Birazdan loglama geliştirme kısmında inceleyeceğimiz Loglama context’imizdeki ILogger property’mizin private static olduğu için field’larını alıyoruz) dolanarak enjekte yapılacak class’ımızın (TSource) içindeki, dışarıdan enjekte edilecek class tipini bularak, varsa:

implementedByInstance.GetType().GetInterfaces().Where(x => x.Name.Equals(pi.FieldType.Name))

SetValue methodu ile TSource’umuza, dışarıdan enjekte edilecek sınıfın instance’sini set ediyoruz. Loglama context’imizdeki ILogger property’mizi static olarak tanımladığımız için, instance’si ram bellekte saklanacaktır ve dolayısıyla uygulamamızın çalışması boyunca loglama sistemimizi kullanabiliriz. Tekrardan dışarıdan bir tipi enjekte etme ihtiyacı duymayız. Böylelikle hem kod tekrarından kurtulmuş olduk, hemde generic bir şekilde tekrar kullanılabilirliğini arttırdık.

IoC Container tasarımımız işte bu kadar. 🙂 Artık istediğimiz zaman uygulamamızın Application_Start’ında veya Main’inde bir kere enjekte etmemiz yetecektir.

 

Hemen basit bir Loglama sistemimizi tasarlayalım:

GokFramework.Logger isminde yeni bir proje daha ekleyerek, Log sistemimizi soyutlamak için ILogger isminde bir interface ekliyorum.

using System.Reflection;

namespace GokFramework.Logger
{
    public interface ILogger
    {
        void Log(MethodBase methodBase, string message);
    }
}

MethodBase’i isteyerek hangi class’dan ve method’dan çağrıldığını yakalayabileceğiz loglama sırasında ve log tutulacak mesajımız.

 

FileLogger isminde bir yeni class ekleyerek ILogger interface’mizi implemente ediyoruz. FileLogger burada concrete class’ımız oluyor.

using System;
using System.Configuration;
using System.IO;
using System.Reflection;

namespace GokFramework.Logger
{
    public class FileLogger : ILogger
    {
        public void Log(MethodBase methodBase, string message)
        {
            string logPath = ConfigurationManager.AppSettings["GokFramework.LoggerPath"].TrimEnd('/');
            string logName = DateTime.Now.ToString("yyyyMMdd_HHmm") + ".txt";
            string logMessage = string.Format("Namaspace: {0}\r\nClass: {1}\r\nMethod: {2}\r\nMessage: {3}",
methodBase.ReflectedType.Namespace, methodBase.ReflectedType.Name, methodBase.Name, message);
            string path = System.IO.Path.Combine(logPath, logName);

            if (!Directory.Exists((logPath)))
                Directory.CreateDirectory(logPath);

            StreamWriter sw = new StreamWriter(path, true);
            sw.WriteLine(logMessage);
            sw.Close();
            sw.Dispose();
        }
    }
}

Basit olarak bir log tutma methodu yazıyorum, tarihe göre bir txt dosyası oluşturarak, hangi namespace’den ve method’dan çağırıldığı gibi içeriği tutacağız.

Log tutulacak path’imizi App.Config dosyasından “GokFramework.LoggerPath” key’i ile okuyorum.

 

Hemen ardından LoggerContext isminde bir class daha ekleyerek kodlamaya geçiyorum.

using System.Reflection;

namespace GokFramework.Logger
{
    public class LoggerContext
    {
        #region Properties
        private static ILogger Logger { get; set; }
        #endregion

        public static void Log(MethodBase methodBase, string message)
        {
            Logger.Log(methodBase, message);
        }
    }
}

Log sistemimizi LoggerContext ile sarmalayarak, Logger isminde ve ILogger tipinde bir static property tanımlıyoruz. Böylelikle LoggerContext’imizin ILogger tipinden gelecek olan Loglama concrete class’ımız ile sıkı bağlılığını engellemiş oluyoruz.

İleride gelecek olan FileLogger haricinde yeni geliştirmelerin örneğin, MailLogger, SmsLogger gibi concrete class’larımızıda kolaylıkla sistemimize dahil edebilmemizi sağlamış olacak. Aslında burada Strategy patterni uygulamış olduk.

Bir log tutma işimiz var ve bunu tutabilmek için birden fazla algoritmamız var.

 

Hemen kullanımlarına geçelim.

Örnek bir console uygulaması oluşturarak Program.cs’in içerisinde kodlamaya başlıyorum.

using GokFramework.IoCContainer;
using GokFramework.Logger;
using System.Reflection;

namespace GokFramework.ConsoleForTest
{
    class Program
    {
        static void Main(string[] args)
        {          
            #region Logger Test
            // Application_Start'da bir kere çalıştırılacak, injection için.
            IoCResolver.getInstance.Resolve<LoggerContext, FileLogger>();

            // Tüm projede injection yapılan tipte çalışacaktır.
            LoggerContext.Log(MethodBase.GetCurrentMethod(), "blabla");
            #endregion
        }
    }
}

Kullanımı bu kadar basit.

Uygulamamızda hangi tipte log tutmak istiyorsak sadece bir kere Resolve etmemiz yeterli. Burada LoggerContext yani içerisinde ILogger’imizi barındıran class’ımız yani TSource oluyor, enjecte yapılacak class’ımız.

FileLogger ise dışarıdan ILogger’a enjekte edilecek concrete class’ımız yani TDestination. Bu MailLogger’da olabilir, SmsLogger’da.

 

Umarım yararlı bir konu olmuştur. Örnek kodlar ektedir. 🙂

GokFramework

 

Gökhan Gökalp

View Comments

  • İyi çalışmalar dilerim Gökhan hocam. Bir sorum olacaktı. Buradaki IoCResolver'a gerçekten Singleton design pattern uygulanmış mı? Set bloğu duruyor. En azından private set olmalı diye düşünüyorum illa set olacaksa.

    #region Constructor
    private static object objLock = new object();
    private static IoCResolver m_IoCResolver;
    public static IoCResolver getInstance
    {
    get
    {
    if (m_IoCResolver == null)
    {
    lock (objLock)
    {
    if (m_IoCResolver == null)
    m_IoCResolver = new IoCResolver();
    }
    }

    return m_IoCResolver;
    }
    private set { m_IoCResolver = value; }
    }

    #endregion

  • Bu tip işleri frameworklerden yapsak sanırım daha sağlıklı olcak Autofact gibi yada .net corede default gelen gibi

    • Kesinlikle katılıyorum, bu makale daha çok kullanmış olduğunuz o framework'lerin arka planda az, çok nasıl çalıştığını anlatabilmek adına. Teşekkürler

  • Hala tam anlayamadım ama eline sağlık üstat. Öğrenicez yavaş yavaş :) Türkçe kaynak için sağolasın.

Recent Posts

Securing the Supply Chain of Containerized Applications to Reduce Security Risks (Policy Enforcement-Automated Governance with OPA Gatekeeper and Ratify) – Part 2

{:tr} Makalenin ilk bölümünde, Software Supply Chain güvenliğinin öneminden ve containerized uygulamaların güvenlik risklerini azaltabilmek…

5 months ago

Securing the Supply Chain of Containerized Applications to Reduce Security Risks (Security Scanning, SBOMs, Signing&Verifying Artifacts) – Part 1

{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…

8 months ago

Delegating Identity & Access Management to Azure AD B2C and Integrating with .NET

{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…

12 months ago

How to Order Events in Microservices by Using Azure Service Bus (FIFO Consumers)

{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…

2 years ago

Providing Atomicity for Eventual Consistency with Outbox Pattern in .NET Microservices

{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…

2 years ago

Building Microservices by Using Dapr and .NET with Minimum Effort – 02 (Azure Container Apps)

{:tr}Bir önceki makale serisinde Dapr projesinden ve faydalarından bahsedip, local ortamda self-hosted mode olarak .NET…

2 years ago