Caner Tosuner

Leave your code better than you found it

StructureMap Nedir ? WebApi ile Kullanımı

Daha önceki IoC container yazılarında Ninject ve Windsor 'dan bahsetmiştik. Bu yazımızda ise 2016 benchmark'larına göre en hızlı IoC container olduğu söylenen StructureMap'i WebApi üzerinde örnek proje ile inceleyeceğiz. 

StructureMap ilk release'i .Net framework 1.1 için 2004 yılında çıkmış ve 12 yıldır hayatımızda olan en eski IoC/DI tool'u dur. Uygulama genelindeki instance yönetiminden sorumlu olup bağımlılıkları enjecte edebilmemizi sağlar.

Yazımızda StructureMap kullanarak basit bir infrastructure tasarlamaya çalışacağız. 

İlk olarak Vs'da bir Web Api projesi oluşturalım.

Sonrasında projemize nuget üzerinden StructureMap.WebApi2 paketini install edelim.

Kurulum işlemi tamamlandıktan sonra solution'da DependencyResolution adında auto generate olan bir klasör ve hem bu klasör içerisinde hemde App_Start klasörü içerisinde StructureMap konfigurasyonlarını yapabilmemizi sağlayacak olan class'ları göreceğiz. Şimdilik bu klasörü es geçelim projemizi hazır hale getirdikten sonra gerekli register işlemlerini yaparız. 

Örnek case'imiz şu şekilde olsun; UserController adında bir controller oluşturalım ve bu controller içerisinde tanımlı IUserService intercase'ini contructor injection yöntemi ile inject edelim. GetUserFullNames adında end-point ile geriye List of string dönelim.

    public class UserController : ApiController
    {
        private readonly IUserService _userService;
        public UserController(IUserService userService)
        {
            _userService = userService;
        }

        [HttpGet]
        public HttpResponseMessage GetUserFullNames()
        {
            var response = _userService.GetUserFullNames();

            return Request.CreateResponse(response);
        }
    }

Controller içerisinde kullandığımız IUserService interface'i ve onun implemantasyonunu aşağıdaki gibi oluşturalım.

public interface IUserService
{
    List<string> GetUserFullNames();
}
 
public class UserService : IUserService
{
    public List<string> GetUserFullNames()
    {
        return new List<string>
                              { "Olcay Şahan",
                                "Anderson Talisca",
                                "Oğuzhan Özyakup",
                                "Ricardo Quaresma",
                                "Cenk Tosun" };
    }
}

Yukarıda da söylediğimiz gibi UserController içerisindeki GetUserFullNames metodu HttpGet isteği alarak geriye UserService içerisinde bulunan GetUserFullNames metodunun return ettiği List of string'i dönecektir.

Örneğimiz hazır. Şimdi ise geriye son 2 adım kaldı.

1-) StructureMap ile ilgili container konfigurasyonlarını yapmak. DependencyResolution klasörü içerisinde bulunan DefaultRegistry adlı class'a gidip aşağıdaki gibi UserService'i container'a register edeceğiz.

    public class DefaultRegistry : Registry {
        #region Constructors and Destructors

        public DefaultRegistry() {
            Scan(
                scan => {
                    scan.TheCallingAssembly();
                    scan.WithDefaultConventions();
                });
            For<IUserService>().Use<UserService>();
        }

        #endregion
    }

2-) Son olarak App_Start/WebApiConfig.cs class'ına gidip DI container'ı start etmemiz gerekiyor. 

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
            
            //structureMap start
            StructuremapWebApi.Start();
        }
    }

Hem örneğimiz hemde container ile ilgili konfigurasyonlarımız hazır. Artık projemizi run edip yazdığımız kodları test edebiliriz. Bunun için projemizi run edelim ve yazmış olduğumuz http-Get end-point'ini browser da çağıralım.

Yukarıda görüldüğü gibi UserController da bulunan end-point'e call yaptık ve bize container'da bulunan IUserService'ine ait olan implementasyonu resolve edip UserService içerisinde bulunan GetUserFullNames metodunu execute edip geriye user listesini return etti

 StructureMap yazımız şimdilik bu kadar. İlerleyen günlerde daha farklı DI Container yazılarına devam edeceğiz.

Optimistic Lock Nedir ? Pessimistic Lock Nedir ? Data concurrency

Db'si olan ve son kullanıcı tarafından CRUD işlemlerinin bolca yapılabildiği bir proje geliştiriyorsanız veri tutarlılığı sizin için oldukça önemli bir hal almak durumundadır. Kayıtlı olan veriyi son kullanıcıya ulaştırabilip en güncel veri üzerinden transaction'ları işleyebilmek ve stale yada dirty data olarak da adlandırılan bayat veriyi handle edip kullanıcının erişmesini engellemek oldukça önemlidir. 

Transactional operasyonlar Concurrency'yi sağlayabilmek adına genelde üzerinde işlem yapılan veriye lock işlemi uygulanarak gerçekleştirilirler. Bu lock işlemi için 2 farklı yaklaşım vardır. Pessimistik Lock ve Optimistic Lock.

Pessimistic Lock

O an işlem gerçekleşirken üzerinde çalışılan kayıt lock'lanır ki o anda başka birisi o kayıt üzerinde değişiklik yapmasın. Bu işlem session bazlı olur ve transaction başlarken açılan session sonlandırılıncaya veya rollback yapılıncaya kadar işlem yapılan row db de lock'lanır. Örnek olarak bir bankacılık uygulaması düşünün ve bir hesaba aynı anda hem para çekme hemde para yatırma işlemi geldi. İlk para yatırma işlemini yapan thread öncelikli düşündüğümüzde bu transaction'ı gerçekleştiren session o account'u işlem sonlanıncaya kadar lock'lar ve para çekme işlemini bekletir. Transaction sonlandıktan sonra diğer thread'in gerçekleştireceği para çekme işleminin session'nını açarak güncel veri üzerinden işlemlerin gerçekleşmesine olanak sağlar. Bunu yapmasındaki amaç güncel veri üzerinden transaction'ı geçirip oluşabilecek kayıpları engellemektir. Ancak pessimistic lock'ın deadlock'lara sebep olabileceğini de unutmayalım.

 

Optimistic Lock

Optimistic Lock ise, adından da anlaşılacağı üzre "iyimser" birden fazla işlemin birbirini etkilemeden gerçekleşeceğini ve kimsenin kimse üzerinde bir lock koymayacağını söyler. Diğer bir deyişle farklı thread'ler de aynı row üzerinde işlem yapılırken herhangi bir lock işlemi olmadan update edilmek istenen verinin bayat olup olmadığını o verinin kayıtlı olduğu tabloda yer alan versiyon numarası olarak da adlandırılan bir column'da bulunan değeri kontrol eder ve eğer versiyon eşleşmiyorsa yani veri bayat ise işlem geri çekilir.

Peki bayat(stale) data ne demek ?

Örnek üzerinden anlatacak olursak; bir internet sitesinde kayıtlı bulunan adres bilginizi güncellemek istiyorsunuz. Aynı anda 2 farklı bilgisayardan bilgileri güncelle sayfasını açtınız ve adresiniz o an "Samsun" olarak kayıtlı yani 2 ekranda da "Samsun" yazıyor. İlk bilgisayarda bulunan kişi adres bilgisini "Ankara" olarak değiştirdi ve güncelle butonuna basıp bilgiyi güncelledi.

İkinci ekranda bulunan kişi ise ekranda halen "Samsun" yazılı iken adres bilgisini "İstanbul" olarak değiştirdi ve güncelle butonuna bas��p bilgiyi güncelledi. Ekranda yazan "Samsun" kaydı artık bizim için bayat bir kayıttır ve birinci kullanıcı değişikliği "Samsun" => "Ankara" yaptığını düşünürken ikinci kişi bu değişikliği "Samsun" => "İstanbul" yaptığını düşünüyor. Halbuki gerçekte olan ikinci kişi adres bilgisi ekranda "Ankara" iken => "İstanbul" olarak değiştirmiş oldu.

Ne oldu ? Pek de istemediğimiz bir case oluştu. İkinci kullanıcı Samsun olan kaydı İstanbul yaptığını düşünürken aslında Ankara olan kaydı İstanbul yaptı. Yani stale olan kaydı güncellemiş oldu.

Optimistic Lock ile ikinci kullanıcının stale olan veriyi update etmesine şu şekilde engel olabiliriz; Eğer Ado.Net kullanıyorsanız ve db de bulunan her bir row icin birer versiyon

numarasi vb gibi kaydinizvar ise query nizde where koşuluna o row için güncel olarak bulunan version bilgisini ekleyerek kontrol sağlayabiliriz veya Entity Framework yada NHibernate gibi ORM tool'larından birini kullanıyorsanız bu işlemi size bırakmadan güncellenmek istenen row'a ait versiyon numarasını select işleminde memory de tutuyor ve o veri için update transaction'ı execute edilirken bu versiyon numarası db de karşılaştırıyor. Eğer o versiyon numarası db de bulunan ile aynı ise versiyon numarasını 1 artırıp execution'a izin veriyor değilse hata fırlatıyor. Hata mesajı olarak kullanılan ORM türüne göre "The record you attempted to edit was modified by another user after you got the original value" gibi bir message return ediyor.

 

Hem optimistic hemde pessimistic lock konuları çok fazla önemsemediğimiz anlar olsa da oldukça önemli konulardır. Sonraki yazılarımızda Nhibernate veya Entity Framework kullanarak nasıl bir Optimistic Lock yapısı implement edebiliriz inceleyeceğiz.

Factory Method Pattern Nedir Nasıl Kullanılır

Design pattern'lar geliştirme yaparken tekrar eden problemlere denenip onaylanıp çözüm olarak sunulan kalıplardır. İyi bir tasarım deseni; yazmış olduğumuz kodları temiz, okunabilir kılıp sizden sonra gelecek olan kişilere daha kolay adapte olmayı sağlamalıdır.

Bu yazıda object oriented programming'in en çok tercih edilen design pattern'lerin den biri olan Creational pattern'ler grubundan Factory Pattern'i nedir ne değildir nasıl implemente edilir örnek proje ile inceleyeceğiz.

Factory Pattern

Gang of Four patternleri günümüz dünyasında en sıkı şekilde takip edilip en çok kullanılan ünlü tasarım desenleridir. Factory pattern'de bu 4 lü den biridir. Kısaca tanımı ; aynı abstract sınıf veya interface'den türeyen nesnelerin üretiminden sorumlu yapıdır. Bu pattern ile nesne yaratılma işini inheritance yoluyla client-side'dan ayırıp sub-classes'lara vermek amaçlanır.

Geliştirmekte olduğunuz uygulamaya yeni bir feature eklerken en az dokunuş ile client'ı bu duruma hiç sokmadan yapabilmek amaçlanır ve factory pattern de bu amaca yönelik olarak önerilen en önemli pattern'lerden birisidir.

Factory pattern 2 alt kategoriye ayrılabilir.

  1. Factory Method 
  2. Abstract Factory

Factory Method

Aynı interface'i veya abstract sınıfı implement etmiş etmiş factory nesnelerinin üretiminden sorumlu pattern dir.

Örnek bir case üzerinden ilerleyelim. Araç üretimi yapan bir fabrikamız olsun. Bu fabrika car, truck ve motorcycle üretebiliyor olsun. İlk olarak factory nesnelerimizin kullanacağı IVehicleFactory interface'ini ve car, truck, motorcycle nesnelerini oluşturalım.

    public interface IVehicle
    {
        void DisplayInfo();
    }

    public class Car : IVehicle
    {
        public void DisplayInfo()
        {
            Console.WriteLine("Car produced");
        }
    }

    public class Truck : IVehicle
    {
        public void DisplayInfo()
        {
            Console.WriteLine("Truck produced");
        }
    }

    public class Motorcycle : IVehicle
    {
        public void DisplayInfo()
        {
            Console.WriteLine("Motorcycle produced");
        }
    }

Yukarıdaki gibi nesneleri ve arayüzleri oluşturduktan sonra ismi VehicleFactory olan ve içerisinde geriye IVehicle döndüren ProduceVehicle adında bir sınıf tanımlayacağız. ProduceVehicle metodu VehicleType adında bir bir enum parametre olarak alacak. Bu enum'ı kullanarak factory metoduna üretmesini istediğimiz tip bilgisini geçeceğiz.

    public enum VehicleType
    {
        Car = 1,
        Truck = 2,
        Motorcycle = 3
    }

    public interface IVehicleFactory
    {
        IVehicle ProduceVehicle(VehicleType type);
    }

    public class VehicleFactory : IVehicleFactory
    {
        public IVehicle ProduceVehicle(VehicleType type)
        {
            IVehicle vehicle = null;
            switch (type)
            {
                case VehicleType.Car:
                    vehicle = new Car();
                    break;
                case VehicleType.Truck:
                    vehicle = new Truck();
                    break;
                case VehicleType.Motorcycle:
                    vehicle = new Motorcycle();
                    break;
            }
            return vehicle;
        }
    }


Factory metotlarımız da hazır artık üretime başlayabiliriz. Client dediğimiz kısım aslında tam da aşağıdaki kod parçaları oluyor Program.cs içerisinde aşağıdaki gibi üretmek istediğimiz türdeki aracı factory'e söyleyip üretebiliriz.

    class Program
    {
        static void Main(string[] args)
        {
            var vehicleFactory = new VehicleFactory();

            IVehicle vehicleCar = vehicleFactory.ProduceVehicle(VehicleType.Car);
            vehicleCar.DisplayInfo();

            IVehicle vehicleMotorcycle= vehicleFactory.ProduceVehicle(VehicleType.Motorcycle);
            vehicleMotorcycle.DisplayInfo();
        }
    }

Yazımızın başında da bahsettiğimiz gibi yapılabilecek değişikliklerden client'ı etkilemeden yapabilmek birinci önceliğimizdir. Bu örneğimizde araba üretimi yapmak için IVehicle interface'ini implement eden Car nesnesini kullandık ancak ilerde bir gün yine IVehicle interface'ini implement eden XCar adında bir nesne oluşturup üretim yaparken bu nesnyi kullanabiliriz ve bu durum client açısından hiç bir değişikliğe gidilmeden yapılabilmektedir.

Castle Windsor ile Exception Handling Interceptor (Dynamic Proxy)

Daha önceki IoC ve AOP yazılarında  çeşitli konulara değinerek örnek projeler üzerinde anlatımlarda bulunduk. Bu yazımızda da Castle Windsor'dan yararlanarak projelerimizde sıklıkla kullanacağımız bir ExceptionAspect veya Interceptor oluşturacağız. 

Interceptor'lar veya Dynamic Proxies Aspect Oriented Programming'in bir implementasyonudur. Bu bize oluşturduğu proxy ile metodu intercept ederek kendi kodlarımızın arasına inject etmemizi sağlar.

IL kodlarına baktığımızda aşağıdaki resimde olduğu gibi geliştirmiş olduğumuz kodları try catch finally blokları arasına alınır.

 

 

 

 

 

 

 

Daha önceki Castle Windsor Kullanarak Cache Interceptor Oluşturma yazısındaki örneğimiz üzerinden ilerleyelim. O yazıda il kodu alarak geriye ilçeleri dönen bir case üzerinden caching işlemi yapan aspect geliştirmiştik. Bu yazımızda da aynı örnek üzerinden ilerleyerek projemiz için bir ExceptionHandling aspect oluşturalım. 

İlk olarak ExceptionHandlingInterceptor adında aspect'imizi oluşturalım.

public class ExceptionHandlingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        try
        {
            invocation.Proceed();      
        }
        catch (Exception ex)
        {
            LogManager.Log("Exception in : " + invocation.Method.Name + " method." + ex);

            invocation.ReturnValue = new BaseResponse { IsSuccess = false };
        }
    }
}

Yukarıdaki kodu incelediğimizde özetle şunu söylüyor ;

  • Invoke edilecek metodu try-catch-finally bloğu arasına al
  • Invoke edilen metot içerisinde hata alırsan catch bloğuna gir ve önce alınan hatayı log'a yaz
  • Sonrasında BaseResponse modelini initialize ederek client'a clear bir response model dön.

Tabi ki de çok daha farklı ayrıntıları log'a yazmanız gerekir ancak sample olduğundan şimdilik bunları yazalım.

İkinci adım olarak yazmış olduğumuz Exception aspect'ini container'a register etmek var.

Castle Windsor Kullanarak Cache Interceptor Oluşturma örneğinde ServiceInstaller class'ını olduğu gibi alıp ExceptionHandlingInterceptor'ını register işlemimizi yapalım.

public class ServiceInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component.For<CacheInterceptor>().LifestyleSingleton());
        container.Register(Component.For<ExceptionHandlingInterceptor>().LifestyleSingleton());

        container.Register(Component.For(typeof(ILocationService))
                 .ImplementedBy(typeof(LocationService))
                 .Interceptors(typeof(CacheInterceptor)
                 .Interceptors(typeof(ExceptionHandlingInterceptor))));
    }
}

Yukarıda ki register işlemi şunu söylüyor; "ILocationService interface'inin implementasyonu LocationService class'ı dır. Bu implementasyona ait 2 adet Interceptor var "CacheInterceptor" ve "ExceptionHandlingInterceptor" adında ve bu interceptor'lar LifeStyleSingleton olarak container'a register edilmişlerdir."

Uygulamamız hazır diyebiliriz. LocationService içerisinde bulunan GetDistrictsByCityCode adlı metodumuzda test etmek için aşağıdaki gibi kendimiz bir exception throw edelim.

public class LocationService : ILocationService
{
    public GetDistrictsByCityCodeResponse GetDistrictsByCityCode(int cityCode)
    {
        throw new NullReferenceException();
    }
}

Projemizi run edip ilgili endpoint'e istekte bulunduğumuzda exception fırlatılıp Interceptor içerisinde bulunan catch bloğuna düşecektir ve loglama işlemini yaptıktan sonra client'a BaseResponse modelini dönecektir.

 

Peki bunları yaparak ne sağladık ? 

  1. Exception Handling işini eski usül projede yüzlerce try-catch bloğu oluşturmak yerine AOP'in faydalarından yararlanarak tek bir yerden yönetebilir duruma getirdik.
  2. Çok daha reusable olan ve başka yerlerde de kullanabileceğimiz bir yapı tasarladık.
  3. Uygulama exception fırlattığında client'a saçma sapan StackTrace mesajı yerine her response da aldığı BaseResponse modelini dönerek response'larımızı daha tutarlı bir hale getirdik.

Postsharp Kullanarak Metot Execution Sürelerini Hesaplama

Daha önceki aspect oriented yazılarımızda ne aspect oriented'ın ne olduğunu ve nasıl kullanıldığını çeşitli örneklerle anlatmıştık ve AOP yaklaşımı ile yapılabileceklerin sınırının olmadığından bahsetmiştik. Bu yazımızda da özellikle server-side geliştirme yapanların sıkça ihtiyaç duyduğu metot execution süresinin AOP yaklaşımı ile nasıl yapıldığından bahsedeceğiz. 

Yazılımda performans bildiğiniz üzre en önemli konuların başında gelir ve büyük çapta 1 den fazla client'ın consume ettiği bir projeniz var ise metot execution sürelerini bir yerlerde logluyor olmak bizler için şu gibi durumlarda hayat kurtarıcı bir rol üstlenir; client der ki "Abi sayfa 8 saniyede açılıyor bizde bir sıkıntı yok service'den geç geliyor....vs.", server-side'da geliştirme yapan arkadaşta der ki "Yok abi ben 0.2 saniyede response dönmüşüm bu bizden değil...vs.". Bu gibi durumlarda eğer siz service olarak projenizden eminseniz bu durumu kanıtlamak için en güzel çözüm metot execution sürelerini hesaplayıp delil olarak ilgili mercilere sunmak. Ya da bu case'i de geçelim bizler developerlar olarak kendimize "arkadaş bi proje yaptık ama işler nasıl gidiyor, yazılımsal olarak performansı yerinde mi.." vs. gibi soruları sorup cevaplarını da biliyor olmamız gerekir.

Bunun için AOP çözümlerinden biri olan Postsharp'dan yararlanacağız. Case şu şekilde olacak; Bir Api projemiz olsun ve MoneyTransfer adında eft-havale-virman işlemlerini yapan end-point geliştirelim ve bu end-point'e gelen request-response'ların execution sürelerini LoggingAspect kullanarak loglayalım.

*PostSharp kurulumu vs bilgileri için şu yazıyı inceleyebilirsiniz.

    [Serializable]
    public class LoggingAspect : OnMethodBoundaryAspect
    {
        private readonly Stopwatch timer = new Stopwatch();  

        public override void OnEntry(MethodExecutionArgs args)
        {
            timer.Start();
        }

        public override void OnExit(MethodExecutionArgs args)
        {
           var executionTime = timer.ElapsedMilliseconds;
            timer.Stop();
            var logMessage = "Method : " args.Method.Name +  " Execution Time : " + executionTime + " Millisecond";
            Console.Write(logMessage );
        }
    }

Sırada MoneyTransfer işlemi yapan metodu yazalım ve bu metot parametre olarak TransferRequest objesi alsın ve response olarak TransferResponse adında bir model dönsün.

        [LoggingAspect]
        public TransferResponse MoneyTransfer(TransferRequest request)
        {
            // back-end kodlarınız

            var resp = new TransferResponse
            {
                IsSuccess = true,
                Message = "Transfer İşleminiz Gerçekleşti."
            };
            return resp;
        }

        public class TransferRequest
        {
            public decimal Amount { get; set; }
            public string SenderIBAN { get; set; }
            public string ReceiverIBAN { get; set; }
        }

        public class TransferResponse
        {
            public bool IsSuccess { get; set; }
            public string Message { get; set; }
        }
    }

Hepsi bu kadar artık yazdığımız kodları test edebiliriz. TransferMoney metoduna request yolladığımızda metot scope'ları içerisindeki kodları run etmeden aspect'imiz içerisinde bulunan OnEntry metoduna girecektir ve burada timer'ı start ediyoruz. Sonrasında metot scope'ları içerisinde bulunan kodları çalıştırıp metottan çıktıktan sonra OnExit metoduna girip timer'ı stop edip devamında execution süresini millisecond cinsinden Console'a yazacaktır.

Metot execution süresi yukarıda da belirttiğim gibi önemli bir konudur ve ne nerde ne kadar süre harcayıp client'a gidiyor bunu ölçeklendirebiliyor olmak gerekir.

Not: Çok uzun süren execution sürelerinin çok büyük bir ihtimalle networksel sorunlardan dolayı kaynaklanabileceği ihtimalini de unutmamak gerekir.

Castle Windsor Kullanarak Cache Interceptor Oluşturma

Daha önceki yazılarımızda IoC nedir ve Castle Windsor Kullanarak Cache Interceptor Oluşturma konularına değinmiştik. Bu yazımızda ise yine Windsor kullanarak projelerimizde sıkça kullandığımız bir özellik olan Caching işlemini basitçe yapabilen bir interceptor oluşturacağız.

Bir Api projesi oluşturalım ve projemizde cache ile ilgili işlemlerin yönetildiği ICache isminde aşağıdaki gibi bir interface'imiz olsun ve arka planda kullandığınız herhangi bir cache yapısı olabilir (Redis, memory cache, vs.) bu interface'in implementasyonunu kullandığınız cache yapısına göre yapmış varsayalım.

public interface ICache : IDisposable
{
    object Get(string key);
 
    void Set(string key, object obj, DateTime expireDate);
 
    void Delete(string key);
 
    bool Exists(string key);
}

Şimdi ise projemize Castle'ı nuget'ten indirip kuralım ve sonrasında aşağıdaki gibi CacAttribute adında bir attribute tanımlayalım. Bu attribute'ü kullanarak cache'e atmak istediğimiz metotları bir nevi flag'lemiş olacağız.

    [AttributeUsage(AttributeTargets.Method)]
    public class CacheAttribute : Attribute
    {
        public int CacheDurationInSecond { get; private set; }

        public CacheAttribute(int cacheDurationInSecond = 300)
        {
            CacheDurationInSecond = cacheDurationInSecond;
        }
    }

Örnek olarak projemizde LocationController adında bir controller ve içerisinde GetDistrictsByCityCode adında int cityCode parametresine göre geriye o şehirde bulunan ilçelerin listesini dönen bir end-point tanımlayalım. Projemizde kullanacağımız modellerimizi aşağıdaki gibi oluşturalım.

        public class BaseResponse
        {
            public bool IsSuccess { get; set; }
        }

        public class GetDistrictsByCityCodeResponse : BaseResponse
        {
            public List<string> CityList { get; set; }
        }

Controller'ın kullanacağı ILocationService ve onun implementasyonunu aşağıdaki gibi tanımlayalım.

        public interface ILocationService
        {
            [Cache(300)]
            GetDistrictsByCityCodeResponse GetDistrictsByCityCode(int cityCode);
        }

        public class LocationService : ILocationService
        {
            public GetDistrictsByCityCodeResponse GetDistrictsByCityCode(int cityCode)
            {
                return new GetDistrictsByCityCodeResponse
                {
                    CityList = new List<string> {
                        "Alaçam","Asarcık","Atakum",
                        "Ayvacık", "Bafra", "Canik",
                        "Çarşamba","Havza", "İlkadım",
                        "Kavak","Ladik", "Ondokuzmayıs",
                        "Salıpazarı","Tekkeköy", "Terme",
                        "Vezirköprü", "Yakakent" },
                    IsSuccess = true
                };
            }
        }

Yukarıda görüldüğü üzre GetDistrictsByCityCode metodu üzerinde CacheAttribute'ünü tanımladık ve cache süresi olarak 300 saniye yani 5 dk set ettik. Şimdi ise LocationController'ı oluşturalım ve controller içerisinde tanımlayacağımız end-point ILocationService'i kullanarak GetDistrictsByCityCode metoduna ilgili isteği yapacak. ILocationService'i controller'da kullanabilmek içinde controller'ın constructor'ında seviyesinde ILocationService'i inject edeceğiz.

        public class LocationController : ApiController
        {
            private readonly ILocationService _locationService;
            public LocationController(ILocationService locationService)
            {
                _locationService = locationService;
            }
            [HttpGet]
            public HttpResponseMessage GetDistrictsByCityCode(int cityCode)
            {
                var response = _locationService.GetDistrictsByCityCode(cityCode);
                return Request.CreateResponse(response);
            }
        }

Metodumuz geriye BaseResponse'dan türemiş olan GetDistrictsByCityCodeResponse objesini dönecek ve bu objede bulunan IsSuccess parametresini kontrol ederek modeli cache'e atıp atmamaya karar vereceğiz. Bunu yapmamızın sebebi hatalı response'ları cache atmayı önlemek. LocationService içerisindeki metotta dummy olarak geriye Samsun ilinin ilçelerini return eden bir response oluşturduk ve projemizi deneme amaçlı bunu return edeceğiz.

Sırada cache işlemlerinin yapıldığı aspect'imizi tanımlama var. Aşağıda olduğu gibi CacheInterceptor adında aspect'imizi tanımlayalım.

    public class CacheInterceptor : IInterceptor
    {
        private readonly ICache _cacheProvider;
        private readonly object _lockObject = new object();
        public CacheInterceptor(ICache cacheProvider)
        {
            _cacheProvider = cacheProvider;
        }

        public void Intercept(IInvocation invocation)
        {
            //metot için tanımlı cache flag'i var mı kontrolü yapıldığı yer
            var cacheAttribute = invocation.Method.GetAttribute<CacheAttribute>();
            if (cacheAttribute == null)//eğer o metot cache işlemi uygulanmayacak bir metot ise process normal sürecinde devam ediyor
            {
                invocation.Proceed();
                return;
            }

            lock (_lockObject)
            {
               //eğer o metot cache işlemlerinin yapılması gereken bir metot ise ilk olarak dynamic olarak aşağıdaki gibi bir cacheKey oluşturuyoruz
                var cacheKey = string.Concat(invocation.TargetType.FullName, ".", invocation.Method.Name, "(", JsonConvert.SerializeObject(invocation.Arguments), ")");
                //bu key ile tanımlı bir cache objesi var mı kontrol ediyoruz
                var cachedObj = _cacheProvider.Get(cacheKey);
                if (cachedObj != null)//eğer var ise o objeyi alıp client'a return ediyoruz
                {
                    invocation.ReturnValue = cachedObj;
                }
                else
                {
                    //yok ise metottan çıktıktan sonra return edilen response'u 
                    invocation.Proceed();
                    var returnValue = invocation.ReturnValue;
                    var response = returnValue as BaseResponse;
                    if (response != null)
                    {
                        //eğer o response bizim beklediğimiz gibi BaseResponse'dan türeyen bir model ise o modeli alıyoruz ve  başarılı bir şekilde client'a dönülen bir response ise cache'e atıyoruz
                        if (response.IsSuccess)
                        {
                            var cacheExpireDate = DateTime.Now.AddSeconds(cacheAttribute.CacheDurationInSecond);
                            _cacheProvider.Set(cacheKey, invocation.ReturnValue, cacheExpireDate);
                        }
                    }
                }
            }
        }
    }

Yukarıdaki akışı anlatmak gerekirse;

  • İlk olarak ilgili metot üzerinde bir cache flag'i var mı yok mu ona bakıyoruz. Eğer metot üzerinde tanımlı CacheAttribute'ü var ise o metot bizim için cacheinterceptor tarafından cache işlemlerine tabi tutulur demek. Yoksa hiç cache'i karıştırmadan process devam eder.
  • CacheAttribute varsa sonraki adımda metot namespace + metot ismi + metot parametrelerinden oluşan bir dynamic cacheKey oluşturuluyor ve bu cacheKey ile cache provider'ınızda (redis, memory cache, vs.) daha önce tanımlanmış olup halen geçerliliği olan bir cache objesi var mı bu kontrol yapılıyor.
  • Eğer varsa cache de bulunan obje return ediliyor ve GetDistrictsByCityCode metoduna girmeden client'a cevap dönülüyor.
  • Eğer cache'de bulunan bir obje yoksa GetDistrictsByCityCode metoduna girip ilgili işlemleri yaptıktan sonra metottan çıkıyor. Sonrasında metodun return ettiği model BaseResponse'dan türeyen bir model ise ve IsSuccess'i true ise obje cache'e atılıyor ve daha sonra client'a dönülüyor.

Geriye son adım olarak da ilgili bağımlılıkları tanımlayıp CacheInterceptor'ını install etmek kalıyor.  Aşağıdaki gibi ilk olarak interceptor'ı install ediyoruz ve sonrasında kullanmak istediğimiz LocationService için register ediyoruz.

        public class ServiceInstaller : IWindsorInstaller
        {
            public void Install(IWindsorContainer container, IConfigurationStore store)
            {
                container.Register(Component.For<CacheInterceptor>().LifestyleSingleton());

                container.Register(Component.For(typeof(ILocationService))
                         .ImplementedBy(typeof(LocationService))
                         .Interceptors(typeof(CacheInterceptor)));
            }
        }

Installer'ı container'a tanımlamak için projemizde bulunan Global.asax içerisindeki Application_Start metodu içerisinde container'ı aşağıdaki gibi ayağa kaldırıyoruz.

protected void Application_Start()
{
    var container = new WindsorContainer();
    container.Install(new ServiceInstaller());
    container.Install(new WebApiControllerInstaller());
    GlobalConfiguration.Configuration.Services.Replace(
        typeof(IHttpControllerActivator),
        new ApiControllerActivator(container));
    GlobalConfiguration.Configure(WebApiConfig.Register);
}

Yukarıda bulunan WebApiControllerInstaller ve ApiControllerActivator daha önceki yazımızda  oluşturduğumuz installer ile birebir aynıdır. 

public class WebApiControllerInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly()
            .BasedOn<ApiController>()
            .LifestylePerWebRequest());
    }
}

public class ApiControllerActivator : IHttpControllerActivator
{
    private readonly IWindsorContainer _container;
 
    public ApiControllerActivator(IWindsorContainer container)
    {
        _container = container;
    }
 
    public IHttpController Create(
        HttpRequestMessage request,
        HttpControllerDescriptor controllerDescriptor,
        Type controllerType)
    {
        var controller =
            (IHttpController)this._container.Resolve(controllerType);
 
        request.RegisterForDispose(
            new Release(
                () => this._container.Release(controller)));
 
        return controller;
    }
 
    private class Release : IDisposable
    {
        private readonly Action _release;
 
        public Release(Action release)
        {
            _release = release;
        }
 
        public void Dispose()
        {
            _release();
        }
    }
}

Projemiz hazır ve artık yazmış olduğumuz end-point'i test edebiliriz. GetDistrictsByCityCode metoduna ilk gelen request'ten sonra return edilen response cache'e atılacaktır ve CacheAttribute tanımlarken verdiğiniz cacheDuration süresi içerisindeki ikinci request'i yaptığınızda GetDistrictsByCityCode scope'ları arasındaki işlemleri yapmadan ilgili response oluşturulan cacheKey ile bulup cache'den döndüğünü göreceksinizdir. 

Constructor Injection Hell (IoC)

Daha önceki IoC yazılarımızda nedir ne değildir den uzunca bahsedip hayatımıza ne gibi güzellikler getirdiğini anlatmıştık. Basitçe tekrar değinmek gerekirse uygulamadaki instance yönetiminden sorumlu ve bu instance'ları interface seviyesinde kullanıp runtime da container tarafından resolve edilip uygulamada kullanabiliyoruz.

Projenin İlk Günlerinde...

Kullanmak istediğimiz interface'leri aşağıdakine benzer bir şekilde constructor'a inject ediyoruz.

    public class FooController : ApiController
    {
        private readonly IUserService _userService;

        public FooController(IUserService userService)
        {
            _userService = userService;
        }
    }

1-2 Yıl Sonra...

Projenin ilk başlarında aslında ne de güzel duygularla geliştirme yaparken 1-2 sene sonra FooController'ın son durumunun aşağıdaki gibi bir hal aldığını görebiliyoruz.

    public class FooController : ApiController
    {
        private IUserService _userService;
        private ICustomerService _customerService;
        private IBankService _bankService;
        private IAccountService _accountService;
        private ITransferService _transferService;
        private IBranchService _branchService;
        private IEmployeeService _employeeService;
        private IMoneyService _moneyService;
        private IBasketService _basketService;
        private IAyService _ayService;
        private IVayService _vayService;
        private ILayService _layService;
        private IUserService _userService;
        private ICustomerService _customerService;
        private IBankService _bankService;
        private IAccountService _accountService;
        private ITransferService _transferService;
        private IBranchService _branchService;
        private IEmployeeService _employeeService;
        private IMoneyService _moneyService;
        private IBasketService _basketService;
        private IAyService _ayService;
        private IVayService _vayService;
        private ILayService _layService;
        private IUserService _userService;
        private ICustomerService _customerService;
        private IBankService _bankService;
        private IAccountService _accountService;
        private ITransferService _transferService;
        private IBranchService _branchService;
        private IEmployeeService _employeeService;
        private IMoneyService _moneyService;
        private IBasketService _basketService;
        private IAyService _ayService;
        private IVayService _vayService;
        private ILayService _layService;
        private IUserService _userService;
        private ICustomerService _customerService;
        private IBankService _bankService;
        private IAccountService _accountService;
        private ITransferService _transferService;
        private IBranchService _branchService;
        private IEmployeeService _employeeService;
        private IMoneyService _moneyService;
        private IBasketService _basketService;
        private IAyService _ayService;
        private IVayService _vayService;
        private ILayService _layService;

        public FooController(IUserService userService,
                             ICustomerService customerService,
                             IBankService bankService,
                             IAccountService accountService,
                             ITransferService transferService,
                             IBranchService branchService,
                             IEmployeeService employeeService,
                             IMoneyService moneyService,
                             IBasketService basketService,
                             IAyService ayService,
                             IVayService vayService,
                             ILayService layService,
                             IUserService userService,
                             ICustomerService customerService,
                             IBankService bankService,
                             IAccountService accountService,
                             ITransferService transferService,
                             IBranchService branchService,
                             IEmployeeService employeeService,
                             IMoneyService moneyService,
                             IBasketService basketService,
                             IAyService ayService,
                             IVayService vayService,
                             ILayService layService,
                             IUserService userService,
                             ICustomerService customerService,
                             IBankService bankService,
                             IAccountService accountService,
                             ITransferService transferService,
                             IBranchService branchService,
                             IEmployeeService employeeService,
                             IMoneyService moneyService,
                             IBasketService basketService,
                             IAyService ayService,
                             IVayService vayService,
                             ILayService layService,
                             IUserService userService,
                             ICustomerService customerService,
                             IBankService bankService,
                             IAccountService accountService,
                             ITransferService transferService,
                             IBranchService branchService,
                             IEmployeeService employeeService,
                             IMoneyService moneyService,
                             IBasketService basketService,
                             IAyService ayService,
                             IVayService vayService,
                             ILayService layService)
        {
            _userService = userService;
            _customerService = customerService;
            _bankService = bankService;
            _accountService = accountService;
            _transferService = transferService;
            _branchService = branchService;
            _employeeService = employeeService;
            _moneyService = moneyService;
            _basketService = basketService;
            _ayService = ayService;
            _vayService = vayService;
            _userService = userService;
            _customerService = customerService;
            _bankService = bankService;
            _accountService = accountService;
            _transferService = transferService;
            _branchService = branchService;
            _employeeService = employeeService;
            _moneyService = moneyService;
            _basketService = basketService;
            _ayService = ayService;
            _vayService = vayService;
            _userService = userService;
            _customerService = customerService;
            _bankService = bankService;
            _accountService = accountService;
            _transferService = transferService;
            _branchService = branchService;
            _employeeService = employeeService;
            _moneyService = moneyService;
            _basketService = basketService;
            _ayService = ayService;
            _vayService = vayService;
            _userService = userService;
            _customerService = customerService;
            _bankService = bankService;
            _accountService = accountService;
            _transferService = transferService;
            _branchService = branchService;
            _employeeService = employeeService;
            _moneyService = moneyService;
            _basketService = basketService;
            _ayService = ayService;
            _vayService = vayService;
        }
    }

Not : Yukarıda initialize edilen interface'ler örnek olarak kullanıldı. Kod bloğunu kendi projenize copy paste yaptığınızda bu interface'ler projede oluşturulmadığından hata alacaksınızdır. Sadece constructor'a inject edilen nesnelerin fazlalığından bahsetmek adına yukarıdaki gibi kullanım uygulanmıştır.

Kullanılan interface sayısı bir elin parmaklarıyla sınırlı olduğunda constructor injection uygulamak oldukça güzel duruyor ancak çok daha büyük bir projede aylar yıllar geçtikçe yukarıdaki gibi bir constructor injection hell dediğimiz durum ortaya çıkabiliyor.

Bu durumu sadece Controller üzerinde değilde constructor injection uyguladığımız her hangi bir yer Service class'ı, Repository olabilir.  

TDD uyguladığımızı da düşünürsek bu controller'ın birde test class'ları olacaktır. Aynı injection muhabbeti ordada önümüzde çıkacaktır.

Çözüm Olarak..

Aslında bu durumu bir sorun olarak da görmeyebiliriz de. Sonuçta "kod çalışıyor abi tıkır tıkır devam.." diyebiliriz ancak bunun bir sıkıntı olduğunu geliştirme yaparken anlıyorsunuz. Alt alta veya yan yana satırlarca kod ortaya çıktığında kodu okumak istemiyorsunuz ve bir süre sonra yeni bir interface enjecte etmeye kalktığınızda Vs kasmaya başladığını anlıyorsunuz.

Peki ne yapabiliriz bunun için ?

Projemizde container olarak Castle Windsor kullandığımızdan yola çıkarak windsor'ın görevi bize instance yönetimini sağlamaksa bizde Controller'larda kullanacağımız interface'leri implementasyonu olmayan bir IServiceFactory adında bir interface'de tanımlayıp container'a Typed Factory Facility servisini register edip sonrasında interface'imizi register edeceğiz.

    public interface IServiceFactory
    {
        IUserService CreateUserService();
        void Release(IUserService userService);

        ICustomerService CreateCustomerService();
        void Release(ICustomerService customerService);

        IBankService CreateBankService();
        void Release(IBankService bankService);

        IAccountService CreateAccountService();
        void Release(IAccountService accountService);

        ITransferService CreateTransferService();
        void Release(ITransferService transferService);

        IBranchService CreateBranchService();
        void Release(IBranchService branchService);

        IEmployeeService CreateEmployeeService();
        void Release(IEmployeeService employeeService);

        IMoneyService CreateMoneyService();
        void Release(IMoneyService moneyService);

        IBasketService CreateBasketService();
        void Release(IBasketService basketService);

        IAyService CreateAyService();
        void Release(IAyService ayService);

        IVayService CreateVayService();
        void Release(IVayService ayService);

        ILayService CreateLayService();
        void Release(ILayService layService);
    }

Bu interface bir dummyService interface'i ve tek görevi bize istediğimiz service'in instance'ını container'dan resolve ederek vermek.

Sırada son adım olarak Factory interface'ini register etmek. Yukarıda bahsettiğimiz gibi container'a Typed Factory Facility servisini register edip sonrasında IServiceFactory interface'ini aşağıdaki gibi register edeceğiz. 

Container.AddFacility<TypedFactoryFacility>();
Container.Register(Component.For<IServiceFactory>().AsFactory());

Geliştirmemiz hazır. Artık FooController'a gidip IServiceFactory interface'ini constructor injection yöntemiyle inject edebiliriz.

    public class FooController : ApiController
    {
        private IServiceFactory _serviceFactory;

        public FooController(IServiceFactory serviceFactory)
        {
            _serviceFactory = serviceFactory;
        }
    }

Görüldüğü üzre satırlarca uzayan kodlardan kurtulduk ve artık tek bir interface üzerinden ihtiyaç duyduğumuz service'in Create() metodunu çağ��rarak service içerisinde tanım olan metotları vs. kullanabiliriz.

Örnek olarak;

    public class FooController : ApiController
    {
        private IServiceFactory _serviceFactory;

        public FooController(IServiceFactory serviceFactory)
        {
            _serviceFactory = serviceFactory;
        }


        [HttpGet]
        public HttpResponseMessage GetCustomers()
        {
            var response = _serviceFactory.CreateCustomerService().GetAllCustomers;
            return Request.CreateResponse(response);
        }
    }

Geliştirmemiz bu kadar. Büyük ölçekte projelerde çalışanların büyük bir kısmı benzer bir sorunla karşılaşmıştır veya karşılaşacaktır. Çözüm olarak farklı alternatiflerde mevcut bu sadece onlardan bir tanesiydi. 

Typed Factory Facility ile ilgili daha geniş bilgiyi bu linkten bulabilirsiniz.

Local Functions C# 7

Local Functions bizlere method scope'ları içerisinde function'lar tanımlamamızı sağlar ve o fonction'ı tıpkı o scope aralığında tanımlanmış bir değişken gibi kullanabiliriz. 

Local Functions C# 7.0 ile gelen oldukça özel tabir edilen feature'lar dan biridir. Local Function tanımladığımız yer bir method, property veya bir constructor olabilir ve IL kodlarına baktığımızda local function'lar compiler tarafından private metotlara dönüştürülürler.

Geliştirme yaparken bir UpdateModel adında bir metodunuzun olduğunu varsayalım ve akış gereği bu metodun içerisinde küçük operasyonel işler yapan kod parçacıkları olduğunu düşünelim. Bu kod parçacıkları sadece o metot tarafından kullanılacağından o metot dışında ayrı bir metot olarak yazma gereği duymayız. Ancak o metoda baktığımızda da belki 200 satırdan fazla koddan oluşmuş ve sürekli kendi içerisinde tekrar eden kodlar içeren bir metot haline gelmiş olabilir. Local function tam da bunun gibi durumlarda karşımıza çıkıyor. O küçük kod parçacıklarını UpdateModel metodu içerisinden ayrı küçük birer metot olarak tanımlayabiliyoruz.

Örnek olarak aşağıdaki gibi küçük bir toplama işlemi yapalım.

public class Program
{
	public static void Main()
	{ 
		int Topla(int x, int y, int z)
        {
           return x + y + z;
		}

		Console.WriteLine(Topla(3, 4, 6));
	}
}

Projeyi çalıştırdığınızda 3,4 ve 6 nın toplamı olan 13 değerini ekranda görüyor olacaksınız.

Local function'lar aynı zamanda tanımlandığı metot içerisindeki değişkenlere de access özelliği taşır. Örnek olarak aşağıdaki gibi bir local function tanımlayabiliriz.

	public static void Main()
	{ 
		int Topla(int x, int y, int z)
        {
           return x + y + z + t;
		}

		int t = 8;
		
		Console.WriteLine(Topla(3, 4, 6));
	}

Local Function'lar kısaca bu şekilde. Küçük bir feature gibi görünse de doğru yerde doğru zamanda oldukça faydalı olacakları kesindir :)

C# Compare Two Objects and Get Differences

Db de kayıtlı bulunan bir nesne için Update işlemi yaparken bazen öyle denk gelir ki sizden sadece ilgili objede değişen alanların gönderilmesi veya değişen alan var mı yok mu diye bir takım can sıkıcı validasyonlar yapmanız istenir ki hele bir de işin içine javascript girerse yemede yanında yat. İşte bu gibi durumlar için gidip sürekli olarak mevcut objedeki alanla yeni değer compare edilip değişmiş mi değişmemiş mi diye bir sürü logic yazmayı tabii ki de pek istemeyiz.

Örnek bir case üzerinden anlatmak gerekirse; aşağıdaki gibi User diye bir modeliniz ve sizden UpdateUser adından bir end-point yazmanız isteniyor ve analizde objede bulunan hangi alan kullanıcı tarafından değiştirilmiş bunlarla ilgili log tutmanız veya bilgilendirme emaili atmanız gerektiği yazılmış.

    public class User
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string EMail { get; set; }
        public DateTime BirthDate { get; set; }
        public int ZipCode { get; set; }
        public bool IsPublic { get; set; }
    }

Gidip db de kayıtlı bulunan objedeki property ile kullanıcıdan aldığınız değeri teker teker compare etmek istemeyiz elbette  if(dbUserModel.FirstName == newUserModel.FirstName) . Bu gibi bir durum için Reflection'dan faydalanabiliriz. Aşağıdaki gibi bir MemberComparisonResult adında bir model tanımlayalım ve generic tanımlayacağımız CompareObjectsAndGetDifferences  adından ki metottan bu modeli döndürelim.

    public class MemberComparisonResult
    {
        public string Name { get; }
        public object FirstValue { get; }
        public object SecondValue { get; }

        public MemberComparisonResult(string name, object firstValue, object secondValue)
        {
            Name = name;
            FirstValue = firstValue;
            SecondValue = secondValue;
        }

        public override string ToString()
        {
            return Name + " : " + FirstValue.ToString() + (FirstValue.Equals(SecondValue) ? " == " : " != ") + SecondValue.ToString();
        }
    }

Şimdi ise CompareObjectsAndGetDifferences adındaki metodumuzu aşağıdaki yazalım.

        public static List<MemberComparisonResult> CompareObjectsAndGetDifferences<T>(T firstObj, T secondObj)
        {
            var differenceInfoList = new List<MemberComparisonResult>();

            foreach (var member in typeof(T).GetMembers())
            {
                if (member.MemberType == MemberTypes.Property)
                {
                    var property = (PropertyInfo)member;
                    if (property.CanRead && property.GetGetMethod().GetParameters().Length == 0)
                    {
                        var xValue = property.GetValue(firstObj, null);
                        var yValue = property.GetValue(secondObj, null);
                        if (!object.Equals(xValue, yValue))
                            differenceInfoList.Add(new MemberComparisonResult(property.Name, xValue, yValue));
                    }
                    else
                        continue;
                }
            }
            return differenceInfoList;
        }
    }

Son olarak da yazdığımız metodu aşağıdaki gibi bir ConsoleApplication'da test edelim.

        static void Main(string[] args)
        {
            var existingUser = new User
            {
                FirstName = "Mestan",
                LastName = "Tosuner",
                BirthDate = DateTime.Today.AddYears(23),
                EMail = "mestan@mestanali.com",
                ZipCode = 34000,
                IsPublic = false
            };

            var existingUserWithNewValue = new User
            {
                FirstName = "Mestan Ali",
                LastName = "Tosuner",
                BirthDate = DateTime.Today.AddYears(23),
                EMail = "mestan@mestanali.com",
                ZipCode = 55400,
                IsPublic = true
            };

            var changes = CompareObjectsAndGetDifferences(existingUser, existingUserWithNewValue);
            foreach (var item in changes)
            {
                Console.WriteLine(item.ToString());
            }
        }

Projenizi çalıştırdıktan sonra ekran çıktısı aşağıdaki gibi olacaktır.

Gördüğünüz gibi existingUserWithNewValue ismindeki modeldeki değerler kullanıcıdan yeni alınan değerlere sahip. Yukarıdaki ekran görüntüsünde de bize FirstName, ZipCode ve IsPublic alanlarının güncellendiğini söylemekte ve ilk değerleri ile ikinci değerlerini ekrana display etmekte.

NInject Nedir ? NInject Kullanarak Web Api İçin Dependency İnjection

Daha önceki IoC yazılarımızda Castle Windsor dan bahsedip örnekler üzerinden kütüphaneyi incelemiştik. Bu yazımızda ise çokça yaygın olarak kullanılan IoC container'lar dan biri olan NInject'i Web APi üzerinde inceleyeceğiz.

Ninject oldukça popüler IoC container'lar dan biri olup bağımlılıkları enjekte etmede kullanılan open source bir kütüphanedir. Dependency injection bizlere loosely coupled dediğimiz birbirlerine gevşek bağlı ve daha kolay test edilebilir geliştirmeler yapmamızı sağlayan bir design pattern dir. IoC ise belli özelliklere sahip ve birbirlerine bağımlı nesnelerin işlevlerini gerçekleştirmek için ihtiyacı olan instance'ları kendilerinin değilde bir IoC container tarafından yönetilmesini söyler.

Bu yazımızda ise Web Api üzerinde Ninject implementasyonu nasıl yapılır bunu inceliyor olacağız.

İlk olarak VS'da bir tane Web Api projesi oluşturalım ve proje referanslarına nuget üzerinden Ninject.Web.WebApi.WebHost'u aşağıdaki gibi bulup install edelim.

Kurulum bittikten sonra projede bulunan App_Start klasörü içerisinde NinjectWebCommon adında otomatik olarak bir class oluşturulduğunu göreceksiniz. Bu class içerisinde ilgili install ve register işlemlerini yapacağız. Class içerisinde baktığınızda oto-generated olan bir çok kod bloğu var ancak biz şimdilik sadece   RegisterServices() metodu ile haşır neşir olacağız.

        /// <summary>
        /// Load your modules or register your services here!
        /// </summary>
        /// <param name="kernel">The kernel.</param>
        private static void RegisterServices(IKernel kernel)
        {

        }  

Register etmek istediğimiz bağımlılıkları bu metot içerisine tanımlayacağız.

Öncelikle UserController adında örnek bir controller oluşturalım ve bu controller içerisinde tanımlı IUserService intercase'ini contructor injection yöntemi ile inject edelim.

    public class UserController : ApiController
    {
        private readonly IUserService _userService;
        public UserController(IUserService userService)
        {
            _userService = userService;
        }

        [HttpGet]
        public HttpResponseMessage GetUserFullNames()
        {
            var response = _userService.GetUserFullNames();

            return Request.CreateResponse(response);
        }
    }

Controller içerisinde kullandığımız IUserService interface'i ve onun implemantasyonunu aşağıdaki gibi oluşturalım.

    public interface IUserService
    {
        List<string> GetUserFullNames();
    }

    public class UserService : IUserService
    {
        public List<string> GetUserFullNames()
        {
            return new List<string>
                                  { "Olcay Şahan",
                                    "Anderson Talisca",
                                    "Oğuzhan Özyakup",
                                    "Ricardo Quaresma",
                                    "Cenk Tosun" };
        }
    }

UserController içerisindeki GetUserFullNames metodu HttpGet isteği alarak geriye UserService içerisinde bulunan GetUserFullNames metodunun return ettiği List of string'i dönecektir.

Şimdi sırada bağımlılıkları inject etmek var. Yukarıda NinjectWebCommon class'ı içerisinde bulunan RegisterServices metodunda IUserService'i register edeceğiz. 

        private static void RegisterServices(IKernel kernel)
        {
            kernel.Bind<IUserService>().To<UserService>();
        }

Yukarıda görüldüğü üzre implementasyon oldukça basit ve projemiz hazır durumda. UserController içerisindeki metoda Postman kullanarak aşağıdaki gibi request atıp sonucu görelim.

 

Görüldüğü üzre Ninject controller'ın constructor'ında IUserService'i için bize UserService'ini resolve etti ve herhangi bir yeni instance oluşturmadan kolayca controller'ı kullanabildik.

Ninject implementasyonu diğer container'lara göre daha basittir. Sonraki yazılarımızda Ninject ile ilgili örneklerimize devam edeceğiz.