Caner Tosuner

Leave your code better than you found it

Castle Windsor Kullanarak Logging Interceptor Oluşturma

Daha önceki Aspect Oriented yazılarında interceptor'lardan bahsetmiştik ve biz developer'lar için bulunmaz bir nimet olduğunu söylemiştik. Server-Side bir projede olmazsa olmaz özelliklerin başında gelen Logging, Exception Handling, Caching vs gibi özellikleri çok basit küçük interceptor'lar yazarak uygulamamıza bu özellikleri kazandırabiliriz. Daha önceki yazılarımızda bu ihtiyaçları karşılayabilmek için .Net tarafında oldukça entegrasyonu basit olan Postsharp kütüphanesinden faydalanmıştık. Bugün ki yazımızda ise IoC container'lardan Castle Windsor'ı kullanarak uygulamamız için bir Logging intercepter'ı geliştireceğiz. 

Örneğimiz şu şekilde; Email göndermek için kullanılan basit bir Service projemiz olsun ve parametre olarak Address ve html olarak Content alsın. İlk olarak VS de EmailSender adında bir Api projesi oluşturalım ve projemizin referanslarına Nuget üzerinden Castle Windsor'ın paketlerini indirip kuralım.

Sonrasında SendEmailRequest adında request modelimizi tanımlayalım

    public class SendEmailRequest
    {
        public string Address { get; set; }
        public string Content { get; set; }
    }

Email gönderme işlemini yapacak olan service interface'imiz ve onun impl. class'ını aşağıdaki gibi oluşturalım

    public interface IEmailService
    {
        bool Send(SendEmailRequest reqModel);
    }
    public class EmailService : IEmailService
    {
        public bool Send(SendEmailRequest reqModel)
        {
            //todo return true dedik ancak bu kısımda email göndermek için kullandığınız kodları yazmalıyız. 
            return true;
        }
    }

Email gönderme servisimiz artık kullanıma hazır. Artık bu service'i Api ile dışarıya açma zamanı. Projemize CommunicationController adında bir controller ve bu controller içerisinde HttpPost kabul eden SendEmail adında bir endpoint tanımlayalım.

    public class CommunicationController : ApiController
    {
        private readonly IEmailService _emailService;

        public CommunicationController(IEmailService emailService)
        {
            _emailService = emailService;
        }

        [HttpPost]
        public HttpResponseMessage SendEmail(SendEmailRequest reqModel)
        {
            var result = _emailService.Send(reqModel);
            if (!result)
            {
                return Request.CreateResponse(HttpStatusCode.NotFound);
            }
            return Request.CreateResponse(result);
        }
    }

Yukarıda görüldüğü üzre kullanacağımız IEmailService'ini Castle kullanarak EmailService'ine inject edeceğiz ve controller seviyesinde bu service interface'ini kullanarak email gönderme işlemini yapacağız.

Şimdi sırada LoggingInterceptor'ımızı oluşturma var. Bu interceptor ile log kayıtlarına MethodName, request ise aldığı parametreler ve response için return edilen değer bilgilerini atacağız. 

    public class LoggingInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            var serializer = new JavaScriptSerializer();
            var parametersJson = serializer.Serialize(invocation.Arguments);

            System.Diagnostics.Debug.WriteLine("Request of " + invocation.Method.Name + " is " + parametersJson);

            invocation.Proceed();

            var returnValueJson = serializer.Serialize(invocation.ReturnValue);

            System.Diagnostics.Debug.WriteLine("Response of " + invocation.Method.Name + " is: " + invocation.ReturnValue);
        }
    }

Logları şimdilik sadece Output Window'a yazdırdım ancak gerçek hayatta tabikide NLog vs gibi bir kütüphane kullanıyor olmamızda fayda var. 

Interceptor'ımızda hazır olduğuna göre artık IoC Initialize tarafına geçip gerekli register işlemlerimizi yapabiliriz. ServiceInstaller adında aşağıdaki gibi bir class oluşturalım ve içerisinde service'imizi ve interceptor'ımızı register edelim.

    public class ServiceInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(Component.For(typeof(IEmailService))
                     .ImplementedBy(typeof(EmailService))
                     .Interceptors(typeof(LoggingInterceptor)));
        }
    }

EmailSender servisini ve interceptor'ı register eden installer'ı tanımladık. Şimdi ise Web Api projemizin controller'ını register eden installer'ı oluşturalım.

    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();
            }
        }
    }

Son adım olarak ise oluşturduğumuz bu installer'ları container'a Install edeceğiz. Bunun için projemizde bulunan Global.asax içerisinde bulunan Application_Start metodu aşağıdaki gibi olacak.

        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);
        }

Hepsi bu kadardı :) 

Şimdi yazdığımız kodları test edelim. Postman kullanarak controller'da bulunan endpoint'e aşağıdaki gibi bir istek gönderip Output Window dan neler yazdırdığına bir bakalım.

 

Request sonrasında Interceptor araya girerek loglama işlemini aşağıdaki görselde olduğu gibi yapmakta.

Örneğimiz şimdilik burada bitiyor, sizlerde projeleriniz için bir çile haline gelebilme potansiyeli olan Loglama konusunu interceptor kullanarak son derece basit ve reusable hale getirebilirsiniz.

Sonraki yazılarımızda Interceptor kullanarak daha başka neler yapabiliriz fırsat buldukça inceleyeceğiz.

IoC Inversion of Control nedir ?

IoC den önce gerilere gidip Dependency Injection'ın tanımınada değinelim. Dependency Injection kısaca "bağımlılıkların  loose coupled yani gevşek bağlı bir şekilde dışarıdan enjecte edilmesi" şeklinde tanımlayabiliriz.

Inversion of Control (IoC) ise bir yazılım tasarım prensibidir ve basit tabiriyle nesnelerin uygulama boyunca ki yaşam döngüsünden sorumludur diyebiliriz. Uygulama içerisinde kullanılan objelerin instance'larının yönetimi sağlar ve bağımlılığı en aza indirgemeyi amaçlar. 

Container program içerisinde request edilen nesneleri abstraction'lara bağlı tutarak otomatik olarak oluşturan ve bağımlılıklarını inject eden bir framework diyebiliriz. Oluşturmuş olduğu bu nesneleri kendi içerisinde yönetimini yaparak tekrardan ihtiyaç duyulduğunda yeni bir instance oluşturmak yerine mevcut olan nesneyi atar.

IoC daha kolay test edilebilir loose coupling dediğimiz gevşek bağlı ve reusable bir yazılım desene oluşturmamızı sağlar.

IoC ilk başlarda implementasyonundan dolayı zor ve karmaşık gibi görünsede geliştirme yaptıkça ve sağladığı kolaylıkları fark ettikçe hayran kalınacak bir yazılım tasarım prensibidir. Basitçe işleyişini anlatmak gerekirse; soyut tiplerin hangi somut tipler tarafından register edildiği bilgisini tutar. Uygulama içerisinde container'dan abstract bir nesne talebinde bulursunuz ve size register bilgisinde tanımlı olan concrete type'ın instance'ını oluşturup verir. Bir tür object factory olarak düşünebilirsiniz.

IoC için kullanılabilecek çeşitli kütüphaneler bulunmakta. Bunlardan en popüler olanlarını ;

şeklinde sıralayabiliriz. Bu kütüphaneleri kullanmayıp kendi IoC infrastructure'ınızıda yaratabilirsiniz ancak instance yönetimi dışında bu kütüphanelerin sağladığı aspect oriented özellikleri de hayli önemli bir diğer özelliktir. 

Framework ler arasından en performanslı olan hangisi sorusunu soracak olursak internette araştırırken bir benchmark testine denk geldim ve aşağıdaki koşullar sağlanarak yapılan benchmark sonucuna göz atalım.

Test Verisi

  • 8 levels of depth for the dependency tree
  • 100 types per level
  • Between 0 and 8 dependencies for each type (excluding level 0)
  • 1 in 5 types are registered as singleton (20%)

 

Sonuç

Container 2012 Version 2012 Time Elapsed (Sec) 2014 Version Time Elapsed (Sec)
Ninject 3.0.1.10 31.29 3.2.2.0 30.84
StructureMap 2.6.2 1.98 3.1.4.143 1.95
Autofac 2.6.3.862 5.56 3.5.2 5.19
Castle Windsor 3.1.0 5.47 3.3.0 5.59
Unity 2.1.505.2 7.76 3.5.1404.0 3.71
SimpleInjector 1.5.0.12199 34.43 2.5.2 48.01
Dynamo 3.0.0.1 Fail 3.0.2 Fail
Hiro 1.0.2 Fail 1.0.2 Fail

 

Görüldüğü üzre StructureMap bütün 800 tipi register ve resolve etmede en hızlı olan ancak en çok kullanılan olarak bakacak olursak Castle Windsor galip geliyor. Unity'nin de son sürümüyle birlikte bugün itibariyle en hızlı olan IoC framework'ü olduğu söylenmekte.

IoC ile ilgili bugünlük bu kadar diyelim gelecek yazılarımızda seçtiğimiz bir IoC framework'ünü kullanarak örnek projeler geliştiriyor olacağız.

Tuples Nedir - C# 7.0

Tuple custom bir class oluşturmadan üzerinde birden fazla specific değer tutabilen bir veri yapısıdır. Diğer bir değişle; geriye birden fazla parametre döndürmek istediğimiz bir metot var diyelim ve bu parametreler için normalde gidip custom bir nesne oluşturup sonra gidip bu nesneyi initialize edip property'lerini set'leyip metottan return ederdik veya bu bahsettiğimizi yeni bir object tanımlamadan out parametresini kullanarak da geriye döndürebilirdik ancak bir çok kişiye göre hem biraz maliyetli olarak kabul edilir hemde out async metotlarda kullanılamamaktadır. Bunun yerine daha basit ve anlaşılır olan tuple kullanarak çok rahat bir şekilde metottan geriye birden fazla parametre return edebiliyoruz.

Tuple ilk olarak .net 4.0 ile hayatımıza girdi. C#7.O ile birlikte kullanım olarak çok daha basit bir hale getirilmiş ve sanki ilerleyen günlerde projelerimizde biraz daka sık kullanacağız gibi duruyor. 

Tuple System.ValueTuple namespace'i altında yer almakta ve ilgili namespace'i nuget üzerinden bulup projemize indirelim.

Örnek olarak aşağıdaki gibi 3 tane integer değer alan ToplamlariCarpimlari adında Tuple özelliği olan bir metot tanımlayalım ve bu metot geriye ilk değer olarak toplamlarını ikinci değer olarak bu sayıların çarpımlarından elde dilen sayıyı return ediyor olsun.

	public static void Main()
	{
		var sonuc = ToplamlariCarpimlari(2,5,7);
		
		Console.WriteLine(sonuc.toplami);
		Console.WriteLine(sonuc.carpimi);
	}
	
	public static (int toplami, int carpimi) ToplamlariCarpimlari (int x,int y,int z)
	{
	   var t = x + y + z;
	   var c = x * y *z;
		
	   return (t,c);
	}

Yukarıda olduğu gibi main function içerisinde metodumuzu çağırdıktan sonra return ettiği değeri sanki içerisinde toplami, carpimi adında iki property barındıran bir custom nesneymiş gibi rahatça kullanabiliyoruz ve intellisense de direkt olarak nokta "." operatörü kullanarak bu return değerlerini algılayabilmekte.

Tuple kısaca bu şekilde. Basit bir özellik gibi görünsede bizleri fazladan nesneler oluşturmaktan ve async metotlar out parametresini kullanamıyorken çok rahat bir şekilde yukarıdaki metodu async olarak yazmamıza olanak sağlamakta.

Web Api Projelerine Swagger Ekleme

Server-side bir geliştirme yapıyorsanız ve yazmış olduğunuz end-point'ler farklı client'lar tarafından kullanılacaksa api da bulunan end-point'lerin kullanımını açıklayıp request-response örneklerini içeren bir döküman yazmak bizler için kaçınılmaz bir iş.

Asp.Net Web Api mimarları aslında bu durum için Help Page ile bir çözüm sunmaya çalışmışlar ancak tam anlamıyla yeterli olamamış. Yeni bir empty olmayan web api projesi oluşturduğunuzda nuget üzerinde Microsoft ASP.NET Web Api Help Page projenize yüklü olarak gelir ve browser üzerinden adres kısmına {IIS de ki uygulama ismi}/help diyerek web api için hazırlanmış olan help page dökümanına ulaşabiliriz ve ekran görüntüsü aşağıdaki gibidir.

Ancak bu döküman bize yazının başında bahsettiğimiz örnek request atıp response alabilmemizi sağlamamakta. Yani bir nevi api'ı gerçek veya fake datalarla test etmemize olanak sağlamamakta. Help page kabaca; yazılan controller'lar da bulunan end-point'ler ve bu end-point'lerin request response modellerini listelemekte.

 

Swagger

Help Page'in hem yapabildiklerini yapan hemde yapamadıklarının fazlasını yapabilen bir tool olan swagger'dan bahsedeceğiz. Swagger.io tarafından şekilde tanımlanmıştır;

"Swagger is a simple yet powerful representation of your RESTful API. With the largest ecosystem of API tooling on the planet, thousands of developers are supporting Swagger in almost every modern programming language and deployment environment. With a Swagger-enabled API, you get interactive documentation, client SDK generation and discoverability."

-swagger.io

Swagger yazılım dünyası tarafından oldukça büyük çapta kabul görmüş yaygın olarak kullanılan bir dynamic döküman oluşturma tool'u dur. .Net tarafı için entegrasyonu oldukça basittir. 

Not: Swagger'ı projenize entegre ettikten sonra hep page'i de kullanmaya devam edebiliyorsunuz yani biri diğerinin yerini almıyor.

Swagger Kurulumu

Projemize swagger eklemek için open source olarak geliştirilen Swashbuckle adındaki kütüphaneyi projemizde Nuget Package Manager Console kullanarak indirip kuracağız.

PM> Install-Package Swashbuckle

Kurulum işlemi bittikten sonra solution da bulunan App_Start klasörünü açarak içerisine swagger configuration işlemleri için SwaggerConfig.cs adında bir class eklendiğini göreceğiz.

Configuring Swagger

SwaggerConfig.cs içerisi default olarak aşağıdaki gibidir.

    public class SwaggerConfig
    {
        public static void Register()
        {
            var thisAssembly = typeof(SwaggerConfig).Assembly;

            GlobalConfiguration.Configuration
                .EnableSwagger(c =>
                    {
                        c.SingleApiVersion("v1", "WebApplication1");
                    })
                .EnableSwaggerUi();
        }
    }

Projenizi run ettiğinizde browser üzerinden Swagger Ui sayfasına {IIS de ki uygulama ismi}/swagger şeklinde ulaşabilirsiniz ve sayfa default olarak aşağıdaki gibidir.

Yukarıda da görüldüğü gibi projemizde controller'lar içerisinde tanımlı end-point'ler, Http Request türleri, aldıkları parametreler vs gibi bilgiler yer almaktadır.

Örnek olarak POST /api/Values metodunu deneyelim. Metot isminin üzerine tıkladığımızda altta bir view expand olur ve burada request olarak göndereceğimiz parametreleri yazıp response'u alabiliriz. 

Yukarıdaki ekran görüntüsünde kısaca Values metodu string bir parametre alıyor ve geriye string bir response dönüyor. Request parametresini yazdıktan sonra Try it out butonuna tıkladığımızda aşağıdaki gibi bir ekranla karşılaşıyoruz.

 

Özetle

Biz yazılımcılar için çile haline gelen request response örnek kodları açıklama döküman vs gibi konuları swagger ile gayet basit ve kullanışlı bir hale getirebiliriz. Swagger ile ilgili daha bir çok configuration bulunmakta. VS üzerinden XML dosya generate ederek kodlarınızın üzerinde bulunan yorumlardan yola çıkarak api dökümanı oluşturma gibi bir çok özelliği bulunmakta. Ayrıntılı bilgi için Swagger.io Swashbuckle ile ilgili güncel ve daha ayrıntılı bilgileri bu linkten takip edebilirsiniz.

 

Not: Yukarıda basit anlamıyla swagger'ı anlatmaya çalıştım ancak yazının başında da belirttiğim gibi swagger.config dosyasını doğru yorumlayabildikten sonra daha bir çok özelliğini keşfedebilirsiniz. 

Repository Pattern CRUD İşlemleri Dışında Bulunan Specific Metotlar İçin Mocking

Bir önceki yazımızda Repository Pattern için Mocking Infrastructure Oluşturma konusuna değinmiştik ve CRUD işlemleri için ortak bir setup yapısı oluşturmuştuk. Peki ya aşağıdaki sorulduğu gibi bir case ile karşılaşırsak;

Soru : Crud metotları dışında sadece o repository'e özel bir metot tanımlamak istersek setup işlemi için nasıl bir yol izlemeliyiz ? 

Örneğin UserRepository için bir önceki örnekte tanımladığımız tanımladığımız All, Get, Insert, Update, Delete metotlarının dışında bir de GetByEmail() adında bir metot gerekli. Bu metot için gidip IRepository içerisine yeni bir metot eklemek ve sonrasında RepositoryBaseTest içerisine setup tanımlaması yapmak doğru olmaz çünkü orası adından da anlaşıldığı üzre Base anlayışına uyan işlemler için sınırlandırılmış bir yer. 

Bu gibi durumlarda IUseRepository adında bir interface tanımlayıp ve UserRepository'yi aşağıdaki gibi modify etmemiz yeterli olacaktır.

    public interface IUserRepository
    {
        User GetByEmail(string email);
    }

public class UserRepository : BaseRepository<int, User>, IUserRepository
    {
        public User GetByEmail(string email)
        {
            throw new NotImplementedException();
        }
    }

Test tarafındaki mocking işlemi için ise IUserRepository interface'ini mock yaparak setup işlemini tamamlayabiliriz.

UserRepositoryTest class'ının son hali aşağıdaki gibidir.

    [TestClass]
    public class UserRepositoryTest : RepositoryBaseTest
    {
        private List<User> _userList;
        private Mock<IRepository<int, User>> _mockRepo;
        private Mock<IUserRepository> _mockUserRepo;

        [TestInitialize]
        public void Setup()
        {
            _userList = new List<User>();
            var user1 = new User
            {
                Id = 1,
                Email = "canertosuner@gmail.com",
                FirstName = "Caner",
                LastName = "Tosuner"
            };
            _userList.Add(user1);

            var user2 = new User
            {
                Id = 2,
                Email = "tanertosuner@gmail.com",
                FirstName = "Taner",
                LastName = "Tosuner"
            };
            _userList.Add(user2);

            var user3 = new User
            {
                Id = 3,
                Email = "janertosuner@gmail.com",
                FirstName = "Janer",
                LastName = "Tosuner"
            };
            _userList.Add(user3);

            var user4 = new User
            {
                Id = 4,
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _userList.Add(user4);

            _mockRepo = new Mock<IRepository<int, User>>();

            // mock common methods
            SetupRepositoryMock<int, User>(_mockRepo, _userList);

            _mockUserRepo = new Mock<IUserRepository>();

            // mock specific method
            _mockUserRepo.Setup(x => x.GetByEmail(It.IsAny<string>()))
                .Returns(new Func<string, User>(
                    email => _userList.Single(x => x.Email == email))
                );
        }

        [TestMethod]
        public void Get_By_Email_Then_Result_OK()
        {
            var userFirst = _mockRepo.Object.All().FirstOrDefault();

            var userByEmail = _mockUserRepo.Object.GetByEmail(userFirst.Email);

            Assert.IsNotNull(userByEmail);
            Assert.AreEqual(userFirst.Email, userByEmail.Email);
            Assert.AreEqual(userFirst.Id, userByEmail.Id);
        }
    }

Yukarıda olduğu gibi ihtiyacımız olan metodu interface aracılığıyla soyutlaştırarak common olan ortak metotlar dışında ayrı olarak mocking işlemi yapabiliriz.

Repository Katmanı için Mocking Infrastructure Oluşturma (Moq Library)

Daha önceki Unit Test yazılarımızda Unit Test Nedir Nasıl Yazılır ve Moq Library Kullanarak Unit Test Yazma konularına değinmiştik. Bu yazımızda ise çokça kullandığımız Repository Pattern CRUD işlemlerinin yapıldığı metotlar için reusable bir mocking yapısı oluşturacağız. 

Öncelikle VS'da RepositoryMocking adında bir proje oluşturalım ve sonrasında projemize Generic Repository ile ilgili tanımlamalarımızı yapalım. İlk olarak IRepository adında bir interface ve database de bulunan tablolardaki unique Id-primary key alanına karşılık gelen generic IUniqueIdentifier interface'ini oluşturalaım. Bu interface'i oluşturmamızdaki amaç her tabloda Id alanı farklı tiplerde olabilir bu nedenle objelerimizi oluştururken IUniqueIdentifier interface'inden implement ederek Id alanı için veri tipini belirteceğiz. Bu bize test metotlarımızı tanımlarken ilgili linq sorgularını oluşturmada yarar sağlayacak.

public interface IUniqueIdentifier<Tkey>
{
    TKey Id { get; set; }
}
 
public interface IRepository<Tkey,TEntity> where TEntity : IUniqueIdentifier<Tkey>
{
    IQueryable<TEntity> All();
    TEntity Get(TKey Id);
    TEntity Add(TEntity entity);
    void Update(TEntity entity);
    void Delete(TEntity entity);
}

Şimdi ise abstract olan ve IRepository den inherit olan BaseRepository class'ını oluşturalım.

    public abstract class BaseRepository<TKey, TEntity> : IRepository<TKey, TEntity> where TEntity : IUniqueIdentifier<TKey>
    {
        public IQueryable<TEntity> All()
        {
            throw new NotImplementedException();
        }

        public TEntity Get(TKey id)
        {
            throw new NotImplementedException();
        }

        public TEntity Add(TEntity entity)
        {
            throw new NotImplementedException();
        }

        public void Update(TEntity entity)
        {
            throw new NotImplementedException();
        }

        public void Delete(TEntity entity)
        {
            throw new NotImplementedException();
        }
    }

Mocking işlemi yapacağımızdan metot içlerini doldurmadım ancak tabikide ilgili linq sorgularının yazılmasını gerekir.

BaseRepository tanımlamasını da yaptıktan sonra database de bulunan User tablosu için bir object ve bu tabloya ait UserRepository class'ını oluşturalım. 

    public class User : IUniqueIdentifier<int>
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
    }
    public class UserRepository : BaseRepository<int, User>
    {

    }

Buraya kadar olan kısımda Repository katmanı için gerekli olan her şey hazır. Artık Test projemizi oluşturabiliriz. Solution'a RepositoryMocking.UnitTest adında yeni bir test projesi oluşturalım ve içerisine RepositoryBaseTest adında bir class ekleyelim. Bu class reusable mocking setup işlemlerini yapacağımız class olacak.

 public abstract class RepositoryBaseTest
    {
        public void SetupRepositoryMock<TK, TE>(Mock mockRepo, List<TE> data) where TE : class, IUniqueIdentifier<TK>
        {
            var mock = mockRepo.As<IRepository<TK, TE>>();

            // setup All method
            mock.Setup(x => x.All()).Returns(data.AsQueryable());

            // setup Add method
            mock.Setup(x => x.Add(It.IsAny<TE>()))
                .Returns(new Func<TE, TE>(x =>
                {
                    dynamic lastId = data.Last().Id;
                    dynamic nextId = lastId + 1;
                    x.Id = nextId;
                    data.Add(x);
                    return data.Last();
                }));

            // setup Update method
            mock.Setup(x => x.Update(It.IsAny<TE>()))
                .Callback(new Action<TE>(x =>
                {
                    var i = data.FindIndex(q => q.Id.Equals(x.Id));
                    data[i] = x;
                }));

            // setup Get method
            mock.Setup(x => x.Get(It.IsAny<TK>()))
                .Returns(new Func<TK, TE>(
                    x => data.Find(q => q.Id.Equals(x))
                ));

            // setup Delete
            mock.Setup(x => x.Delete(It.IsAny<TE>()))
                .Callback(new Action<TE>(x =>
                {
                    var i = data.FindIndex(q => q.Id.Equals(x.Id));
                    data.RemoveAt(i);
                }));
        }
    }

Üstte bulunan kodlarda BaseRepository de bulunan db için All, Get, Insert, Update ve Delete işlemlerini yapacak olan metotlar için ortak bir setup yapısı oluşturduk ve UserRepository gibi diğer oluşturacağınız repository ler içinde RepositoryBaseTest class'ını kullanabileceğiz. Buda bizi her bir repository için ayrı ayrı setup işlemleri yapmaktan kurtarıyor. IRepository interface'ine yeni bir metot eklemek istediğinizde tekrardan yukarıda yazdığımız SetupRepositoryMock içerisine bu metot için gerekli setup işlemini tanımlayabiliriz. 

Şimdi ise UserRepository için UserRepositoryTest adında bir sınıf oluşturalım ve RepositoryBaseTest class'ını kullanarak mock işlemleri yapalım.

    [TestClass]
    public class UserRepositoryTest: RepositoryBaseTest
    {
        //db de bulunan tablo yerine geçecek fake tablomuz
        private List<User> _userList;

        //mock user repository
        private Mock<IRepository<int, User>> _mockRepo;

        [TestInitialize]
        public void Setup()
        {
            //tablomuzun içerisini dolduralım
            _userList = new List<User>();
            var user1 = new User
            {
                Id = 1,
                Email = "canertosuner@gmail.com",
                FirstName = "Caner",
                LastName = "Tosuner"
            };
            _userList.Add(user1);

            var user2 = new User
            {
                Id = 2,
                Email = "tanertosuner@gmail.com",
                FirstName = "Taner",
                LastName = "Tosuner"
            };
            _userList.Add(user2);

            var user3 = new User
            {
                Id = 3,
                Email = "janertosuner@gmail.com",
                FirstName = "Janer",
                LastName = "Tosuner"
            };
            _userList.Add(user3);

            var user4 = new User
            {
                Id = 4,
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _userList.Add(user4);
             
            //mock respository değerini initialize edelim
            _mockRepo = new Mock<IRepository<int, User>>();

            //repositorybasetest class'ını kullarak crud metotlarını için setup işlemlerini yapalım
            SetupRepositoryMock<int, User>(_mockRepo, _userList);
        }
    }

UserRepository için setup işlemlerimizi tamamladık. Yukarıdaki işlemler sonrasında elimizde db de bulunan User tablosu yerine geçen bir _userList array'imiz ve bu array üzerinden repository metotlarını setup ettik. Şimdi bir kaç test metodu yazıp kodlarımızı test edelim. UserRepositoryTest class'ının son hali aşağıdaki gibidir.

    [TestClass]
    public class UserRepositoryTest: RepositoryBaseTest
    {
        private List<User> _userList;
        private Mock<IRepository<int, User>> _mockRepo;

        [TestInitialize]
        public void Setup()
        {
            _userList = new List<User>();
            var user1 = new User
            {
                Id = 1,
                Email = "canertosuner@gmail.com",
                FirstName = "Caner",
                LastName = "Tosuner"
            };
            _userList.Add(user1);

            var user2 = new User
            {
                Id = 2,
                Email = "tanertosuner@gmail.com",
                FirstName = "Taner",
                LastName = "Tosuner"
            };
            _userList.Add(user2);

            var user3 = new User
            {
                Id = 3,
                Email = "janertosuner@gmail.com",
                FirstName = "Janer",
                LastName = "Tosuner"
            };
            _userList.Add(user3);

            var user4 = new User
            {
                Id = 4,
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _userList.Add(user4);

            _mockRepo = new Mock<IRepository<int, User>>();

            SetupRepositoryMock<int, User>(_mockRepo, _userList);
        }

        [TestMethod]
        public void Get_All_Count()
        {
            Assert.AreEqual(_userList.Count, _mockRepo.Object.All().Count());
        }

        [TestMethod]
        public void Get_By_Id_Then_Check_Name()
        {
            var item = _mockRepo.Object.FindBy(4);
            Assert.AreEqual("Yeneer", item.FirstName);
        }

        [TestMethod]
        public void Remove_User_Then_Check_Count()
        {
            var user4 = new User
            {
                Id = 4,
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _mockRepo.Object.Delete(user4);
            Assert.AreEqual(3, _mockRepo.Object.All().Count());
        }

        [TestMethod]
        public void Add_New_User_Then_Check_Count()
        {
            var tempCount = _userList.Count;

            var user5 = new User
            {
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _mockRepo.Object.Add(user5);

            Assert.AreEqual(tempCount + 1, _mockRepo.Object.All().Count());
        }
    }

RepositoryPattern için reusable mocking işlemi için hepsi bu kadar. Projenizde UserRepository dışında bulunan diğer repository'ler içinde aynı UserRepository de olduğu gibi generic oluşturduğumuz RepositoryBaseTest'i kullanarak setup işlemini yapıp testlerinizi yazabilirsiniz.

Moq Library Kullanarak Unit Test Yazma

Daha önceki yazımızda Unit Test Nedir Nasıl Yazılır konusuna değinmiştik ve basit bir console uygulaması ve onun unit test metotlarının bulunduğu test projemizi yazmıştık. O örneğimizde herhangi bir database veya kendi oluşturduğumuz data modelleri vs yoktu 4 işlem yapan bir projeydi. Bu yazımızda Db operasyonları olan bir projede unit test yazmak istesek ne yapardık bu soruya cevap arıyor olacağız.

Bir UserRepository class'ımız olsun ve bu repository için unit test metotları yazıyor olalım. Peki ama test metotlarını yazarken nasıl bir yol izleyeceğiz ? Her bir test case'i için gidip database saçma sapan fake kayıtlar atıp CRUD işlemleri yapmamamız gerekir. Bu gibi durumlar için mocking dediğimiz "alaycı" veya "sahte" kayıtlar oluşturmamızı sağlayan library'ler bulunmakta. Bu library'lerden Moq'u kullanarak sahte nesneler üreterek UserRepository için basit bir test projesi yazacağız.

Moq

Moq .Net tarafında unit test yazmada kullanabildiğimiz bir mocking kütüphanesidir. Testlerimiz için sahte nesneler üreterek normal projemizde ki case'leri test etmemizi sağlar.

Örnek projemiz için öncelikle bir tane UserSample adımda Console Application oluşturalım ve içerisine User.cs ve IUserRepository.cs class'larını aşağıdaki gibi tanımlayalım.

    public class User
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
    public interface IUserRepository
    {
        IList<User> GetAll();
        User GetById(int userId);
        void Insert(User user);
        void Update(User user);
        void Delete(int Id);
    }

Şimdi ise UserSample.Test adında test projemizi oluşturalım ve UserSample projemizi referans olarak test projemize ekleyelim. Sonrasında test projemize Nuget üzerinden Moq kütüphanesini kuralım.

Tools > Nuget Package Manager > Package Manager Console > PM> Install-Package Moq

 Kurulum işlemi tamamlandıktan sonra UserRepositoryTest adında bir class oluşturalım.

İlk olarak User Repository için gerekli olan setup işlemlerini yapalım. Setup işlemi kısaca repository'nin içerisindeki metotların sahte objelerle işlemleri yapmasını sağlayacak kodları yazmak diyebiliriz. Mocking setup ile ilgili kodlarımız aşağıdaki gibi olacaktır.

 [TestClass]
    public class UserRepositoryTest
    {
        public readonly IUserRepository MockUserRepository;

        public UserRepositoryTest()
        {
            // Test metotları genelinde kullanacağımız User listesi
            var userList = new List<User>
            {
                new User {Id=1,FirstName="User1",LastName="User1LastName" },
                new User {Id=2,FirstName="User2",LastName="User2LastName" },
                new User {Id=3,FirstName="User3",LastName="User3LastName" }
            };

            // Mock the Products Repository using Moq
            var mockUserRepository = new Mock<IUserRepository>();

            // GetAll metodu için setup işlemi
            mockUserRepository.Setup(mr => mr.GetAll()).Returns(userList);

            // GetById metodu için setup işlemi
            mockUserRepository.Setup(mr => mr.GetById(It.IsAny<int>())).Returns((int i) => userList.Single(x => x.Id == i));

            // Insert için setup işlemi
            mockUserRepository.Setup(mr => mr.Insert(It.IsAny<User>())).Callback(
                (User target) =>
                {
                    userList.Add(target);
                });

            // Update için setup işlemi
            mockUserRepository.Setup(mr => mr.Update(It.IsAny<User>())).Callback(
                (User target) =>
                {
                    var original = userList.Where(q => q.Id == target.Id).Single();

                    if (original == null)
                    {
                        throw new InvalidOperationException();
                    }

                    original.FirstName = target.FirstName;
                    original.LastName = target.LastName;

                });

            // Test metotlarından erişilebilmesi için global olarak tanımladığımız MockUserRepository'e yukarıdaki setup işlemlerini atıyoruz
            this.MockUserRepository = mockUserRepository.Object;
        }
}

Yukarıda bulunan kodlar kısaca şunları söylemekte;

Arkadaş senin IUserRepository diye CRUD işlemlerinin yapıldığı bir class'ın var ve bu class içerisinde bulunan GetAll, GetById, Insert, Update, Delete metotları için tanımlanan mocking veya kandırmaca işlemleri yukarıdaki gibidir. Sen Database üzerinden bu işlemleri yapmak yerine rahatça userList array'i üzerinden bu işlemleri yapabilirsin.

Buraya kadar her şey OK ise aşağıdaki gibi sırasıyla test metotlarımızı yazalım.

GetAll metodunu çağırarak bize veri döndüğünü gösteren test metodu.

        [TestMethod]
        public void GetAll_Than_Check_Count_Test()
        {
            var expected = this.MockUserRepository.GetAll().Count;

            Assert.IsNotNull(expected);// Test not null
            Assert.IsTrue(expected > 0);// Test GetAll returns user objects
        }

GetById metodu için doğru objeyi return edip etmediği durumu için test metodu.

        [TestMethod]
        public void GetById_Than_Check_Correct_Object_Test()
        {
            var actual = new User { Id = 2, FirstName = "User2", LastName = "User2LastName" };

            var expected = this.MockUserRepository.GetById(2);

            Assert.IsNotNull(expected); // Test is not null
            Assert.IsInstanceOfType(expected, typeof(User)); // Test type
            Assert.AreEqual(actual.Id, expected.Id); // test correct object found
        }

Insert işleminden sonra GetAll metodundan dönen object sayısı doğrumu testi

        [TestMethod]
        public void Insert_User_Than_Check_GetAll_Count_Test()
        {
            var actual = this.MockUserRepository.GetAll().Count + 1;

            var user = new User { Id = 4, FirstName = "User4", LastName = "User4LastName" };

            this.MockUserRepository.Insert(user);

            var expected = this.MockUserRepository.GetAll().Count;

            Assert.AreEqual(actual, expected);
        }

GetById metoduna hatalı bir Id ile çağrım yapıldığında Exception döneceği durumu için test metodu.

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]//Eğer beklediğimiz bir exception var ise bu şekilde tanımlayabiliriz
        public void GetyId_With_Undefined_Id_Than_Exception_Occurred_Test()
        {
            var expected = this.MockUserRepository.GetById(It.IsAny<int>());
        }

Update işlemi sonrasında GetById yapılarak dönen nesnede bulunan değerler doğrumu test metodu.

        [TestMethod]
        public void Ipdate_User_Than_Check_It_Is_Updated_Test()
        {
            var actual = new User { Id = 2, FirstName = "User2_Updated", LastName = "User2LastName_Updated" };

            this.MockUserRepository.Update(actual);

            var expected = this.MockUserRepository.GetById(actual.Id);

            Assert.IsNotNull(expected);
            Assert.AreEqual(actual.FirstName, expected.FirstName);
            Assert.AreEqual(actual.LastName, expected.LastName);
        }

Test metotlarını yazdıktan sonra Run All Tests diyerek testlerimizi çalıştırıp success fail durumlarını görebiliriz.

Bu yazımızda bir Mocking kütüphanesi olan Moq kullanarak basitçe bir Unit Test projesi hazırladık ve halen daha çok fazla önemsenmese de unit Test dev-ops süreçlerinin olgunlaşmasıyla artık bir çok firma için "IsMust" zorunlu hale gelmiş bir kuraldır ve daha önceki yazılarda da bahsettiğim üzre Unit Test yazıyor olmak artık interview'larda beklenen bir durum haline gelmiştir.

Unit Test Nedir

Unit Test günümüz yazılım dünyasında artık olmazsa olmaz bir kural olarak bütün projelerde yazılması zorunlu hale gelmekte. DevOps açısından düşündüğümüzde Continuous Integration ve Continuous Deployment tool'ları yazmış olduğumuz kodları build edip deploy etmeden önce ilk olarak test metotlarını çalıştırır ve metotlardan herhangi biri fail verdiğinde deploy'u durdurabilir. Böylelikle unit test yazmak artık yazılım geliştirme dünyasında Nice to Have olmaktan çıkıp Is Must haline gelmektedir. 

Bu yazımızda Unit Test nedir, nasıl yazılır gibi sorulara cevap arıyor olacağız.

Unit Test Nedir ?

Unit test geliştirmiş olduğunuz kodu test etmek için yazdığınız kod bloğudur ve esas olarak kodun veya fonksiyonun belirli bir biriminin davranışını kontrol eder ve geliştirdiğiniz kod dizinindeki akışa dayalı olarak acceptance criteria'ları cover ediyor mu etmiyor mu ölçebilmek adına da büyük öneme sahiptir.

Hiçbir kod ilelebet el değmeden yaşamayacaktır, her zaman için sizden sonra gelen bir developer extra feature'lar ekleyip commit edecektir yada o kodları kullanacak başka ekiplerde olacaktır. Unit test aynı zamanda kodlarınızı kullanacak diğer developer'lar tarafından okunduğunda onların kodları nasıl kullanması gerektiğini anlatması açısından da önemli bir göreve sahiptir. Nasıl çalıştığını öğrenmek için testleri çalıştırıp okuyabilirler. Yada daha önce yazılmış bir kod bloğuna küçük bir özellik eklemek istiyorsunuz ama bu mevcut kullanıcıları/sistemi bozar mı bozmaz mı diye sorgulamak yerine eğer o kod bloğunun daha önceden yazılmış ve her bir satırını cover eden unit-test metotları var ise tek yapmanız gereken o testleri run edip geçip geçmediğini görmek. Eğer o sonradan yaptığınız geliştirme mevcut'u bozmuyorsa test'ler zaten green statüsünde passed olacaktır.

Unit yazabilmek için nuget üzerinden de install edebildiğimiz bir çok kütüphane bulunmakta.

Bazı Unit Test Frameworkleri

  1. MSTest
  2. NUnit
  3. xUnit
  4. MBunit

 Herhangi birini seçip kendi sitelerinde yayınlamış oldukları sample projeler üzerinden kendi unit test projenizi oluşturabilirsiniz. 

Örnek Proje

Şimdi küçük bir unit test örneği yapalım. Eft işlemi yapan bir modül için bir Console Application ve bu modülü basitçe test etmek için bir unit test projesi oluşturalım.

İlk olarak VS da EftSample adında bir console projesi oluşturalım ve sonrasında solution'a sağ tıklayıp yeni bir proje ekle diyerek ismi EftSample.Test adında bir UnitTest projesi oluşturalım.

Eft işleminde kullanılacak olan TransferRequest ve TransferResponse adında class'larını console uygulamamıza aşağıdaki gibi oluşturalım.

       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; }
       }

Daha sonra transfer işlemini yapacak olan metodu Transfer adında bir class içerisine TransferMoney adında bir metot aşağıdaki gibi tanımlayalım.

Transfer.cs

    public class Transfer
    {
        public TransferResponse MoneyTransfer(TransferRequest request)
        {
            var response = new TransferResponse();
            
            //Aşağıdaki validasyonlar için farklı yöntemler, frameworkler kullanılması daha doğrudur ancak açık görünmesi açısından bu şekilde yapalım
            if (request.Amount < 1)
            {
                response.IsSuccess = false;
                response.Message = "Amount must be greater Then 1";
                return response;
            }
            if (string.IsNullOrEmpty(request.SenderIBAN))
            {
                response.IsSuccess = false;
                response.Message = "Sender Account IBAN must not be null or empty.";
                return response;
            }
            if (string.IsNullOrEmpty(request.ReceiverIBAN))
            {
                response.IsSuccess = false;
                response.Message = "Receiver Account IBAN must not be null or empty.";
                return response;
            }

            //TODO do process
            response.IsSuccess = true;
            response.Message = "Transfer operation is successful.";

            return response;
        }
    }

Şimdi sıra test metotlarını yazmaya geldi. EftSample.Test adında ki test projemize gidip EftSample projemizi referans olarak ekleyelim. Sonrasında TransferMoneyTest.cs adında test metotlarımızı yazacağımız class'ı oluşturalım.

Not - 1: Unit test yazarken naming oldukça önemli bir konudur. Ortak kabul görmüş bir standart olan MethodName_StateUnderTest_ExpectedBehavior şeklinde bir isimlendirme önerilir ancak her yiğidin yoğurt yiyişi farklıdır tezinden yola çıkarak developer anlaşılır yazdığı sürece isimlendirmeyi istediği gibide yapabilmektedir. 

Not - 2: Test metotlarını yazdığımız class'ların başında [TestClass] adında bir attribute tanımlaması yapılması zorunludur. Bu attribute ile sistem o class'ın bir test class'ı olduğunu anlamakta.

Not - 3: Yazılmış olan test metotlarının başında [TestMethod] attribute'ü yazılması gerekir. Bu attribute o metodun bir test metodu olduğunu işaret etmekte.

1- Amount Less Then One Test

İlk test metodumuz Amount = 0 olarak eft işlemi yapmaya çalıştığımızda ki case'i test ediyor olsun.

        [TestMethod]
        public void TransferMoney_With_Zero_Amount_Then_Transfer_Failed_Test()
        {
            var expected = new TransferResponse
            {
                IsSuccess = false,
                Message = "Amount must be greater Then 1"
            };

            var transfer = new Transfer();

            var transferRequest = new TransferRequest
            {
                Amount = 0,
                ReceiverIBAN = "TR806541651616516416541",
                SenderIBAN = "TR13216498468465416"
            };

            var actual = transfer.MoneyTransfer(transferRequest);

            Assert.AreEqual(expected.IsSuccess, actual.IsSuccess);
            Assert.AreEqual(expected.Message, actual.Message);
        }

2- Sender IBAN Empty Test

İkinci test metodu ise sender iban alanı boş bırakıldığında ki case'i test ediyor olsun.

        [TestMethod]
        public void TransferMoney_With_Empty_Sender_IBAN_Then_Transfer_Failed_Test()
        {
            var expected = new TransferResponse
            {
                IsSuccess = false,
                Message = "Sender Account IBAN must not be null or empty."
            };

            var transfer = new Transfer();

            var transferRequest = new TransferRequest
            {
                Amount = 60,
                SenderIBAN = string.Empty,
                ReceiverIBAN = "TR13216498468465416"
            };

            var actual = transfer.MoneyTransfer(transferRequest);

            Assert.AreEqual(expected.IsSuccess, actual.IsSuccess);
            Assert.AreEqual(expected.Message, actual.Message);
        }

3- All Pramters Are Ok Test

Son metodumuz da doğru bilgilerle transfer işlemi yapılmaya çalışıldığında ki case'i test ediyor olsun.

        [TestMethod]
        public void TransferMoney_With_Correct_Request_Parameter_Then_Transfer_OK_Test()
        {
            var expected = new TransferResponse
            {
                IsSuccess = true,
                Message = "Transfer operation is successful."
            };

            var transfer = new Transfer();

            var transferRequest = new TransferRequest
            {
                Amount = 60,
                ReceiverIBAN = "TR806541651616516416541",
                SenderIBAN = "TR13216498468465416"
            };

            var actual = transfer.MoneyTransfer(transferRequest);

            Assert.AreEqual(expected.IsSuccess, actual.IsSuccess);
            Assert.AreEqual(expected.Message, actual.Message);
        }

Test metotlarımızı yazdıktan sonra aşağıdaki görselde olduğu gibi VS'da bulunan Test > Run > All Tests dedikten sonra Test Explorer açılır ve solution da bulunan test metotları çalıştırılıp success ve fail durumlarını görebiliriz.

TransferMoneyTest.cs içerisi son olarak aşağıdaki gibidir.

    [TestClass]
    public class TransferMoneyTest
    {
        [TestMethod]
        public void TransferMoney_With_Zero_Amount_Then_Transfer_Failed_Test()
        {
            var expected = new TransferResponse
            {
                IsSuccess = false,
                Message = "Amount must be greater Then 1"
            };

            var transfer = new Transfer();

            var transferRequest = new TransferRequest
            {
                Amount = 0,
                ReceiverIBAN = "TR806541651616516416541",
                SenderIBAN = "TR13216498468465416"
            };

            var actual = transfer.MoneyTransfer(transferRequest);

            Assert.AreEqual(expected.IsSuccess, actual.IsSuccess);
            Assert.AreEqual(expected.Message, actual.Message);
        }

        [TestMethod]
        public void TransferMoney_With_Empty_Sender_IBAN_Then_Transfer_Failed_Test()
        {
            var expected = new TransferResponse
            {
                IsSuccess = false,
                Message = "Sender Account IBAN must not be null or empty."
            };

            var transfer = new Transfer();

            var transferRequest = new TransferRequest
            {
                Amount = 60,
                SenderIBAN = string.Empty,
                ReceiverIBAN = "TR13216498468465416"
            };

            var actual = transfer.MoneyTransfer(transferRequest);

            Assert.AreEqual(expected.IsSuccess, actual.IsSuccess);
            Assert.AreEqual(expected.Message, actual.Message);
        }

        [TestMethod]
        public void TransferMoney_With_Correct_Request_Parameter_Then_Transfer_OK_Test()
        {
            var expected = new TransferResponse
            {
                IsSuccess = true,
                Message = "Transfer operation is successful."
            };

            var transfer = new Transfer();

            var transferRequest = new TransferRequest
            {
                Amount = 60,
                ReceiverIBAN = "TR806541651616516416541",
                SenderIBAN = "TR13216498468465416"
            };

            var actual = transfer.MoneyTransfer(transferRequest);

            Assert.AreEqual(expected.IsSuccess, actual.IsSuccess);
            Assert.AreEqual(expected.Message, actual.Message);
        }
    }

Yazının başında da belirttiğimiz üzre unit-test yazmak ilk başlarda maliyet olarak görünsede aslında oldukça önemli ve hayat kurtarıcı yanları da olabilmekte. Çeşitli framework'ler kullanarak çok farklı test yapıları oluşturabilirsiniz. Yaptığımız örnekte basitçe unit test nasıl yazılır göstermeye çalıştık. Unit test ile ilgili daha fazla bilgiyi ms'in burada bulunan adresinden de bulabilirsiniz. 

Redis Nedir

Daha önceki yazıda Windows üzerinde Redis kurulumunu anlatmıştık ve o yazımızda bahsettiğimiz gibi bu yazımızda StackExchange.Redis kullanarak bir .Net Client projesi geliştireceğiz. Öncelikle pc nizde Redis'in çalıştığından emin olun. Bunun için aşağıdaki gibi bir deneme yapabilirsiniz. 

Redis çalışıp çalışmadığına dair kontrol için redis-cli.exe'yi çalıştıralım ve aşağıdaki resimde olduğu gibi bir key-value tanımlayalım ve sonrasında get set işlemi yapalım

Eğer Redis Server sorunsuz bir şekilde çalışıyorsa projemizi oluşturmaya başlayalım. Öncelikle VS'da bir adet RedisDotNetClientSample isminde bir ConsoleApplication oluşturalım.

StackExchange.Redis Kurulum

Daha sonra projemize Nuget Package Manager Console üzerinden StackExchange.Redis package'ı indirip kuralım.

PM > Install-Package StackExchange.Redis

Connection

StackExchange.Redis ile uğraşıyosanız herhalde en çok dikkat etmeniz gereken class ConnectionMultiplexer. ConnectionMultiplexer Redis Server'a bağlanmanızı sağlayacak olan sınıftır ve instance yönetimi Singleton olarak yapılması önerilir. Çünkü ConnectionMultiplexer fully thread-safe dir ve her bir işlem için tekrar tekrar instance oluşturmamız gerekir. Kurulum işlemi sorunsuz tamamlandıktan sonra aşağıdaki gibi RedisConnectionFactory adında bir class oluşturalım.

    public class RedisConnectionFactory
    {
        static RedisConnectionFactory()
        {
            lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
            {
                return ConnectionMultiplexer.Connect("localhost:6379");//redis server conn string bilgisi, web config'den almak daha doğru ancak şimdilik buraya yazdık
            });
        }

        private static Lazy<ConnectionMultiplexer> lazyConnection;

        public static ConnectionMultiplexer Connection => lazyConnection.Value;

        public static void DisposeConnection()
        {
            if (lazyConnection.Value.IsConnected)
                lazyConnection.Value.Dispose();
        }
    }

Generic ICache Interface

Connection kısmını hallettik şimdi ise Cache tarafını yazalım. Bir tane aşağıdaki gibi ICache interface'i tanımlayalım.

    public interface ICache : IDisposable
    {
        T Get<T>(string key);

        void Set<T>(string key, T obj, DateTime expireDate);

        void Delete(string key);

        bool Exists(string key);
    }

RedisCache Class'ı

Şimdi ise ICache interface'ini implement etmiş RedisCache class'ımızı oluşturalım. Bu class içerisinde tanımlı fonksiyonları kullanarak string objelerimizi json formatında string olarak Redis'e atıyor olacağız.

    public class RedisCache : ICache
    {
        private readonly IDatabase _redisDb;

        //Connection bilgisi initialize anında alınıyor
        public RedisCache()
        {
            _redisDb = RedisConnectionFactory.Connection.GetDatabase();
        }

        //Redis'e json formatında set işlemi yapılan metot
        public void Set<T>(string key, T objectToCache, DateTime expireDate)
        {
            var expireTimeSpan = expireDate.Subtract(DateTime.Now);

            _redisDb.StringSet(key, SerializerHelper.Serialize(objectToCache), expireTimeSpan);
        }

        //Redis te var olan key'e karşılık gelen value'yu alıp deserialize ettikten sonra return eden metot
        public T Get<T>(string key)
        {
            var redisObject = _redisDb.StringGet(key);

            return redisObject.HasValue ? SerializerHelper.Deserialize<T>(redisObject) : Activator.CreateInstance<T>();
        }

        //Redis te var olan key-value değerlerini silen metot
        public void Delete(string key)
        {
            _redisDb.KeyDelete(key);
        }

        //Gönderilen key parametresine göre redis'te bu key var mı yok mu bilgisini return eden metot
        public bool Exists(string key)
        {
            return _redisDb.KeyExists(key);
        }

        //Redis bağlantısını Dispose eden metot
        public void Dispose()
        {
            RedisConnectionFactory.Connection.Dispose();
        }
    }

Projemizin Redis bağlantı adımları ve set ve get işlemlerini yapan kısımları hazır. Şimdi ise yazdığımız kodları test etme adımı var. 

Örnek bir obje tanımlayıp bu objeyi redis'e atıyor olalım. Bunun için aşağıdaki gibi User adında bir class'ımız olsun.

    public class User
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
    }

Program.cs içerisinde aşağıdaki gibi Redis içerisine bir adet User objesi oluşturup get&set işlemi yapalım.

    class Program
    {
        static void Main(string[] args)
        {
            ICache redisCache = new RedisCache();

            var userToBeCached = new User
            {
                Id = 1,
                Email = "canertosuner@gmail.com",
                FirstName = "Caner",
                LastName = "Tosuner"
            };
            var key1 = "caner_key1";

            redisCache.Set(key1, userToBeCached, DateTime.Now.AddMinutes(30));//30 dakikalığına objemizi redis'e atıyoruz

            if (redisCache.Exists(key1))
            {
                var userRedisResponse = redisCache.Get<User>(key1);
            }
        }
    }

Projenizi çalıştırdıktan sonra aşağıdaki görselde olduğu gibi Redis'e atmış olduğumuz objeyi tekrar get işlemini yaptığımızda userRedisResponse değişkenine atanan değerleri görüyoruz.

 

Bu yazı C# ile Redis Cache ve StackExchange.Redis nasıl entegre edilip kullanılır basitçe özetlemekte. Tabiki Redis ile ilgili konuşulacak çok daha fazla konu var ancak elimden geldiğince basit bir şekilde redis client uygulaması geliştirdik.Bazı önemli linkleri aşağıda paylaşıyorum. Redis ile ilgili önemli gelişmeleri bu siteler üzerinden takip edebilirsiniz. 

redis.io 

MS OpenTech – Redis 

StackExchange.Redis 

Redis Desktop 

 

Redis Server Windows Üzerinde Kurulumu ve Kullanımı

Bu yazıda Distributed Caching sistemlerinden biri olan Redis'i inceliyor olacağız.

Redis Nedir ?

Redis için kısaca open source bir NOSQL Memcached veritabanı sistemidir diyebiliriz. Her ne kadar ilk olarak Linux için tasarlanmış olsada ihtiyaç doğrultusunda Windows işletim sistemlerinde de kullanılabilir hale getirildi. Çalışma şekli olarak Key-Value şeklinde gönderilen bilgileri store etmektedir. 

 

Veri Tipleri

Redis verileri String, Hashe, List, Set ve Sorted List olarak saklayabilir.

 Veri tipleri ile ilgili daha ayrıntılı bilgiyi bu linkte bulabilirsiniz. 

Kurulum ve Kullanımı

Öncelikle Redis'i indirip service olarak bilgisayarımıza kuruyoruz. Bunun için bu linkten sizin için uygun olan .rar uzantılı sürümü bulup bilgisayarımıza indiriyoruz. Sonrasında indirmiş olduğunuz dosyalardan redis-server.exe adlı exe'yi çalıştırıp kurulumu yapıyoruz. Default olarak 6379 port'unu hizmete sokar ancak istersek bunu değiştirebiliriz de. Exe çalıştıktan sonra aşağıdaki gibi bir ekran gördüyseniz kurulum OK dir.

Redis çalışıp çalışmadığına dair kontrol için redis-cli.exe'yi çalıştıralım ve aşağıdaki resimde olduğu gibi test amaçlı bir key-value tanımlayıp sonrasında get set işlemi yapalım

Bu yazımızda Windows üzerinde Redis Server nasıl kurulur ve kullanılır bunu gördük. Bir sonraki Redis yazımızda StackExchange.Redis redis client kullanarak NET dilleri için (C# etc) örnek proje yapıyor olacağız.