Nancy Nedir (NancyFx)

Nancy .Net ve Mono için HTTP protokolü üzerinde çalışan uygulamalar geliştirmemizi sağlayan bir lightweight framework dür. Ruby de kullanılan Sinatra framework'ün den esinlenerek geliştirilmiştir ve az kaynak tüketmesinden dolayı performansıyla ön plana çıkmıştır.

Nancy developer'ları MVC(Model-View-Controller) pattern'nini veya başka herhangi bir pattern kullanmaya zorlamadan basit bir şekilde geliştirme yapmamıza olanak sağlar. Sebebi ise yukarıda bahsettiğimiz gibi sadece HTTP isteklerine cevap veren küçük ve orta ölçekli bir uygulama görevi görüyor olması.

MVC pattern'nini implement etmeye zorlamıyor derken edemeyeceğimiz anlamına da gelmemekte. Tıpkı ASP.Net MVC yada WebApi projelerinde olduğun gibi solution'da View klasörü yaratarak projeniz için olan .cshtml'leri bu dosya altında oluşturabilir veya Model klasörü yaratarak projede kullandığınız request response yada viewModel sınıflarınızı bu klasör altına oluşturabilirsiniz. Özetle Nancy ASP.Net MVC ve Web Api'nin bir alternatifi diyebiliriz. 

En büyük özelliği ise IIS e bağımlı olmadan Windows'da çalışmakta kalmayıp OSX, Linux hatta Raspberry Pi üzerinde bile çalışabilmektedir. Raspberry Pi üzerinden ASP.Net MVC çalıştırmak nasıl olurdu acaba..

Nancy ile örnek bir api projesi yapalım. İlk olarak  vs. da Nancy_Sample adında bir console app. oluşturalım ve sonrasında aşağıdaki gibi nuget üzerinden ihtiyacımız olan dll leri kuralım.

 

Nancy kütüphanesini kullanabilmek için Nancy ve host edebilmemizi sağlayan Nancy.Hosting.Self ve cshtml view'lerini kullanabilmemizi sağlayan Nancy.Viewengines.Razor paketlerini projemize ekleyelim.

Sonrasında Program.cs içerisine aşağıdaki gibi nancy konfigurasyonlarımızı yapalım.

class Program
{
    private readonly NancyHost _nancy;

    public Program()
    {
        var uri = new Uri("http://localhost:7880");
        var hostConfigs = new HostConfiguration { UrlReservations = { CreateAutomatically = true } };
        _nancy = new NancyHost(uri, new DefaultNancyBootstrapper(), hostConfigs);
    }

    private void Start()
    {
        _nancy.Start();
        Console.WriteLine($"Started listenig address {"http://localhost:7880"}");
        Console.ReadKey();
        _nancy.Stop();
    }

    static void Main(string[] args)
    {
        var p = new Program();
        p.Start();
    }
}

Yukarıdaki kod bloğunda Nancy bizim için host edilen makinada http://localhost:7880 portunu reserv ederek dinlemeye başlayacaktır. Bu adrese gelen http isteklerini ilgili route'a yönlendirecektir.

Browser üzerinden bu adrese gittiğimizde aşağıdaki gibi bir ekran ile karşılaşırız.

404 Not Found sayfasını almamızın sebebi projemizde henüz endpoint'leri tanımlayacağımız NancyModule class'ından türeyen bir Module olmaması.

Hemen projemize SampleModule adında NancyModule class'ından inherit alan bir class oluşturalım ve içerisine httpGet isteği alan bir end-point tanımlayalım.

public class SampleModule : NancyModule
{
    public SampleModule()
    {
        Get["/"] = parameters => "Que pasa primo !";
    }
}

Projeyi tekrar run edip browser'dan kontrol ettiğimizde aşağıdaki gibi Get metodunun return ettiği response'u göreceğiz.

Şimdi birde HttpPost örneği yapalım. Request olarak 2 sayı alan ve geriye bu 2 sayının toplamını return eden bir end-point yazalım. Request ve Response modellerimiz aşağıdaki gibi olacak şekilde oluşturalım.

public class SumRequestModel
{
    public int X { get; set; }
    public int Y { get; set; }
}

public class SumResponseModel
{
    public int Result { get; set; }
}

SampleModule içerisine yazacağımız end-point ise aşağıdaki gibi gönderilen request parametrelerine göre geriye toplamlarını dönecektir.

public class SampleModule : NancyModule
{
    public SampleModule()
    {
        Post["/sum"] = parameters =>
        {
            var request = this.Bind<SumRequestModel>();

            return new SumResponseModel { Result = request.X + request.Y };
        };
    }
}

Postman üzerinden aşağıdaki gibi bir httpPost request'inde bulunduğumuzda request olarak gönderilen parametrelere göre response da toplamlarını dönmektedir.

 

Yukarıda yaptığımız örnek ile Nancy kullanarak basit bir api nasıl geliştirebiliriz inceledik. Yazının başında da söylediğimiz üzre Nancy ile geriye View de yani html sayfaları da return edebiliriz.

Örnek olarak solution'da View adında bir klasör ve içerisine Home adında .cshml uzantılı bir htmlFile oluşturalım. İçerisine de aşağıdaki gibi body tagleri arasına basit bir form input'ları ekleyelim.

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    <form method="post" name="myForm">
        First name: <input type="text" name="fname"><br>
        Last name: <input type="text" name="lname"><br>
        <input type="button" value="Send">
    </form>
</body>
</html>

Oluşturduğumuz bu sayfaya solution'da sağ tıklayıp Properties'den Copy to Output Directory özelliğini Copy always olarak değiştirmemiz gerekmekte aksi taktirde proje run edildiğinde Home.cshtml dosyasına erişemiyor.

Yukarıdakileri yaptıktan sonra browser'dan http://localhost:7880/home adresine bir istek geldiğinde Home.cshtml sayfamıza yönlendirecek kodu yazalım.

public class SampleModule : NancyModule
{
    public SampleModule()
    {
        Get["/home"] = parameters =>
        {
            return View["View/Home.cshtml"];
        };
    }
}

Projeyi run edip browser'dan http://localhost:7880/home adresine gittiğimizde bizi Home.cshtml sayfasına yönlendirip ekrana formu yazdıracaktır.

 

Özetle; Nancy Microsoft tarafından ASP.Net'in core dll'i olan System.Web'e bağımlı olmadan özgürce geliştirdiği şahane bir framework dür. Genelde çok büyük ölçekli projelerde tercih edilmese de ihtiyaç olduğunda bizleri çok fazla iş yükünden kurtararak ve de bence en önemlisi IIS'e bağlı kalmadan belli bir göreve hizmet eden küçük ölçekli lightweight uygulamalar geliştirmemize olanak sağlar. Daha fazla bilgi için nancyfx.org sayfasına göz atabilirsiniz.

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. 

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.

ConcurrentStack ve ConcurrentQueue

Thread-safe collection ilk olarak .Net framework 4.0 ile System.Collections.Concurrent namespace'i altında hayatımıza girdi. System.Collections.Concurrent namespace'i altında thread-safe geliştirmeler yapmada kullanabilmek için ConcurrentStack ve ConcurrentQueue adında 2 tane tip bulunmaktadır.

ConcurrentStack

ConcurrentStack LIFO (last in first out) prensibine göre çalışan bir data stracture dır. Generic ConcurrentStack<T> thread safe olan Stack<T>'nin karşılığı olarak düşünebiliriz ve .Net Framework 4.0 ile hayatımıza girmiştir.

Bu class ile kullanılabilecek metotlar ; 

  1. Push(T element) T tipindeki objeyi stack'e eklemek için kullanılır,
  2. PushRange T tipinde ki array'leri stack'e eklemek için kullanılır,
  3. TryPop(out T) stack'te bulunan ilk item'i döner, başarılı ise true değilse false döner
  4. TryPeek(out T) stack'te bulunan bir sonraki item'ı almak için kullanılır ancak bu item'ı stack'ten silmez, başarılı ise true değilse false döner,
  5. TryPopRange TryPop metodu ile aynı çalışır sadece geriye tek bir obje değilde bir array döner.

 ConcurrentStack<T>'nin instance oluşturma ve Push metodu kullanımı aşağıdaki gibidir.

            var concurrentStack = new ConcurrentStack<int>();

            for (var i = 0; i < 10; i++)
            {
                concurrentStack.Push(i);
            }

Concurrent stack'te bulunan bir obje için get işlemi yapmak istediğimizde ise TryPop(out T) metodunu kullanıyoruz. 

            int outData;
            bool isSuccess = concurrentStack.TryPop(out outData);

Full kullanım örneği olarak aşağıda bulunan  metot 1'den başlayıp 100'e kadar olan sayıları Concurrent stack'e atıp sonrasında stack'ten okuma işlemini yapıp ekrana sayıları display etmektedir..

        public static void Main()
        {
            var concurrentStack = new ConcurrentStack<int>();

            for (var i = 1; i < 100; i++)
            {
                concurrentStack.Push(i);
            }

            while (concurrentStack.Count > 0)
            {
                int stackData;

                bool success = concurrentStack.TryPop(out stackData);

                if (success)
                {
                    Console.WriteLine(stackData);
                }
            }
        }

Projeyi çalıştırdığınızda 99'dan başlayıp 0'a kadar olan sayıları display edecektir.

ConcurrentStack aynı zamanda ToArray() property'sini destekler ve stack'i array'a convert etmemize olanak sağlar.

var integerArray = concurrentStack.ToArray();

Stack'in içerisinde item var mı yok mu diye kontrol etmek istediğimizde ise IsEmpty property'sini kullanarabiliriz.

if(!concurrentQueue.IsEmpty)
{
    ;//todo
}

 

ConcurrentQueue

ConcurrentQueue FIFO (first in first out) prensibine göre çalışan bir data stracture dır. Generic ConcurrentQueue<T> thread safe olan Queue<T>'nin karşılığı olarak düşünebiliriz ve yinde ConcurrentQueue de ConcurrentStack gibi .Net Framework 4.0 ile hayatımıza girmiştir.

 ConcurrentQueue<T>'nin bazı önemli metotlarını aşağıdakiler olarak sıralayabiliriz.

  1. Enqueue(T element)  T tipindeki objeyi queue'ye eklemek için kullanılır,
  2. TryPeek(out T) queue'da yada kuyrukta bulunan bir sonraki item'ı almak için kullanılır ancak bu item'ı queue'dan silmez, başarılı ise true değilse false döner,
  3. TryDequeue(out T) bu metot kuyruktaki ilk objeyi almak için kullanılır. TryPeek(out T) metodunun tersine objeyi aldıktan sonra kuyruktan siler, başarılı ise true değilse false döner,

Aşağıda bulunan code-snippet'ı nasıl integer değerler saklamak için ConcurrentQueue instance oluşturulur göstermektedir.

var concurrentQueue = new ConcurrentQueue<int>();

Kuyruğa integer değer atmak için ise aşağıdaki gibi Enqueue metodu kullanılabilir.

concurrentQueue.Enqueue(100);

Full kulalnıma bakacak olursak aşağıda bulunan  metot 1'den başlayıp 100'e kadar olan sayıları ConcurrentQueue'ye atıp sonrasında ConcurrentQueue'den okuma işlemini yapıp ekrana sayıları display etmektedir..

	public static void Main()
	{
		    var concurrentQueue = new ConcurrentQueue<int>();

            for (var i = 1; i < 100; i++)
            {
                concurrentQueue.Enqueue(i);
            }

            int queueData;

            while (concurrentQueue.TryDequeue(out queueData))
            {
                Console.WriteLine(queueData);
            }
	}

Projeyi çalıştırdığınızda 1'den başlayıp 100'e kadar olan sayıları ekrana display edecektir.

Hem ConcurrentStack hem de ConcurrentQueue class'ları thread safe'dirler ve internal olarak locking ve synchronization konularını yönetebilirler.

ConcurrentQueue aynı zamanda ToArray() property'sini destekler ve queue'yu array'a convert etmemize olanak sağlar.

var integerArray = concurrentQueue.ToArray();

Queue'nun içerisi boş mu dolu mu diye kontrol etmek istediğimizde ise IsEmpty property'sini kullanarabiliriz.

while(!concurrentQueue.IsEmpty)
{
     int result;

     concurrentQueue.TryDequeue(out result);

     Console.WriteLine(result);
}

Thread safe programlama ile uğraşıyorsanız ConcurrentStack ve ConcurrentQueue oldukça faydalı sınıflardır. Özellikle mesajlaşma yapıları & web service vs. gibi geliştirmeler gerektiren yerlerde biz developer'lara oldukça kolaylıklar sunmaktadır, unutmamakta fayda var :)

Quartz .Net Nedir Nasıl Kullanılır ?

Quartz.Net, geliştirdiğiniz web tabanlı projelerde scheduled task yani zamana dayalı planlı görevler çalıştırmak istediğinizde çok kolay bir şekilde implement edip kullanabileceğiniz open source bir kütüphanedir. Aslında bir çok kişi bu tarz işlemleri yapmak için bir console uygulaması geliştirir ve windows server'da task schedular'a bu uygulamayı ne zaman çalıştıracağını söyler ve iş biter. Ancak gerek sunucuya erişimin kısıtlı olduğu durumlarda, gerekse ikinci bir uygulama geliştirmenin yol açtığı efor düşündüğünde quartz.net daha avantajlı gibi görünüyor.

Kullanım olarak iki aşamadan oluşuyor diyebiliriz,

  1. Çalışacak olan Job'ı belirleme,
  2. Hangi zaman aralıklarında çalışacağı bilgisi set edilir.

Quartz 3 ana bileşenden oluşur, job, trigger, schedular. Job yapılacak olan iştir. Trigger job'ın ne zaman, ne şekilde tetikleneceği emrini veren & tetikleyen yapıdır. Job ve Trigger ikisi birlikte schedular'a register olurlar ve kısaca çalışma şekli olarak; job schedular üzerinden trigger tarafından çalıştırılır diyebiliriz.

Örnek bir case üzerinden ilerleyelim; bir banka web service projemiz olsun ve müşterilere her gün saat 13:30 da push notification yollayan bir kampanyalar job'ı geliştirelim.

1. Quartz.Net Kurulumu

Kurulum için projenize sağ tıklayıp Manage Nuget Packages dedikten sonra aşağıdaki gibi nuget'ten projeyi install edebiliriz.

 

2. Job'ı Oluşturma

Bunun için ilk olarak IJob interface'inden implement olan CampaignPushJob adında bir class oluşturalım. IJob ile birlikte class'ımız Execute adında bir metoda sahip olur ve job çalışırken aslında bu metot içerisindeki akış çalışır diyebiliriz.

    public class CampaignPushJob : IJob
    {
        public void Execute(IJobExecutionContext context)
        {
            var userRepository = new UserRepository();
            var userList = userRepository.GetPNUserList();

            var pushNotText = "Size özel konut kredisi teklifleri.";

            var pnService = new PnService();
            pnService.SendPush(pushNotText, userList);
        }
    }

Örnekte PnService adında bir service'imiz olduğunu varsayalım ve SendPush metodu ile kullanıcılara notification gönderiliyor olsun.

3. Schedular Oluşturma

Bu adımda CampaignPushJobScheduler adında içerisinde ne zaman ve neyi çalıştıracağı bilgisini verdiğimiz schedular'ımızı yazıyoruz. JobBuilder.Create<CampaignPushJob>().Build() satırında da görüldüğü gibi bizden bir adet IJob interface'ini implement etmiş class istemekte. Bizde bir üstte yazdığımız CampaignPushJob'ını buraya set ediyoruz. Ne zaman çalıştıracağı bilgisini ise TriggerBuilder'ı initialize ederken set edeceğiz. Örneğimizde her gün saat 13:30 da çalışmasını istediğimiz bir job çalıştırmak istediğimiz için TriggerBuilder içerisinde aşağıdaki gibi bir tanımlama yaptık.

    public class CampaignPushJobScheduler
    {
        public static void Start()
        {
            IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
            scheduler.Start();

            IJobDetail job = JobBuilder.Create<CampaignPushJob>().Build();

            ITrigger trigger = TriggerBuilder.Create()
                .WithDailyTimeIntervalSchedule
                  (s =>
                     s
                    .OnEveryDay() //hergün çalışacağı bilgisi
                    .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(13, 30)) //hergün hangi saatte çalışacağı bilgisi
                  )
                .Build();

            scheduler.ScheduleJob(job, trigger);
        }
    }

4. Schedular Start Etme

Bu adıma kadar job, schedular ve trigger bilgilerimizi oluşturduk ve artık uygulama ayağa kalktıktan sonra schedular'ı start etmemiz gerekiyor. Geliştirmekte olduğumuz proje bir Asp.Net projesi olduğundan bu işlemi kolayca Global.asax dosyası içerisinde bulunan Application_Start metodunda yapabiliriz. Tek yapmamız gereken CampaignPushJobScheduler'ın içerisinde bulunan Start metodunu çağırmak.

    public class Global : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            CampaignPushJobScheduler.Start();
        }
    }

Quartz.Net implement etme işlemimiz bu kadar. Artık web projemiz bir kere ayağa kalktıktan sonra set ettiğimiz değerler doğrultusunda çalışacaktır.

Quartz schedular işlemler için .Net tarafında kullanılabilecek en yaygın kütüphanelerden bir tanesidir. Örnekte her ne kadar az kullanmış olsak da trigger time özelliğiyle ilgili kullanıcılara bir çok seçenek sunmakta. Bunlarla ilgili ayrıntılı bilgiyi bu linkte bulabilirsiniz.

Web Api Projelerinde Versiyonlama

Yazmış olduğunuz Api projelerinde versiyonlama yapmak oldukça önemlidir. Düzenli bir versiyonlama yaparak hem mevcut hemde yeni versiyonu kullanacak olan client'lar için tutarlı response'lar sağlayabilirsiniz. Örneğin v1.0 için çalışan listesi dönen bir api metodu yazdınız ve mevcutta kullanan kullanıcılar var. production'da. Sonra v2.0 de dediler ki çalışan isimlerinin yanında departman kodu da yazsın (Caner Tosuner, IT gibi). Her ne kadar kullanıcılar v2.0 geliştirmesini yapacak olsalarda belli bir süre daha eski versiyon için destek veriyor olmak gerekir çünkü bütün herkes aynı anda v2.0'a geçemeyebilir veya v1.0 da kalmak isteyebilir. İşte bu gibi durumları en iyi şekilde yönetilmemiz için versiyonlamayı son derece iyi yapmamız gerekir.

Web Api için versiyonlama yapmanın ortak kabul görmüş bir kaç yol bulunmakta;

  • URL de versiyon bilgisini query string olarak geçebiliriz,
  • Request Header'a "appVersion" gibi bir key/value ekleyebiliriz,
  • Genelde media type için kullanılsada AcceptHeader'a versiyon bilgisini ekleyebiliriz,
  • Her request'te BaseRequest'te içerisinde bir versiyon bilgisini alma.

Yukarıda sıralanan 4 yol da versiyonlama için çözüm ancak WebApi'ın son sürümüyle birlikte tercih edilmemeye başlandı hatta ve hatta yanlış çözüm olarak söyleyenler bile mevcut.

WebApi için versiyonlamayı Api metodlarının başına attribute olarak ekleyeceğimiz Route class'ı bulunmakta. Bu class'ı kullanarak kısaca hangi url bilgisi ile o metod çağrılabilir onu belirtmiş oluyoruz.

Burda dikkat etmemiz gereken konuların başında elimizden geldiğince mevcut URL'i bozmamaız gerekiyor.

Şimdi gelelim örneğimize.

Yukarıda bahsettiğimiz örnek üzerinden ilerleyelim ve EmployeeController adında bir ApiController'ımız var ve içerisinde GetEmployeeList() adında bir metod olsun. Bu metod öncelikle v1.0 için geriye çalışan adı ve soyadı bilgilerini içeren bir string array dönsün.

        [HttpGet]
        [Route("api/v1/Employee/{GetEmployeeList}")]
        public IHttpActionResult GetEmployeeList()
        {
            var list = new string[] { "İlhan Mansız", "Tümer Metin" };

            return Ok(list);
        }

Yukarıda ki gibi v1.0 için kullanıcılar bu metodu kullanmakta. GetEmployeeList() metoduna request'te bulunmak için url "http://localhost/api/v1/Employee/GetEmployeeList".

Ancak v2.0 da ad soyad bilgisinin yanında bir de departman adı istenmekte İlhan Mansız, Pazarlama gibi.

Şimdide v2.0 metodunu yazalım.

        [HttpGet]
        [Route("api/v2/Employee/{GetEmployeeList}")]
        public IHttpActionResult GetEmployeeListV2()
        {
            var list = new string[] { "İlhan Mansız, Pazarlama", "Tümer Metin, Satış" };

            return Ok(list);
        }

v2.0 metodunuda yazdık. Bu metoda request'te bulunmak için url "http://localhost/api/v2/Employee/GetEmployeeList".

Yukarıda da bahsettiğmiz gibi en dikkat etmemiz gerekn konuların başında client'ın yapacağı request'i ve api url'ini elimizden geldiğinde aynı tutmak ve bütün versiyonlarda url için değişen tek şeyin versiyon numarası olduğunu belirtmek. Böylelikle service tarafı için ayrımı url de bulunan versiyon numarasından alarak yapabiliriz.

Route attribute'ü kullanmadan versiyonlama işlemini farklı controller'lar kullanarak ta yapabilirsiniz. Üstteki örneğimizde olduğu gibi GetEmployeeList() meetodlarını içeren 2 farklı EmployeeV1Controller ve EmployeeV2Controller adında controller'lar tanımlayabiliriz. Url route işini de WebApiConfig.cs class'ı içerisine yine yukarıdaki url'leri kullanarak gerekli controller'lar da bulunan metodlara yönlendirmeleri yapabilirsiniz. Örneğin şu şekilde;

config.Routes.MapHttpRoute(
            name: "ApiV1_Route",
            routeTemplate: "api/v1/{controller}/{id}",
            defaults: new { controller = "EmployeeV1Controller", action = "GetEmployeeList", id = RouteParameter.Optional }
            );

config.Routes.MapHttpRoute(
            name: "Apiv2_Route",
            routeTemplate: "api/v2/{controller}/{id}",
            defaults: new { controller = "EmployeeV2Controller", action = "GetEmployeeList", id = RouteParameter.Optional }
            );

Yine "http://localhost/api/v1/Employee/GetEmployeeList" metoduna istekte bulunduğumuzda yukarıda yaptığımız config'den dolayı EmployeeV1Controller'ında bulunan GetEmployeeList metoduna yönlendirecektir. v2 için ise EmployeeV2Controller'ında bulunan GetEmployeeList metoduna yönlendirecektir.

 

Versiyonlama büyük çapta olan projeler için oldukça önemlidir. Production'a uygulamanızı çıktıktan sonraki zaman içerisinde tekrardan update'ler çıkmaya devam edeceksinizdir ve versiyonlama yaparak bu işlemleri yürütüyor olmak hem kolaylıklar sağlar hem de projenizi daha yönetilebilir hale getirir.

NLog EventLog

Daha önce ki Log4Net yazımızda log tutmanın ne kadar önemli olduğu ve .Net tarafında kullanabileceğimiz free yapılardan biri olan Log4Net implementasyonunu incelemiştik. Bu yazıda ise bir diğer free framework olan NLOG dan bahsedeceğim. Nlog ile mail atabilir, file log'a, console'a, event log'a log, db ye log atabiliriz.

NLog ile ;

Fatal Üst Seviye : Sistem çökmeleri

Error Uygulama hataları ( Exceptions )

Warn Uyarılar, yinede uygulama çalışmaya devam edebilir.

Info Bilgilendirme herhangi bir amaçlı olabilir. Kullanıcı bilgileri güncellendi vs.

Debug Çalıştırılan sorgular, oturum süresinin bitmesi vs.

Trace Bir eylem başladı diğeri bitti gibi. Örn : Fonksiyon başlangıcı ve bitişi durumları( En Alt Seviye )

seviyelerinde log tutabiliriz. Biz bu yazıda NLog ile Event Log'a nasıl log atılır bunu inceliyor olucaz.

1.Adım İlk olarak işletim daha önceden yğklenmiş olan PowerShell programını yönetici olarak açıyoruz ve MyTestAppLog adında bir event log ve bu event log altında MyTestAppSource adında bir source oluşturuyoruz. Daha sonrasında oluşturduğumuz uygulama ve source isimlerini webconfig tarafta konfigurasyon yaparken ilgili alanlara set edicez.

PowerShell de aşağıdaki gibi code satırını yazıyoruz.

  New-EventLog -LogName MyTestAppLog -Source MyTestAppSource  

Sonradan işletim sisteminde bulunan search kısmına view event log yazarak event viewer'ı açıyoruz ve oluşturduğumuz MyTestAppLogve MyTestAppSource ekranda görmemiz gerekiyor.

 

 

2.Adım İkinci olarak VS da boş bir Web projesi açıp Nuget paket yöneticisine Install-Package NLog yazıp ilgili kütüphaneyi indiriyoruz ve projemizdeki WebConfig dosyası içerisine NLog configurasyonunu sağlayan satırları yazıyoruz.

Configuration tagları arasına aşağıdakini yazıyoruz

<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>  

Daha sonrada configuration tag'inin kapandığı yerin altına da şu şekilde yazıyoruz

<nlog
    autoReload="true"
    throwExceptions="true">
    <variable name="appName" value="MyTestApp" />
 
    <targets async="true">
        <target type="EventLog"
            log="MyTestAppLog"
            name="eventlog"
            source="MyTestAppSource"
            layout="${message}${newline}${exception:format=ToString}"/>
    </targets>

    <rules>
      <logger name="*" writeTo="eventlog" minlevel="Info" />
    </rules>
  </nlog>

 Üstteki configurasyonda ;

  • <target type="EventLog" diyerek EventLog configurasyonu olduğunu belirtiyoruz,
  • log="MyTestAppLog" EventLog adı,  
  • name="eventlog" diyerek isimlendiriyoruz,
  • source="MyTestAppSource" EventLog da tanımlı olan Source adı.
  • layout="$ ile log formatını belirliyoruz.
  • <rules> tag'i arasına yazdığımız kod satırıyla eventlog adında tanımlı olan log configurasyonu için minimum Info seviyesinde log tut.

 

3.Adım Şimdi C# tarafına geçip nasıl log atacağız onu yazalım

 public class NLogManager 
    {
        //WebConfigde tanımladığımız gibi info ve yukarı seviyeler için eventLog'a log atacaktır
        public void LogError(LogModel entry)
        {
           var logger = NLog.LogManager.GetCurrentClassLogger();
           logger.Log(LogLevel.Info, "Info Logged");
           logger.Log(LogLevel.Error, "Error Logged");
        }
    }

Uygulamamızı çalıştırdığımızda event log da aşağıdaki gibi log düştüğünü göreceğiz

 

 

 

Log4Net delete last N days files

Daha önceki yazımızda projemize nasıl Log4Net implemente edilir burada ki yazıda incelemiştik. Log4Net te bahsettiğimiz gibi bizim verdiğimiz kritere uygun boyutlarda belirttiğimiz klasör altında log dosyalarını saklıyordu. Peki ya o klasör altında aylarca yıllarca çok fazla log dosyası oluşur ve GB boyutunda yer kapladığı düşünülürse ne olacak??? "Hadi be sende GB boyutunda log nerde biriksin.." falan diyebilirsiniz ancak projenizin büyüklüğüne göre bunun olma olasılığı oldukta büyük bir ihtimal.
İşte bu gibi durumlardan kurtulmak için bir File Clear türünde bir manager yazmamız gerekmekte.

Akış şu şekilde olacak; Log4Net kendi işini yapıp loglarını dosyalara atmaya devam ederken uygulamanın her Application_Start adımında file clear manager çalışıp Log klasörüne gidip bakıcak. İçerisindeki dosyalardan son 2 haftadan daha eski tarihte oluşmuş dosyalar var ise gidip bu dosyaları silecek.

ilk olarak aşağıda oludğu gibi Log4NetFileCleanUpManager adında bir class oluşturuyoruz

public class Log4NetFileCleanUpManager
    {
        private DateTime _lastDateToKeepStoring;
        public Log4NetFileCleanUpManager()
        {
            _lastDateToKeepStoring = DateTime.Today.AddDays(-14);
        }

        public void CleanUp()
        {
            var repo = LogManager.GetAllRepositories().FirstOrDefault();

            if (repo == null)
                throw new NotSupportedException("Log4Net has not been configured yet !");

            var app = repo.GetAppenders().FirstOrDefault(x => x.GetType() == typeof(RollingFileAppender));
            if (app != null)
            {
                var appender = app as RollingFileAppender;
                string directory = Path.GetDirectoryName(appender.File);
                 CleanUp(directory);
           }
        }

        public void CleanUp(string logDirectory)
        {
            if (string.IsNullOrEmpty(logDirectory))
                throw new ArgumentException("logDirectory is missing");

            var dirInfo = new DirectoryInfo(logDirectory);
            if (!dirInfo.Exists)
                return;
 
            var fileInfos = dirInfo.GetFiles();
            if (fileInfos.Length == 0)
                return;
 
            foreach (var info in fileInfos)
            {
                if (info.CreationTime < _lastDateToKeepStoring)
                {
                    info.Delete();
                }
            }
        }
    }
Kod şu şekilde çalışıyor. Constructor da hangi tarihten sonraki kayıtları silineceği bilgisi veriliyor (Son 14) gün. CleanUp fonksiyonunda Log4Net için webconfigde tanımlı RollingFileAppender bulunuyor ve bunun için tanımlı log klasörünün directory si bulunup CleanUp(string logDirectory) metduna yollanıyor. Burada o klasörün içerisindeki dosyalarda teker teker gezinip oluşturulma tarihlerine bakıyor. Oluşturulma tarihi bizim belirttiğimiz tarihten önce ise o dosyayı siliyor.
 
 
Son olarak Application_Start metodunda aşağıdaki configurasyonu yapmamız gerekmekte.
XmlConfigurator.Configure();
var task = new Log4NetFileCleanUpManager(); 
task.CleanUp();
 

Log tutmak ne kadar önemli ise tuttuğumuz log'un yönetimi ve başa bela olmaması da o kadar önemlidir. Üstte de söylediğim gibi projenin büyüklüğüne göre onlarca GB boyutunda .txt dosyaları ansızın oluşabilir ve sonrasında down !

Log4Net İmplementasyonu

"Log uğrunda ölen olmadıktan sonra log değildir !!"

Önceki çalıştığım firmalardan birinde uzun soluklu bir bankacılık projesi geliştiriyorduk ve daha önce ilgilenen arkadaşlar bilirler ki development süreci oldukça meşakatlidir. Önce Dev de çalış sonra Test'e at sonra UAT e al sonra PROD a taşı orda testleri yap vs. vs. bir sürü ortam birsürü bug ve en önemlisi yüzlerce şöyle cümleler duyabilirsiniz "TEST te sıkıntı yok ama UAT de çalışmıyor yada sadece PROD da rastladığımız bir durum..." bu gibi durumlarda loglardan yürüyerek ilerlemek sorunun kaynağına en kısa sürede ulaşmanızı sağlayabilir. Tabi log alt yapınız var ise :) Ama bitmedi log altyapınızın olmasıda o loga bir bakış attığınızda ahanda tamam sorunu buldum diyebileceğiniz anlamına gelmeyebilir. Çok fazla yaşadığım bir olaydan bahsetmek istiyorum ve bir arkadaşımın bu olay sonrası projedeki Test Müh. arkadaşa söylediği bir cümle varki efsane :)

Birgün yine üstte bahsettiğim bankacılık projesinde dev yapıyorum ve ekip yaklaşık olarak 10 kişi falan (.net-ios-android-pm-tester vs.). Yine o sihirli cümle geldi ve test müh arkadaş android yazan arkadaşa "droidci arkadaş PROD da rastlanan bir hata varmış ve müşteri ilgili maddeyi JIRA da açmış ve logları paylaşmış. Sorunu kısa sürede çözebilir misin.." vs şeklinde bir cümle kurdu. Developer arkadaş JIRA yı açtı madde yi okudu ve loglara baktıktan sonra test müh arkadaşa "maddeyi bulamadım, müşteriye geri assign ediyorum.." gibi birşey söyledi ve test müh arkadaş da "ama nasıl bulamazsın loglarıda yollamışlar ki.. -%&?!?.." Bu cümleye atarlanan developer arkadaş işte o anda o efsane atasözünü söyledi. "Log uğrunda ölen olmadıktan sonra LOG değildir arkadaşım..!" Sonuç olarak ne oldu sizce ? Log var madde var ama çözüm için elde hiçbir şey yok. Yazılım projelerinde çok büyük öneme sahip olan log yapıları emin olun hayat kurtarır.  Çeşitli loglama türleri vardır. DBLog, FileLog vs gibi. Bu örnekte hemen hemen herkesin en azından duymuş olduğu Log4Net kütüpanesini kullanarak orta çaplı bir FileLog yapısı nasıl kurulur ondan bahsedicem. Log4Net için öncelikle Nugetten ilgili dosyayı indirip projemize kuruyoruz Install-Package log4net    Daha sonra aşağıda olduğu gibi ILog adında bir interface tanımlıyorum. Parametre olarak LogModel türünde bir obje alıyor olacak. Bu obje içerisinde loglamak istediğimiz alanlar mevcut olacak.

public class LogModel
    {
        public string ExcMessage{get;set;}
        public Exception ExceptionModel{get;set;}
      }

  public interface ILog
    {
        void Log(LogModel entry);
     }

Log4Net için WebConfig de birtakım ayarlar yapmamız gerekiyor. Bu ayarların kapsamı çok fazla ancak kısaca özetlemek gerekirse LogDosyalarının adlandırılması, LogDosyalarının bulunacağı path bilgisi, max kaç MB lık dosyalar tutulacak ve enfazla kaç dosya olmasına izin verilecek gibi bir çok LogConfig Settings diyebileceğimiz şey içermekte. Bu projedeki WebConfig dosyası asağıda olduğu şekildedir.

 

 <appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">

      <file value="LogFiles\\" />

      <appendToFile value="true" />

      <DatePattern value="yyyy\\\\MM\\\\dd'.day.logs.txt'" />

      <rollingStyle value="Date" />

      <maxSizeRollBackups value="7" />

      <param name="StaticLogFileName" value="false" />

      <layout type="log4net.Layout.PatternLayout">

        <conversionPattern value="%newline%date %newline%logger [%property{NDC}] %newline>> %message%newline" />

      </layout>

     </appender>

 

Şimdi sırada Loglama işlemini yönetecek bir Log4Manager' ihtiyacımız var. Burda yarın bir gün XManager YManager da tanımlayabiliriz. Bunun bir çok implementasyonu olabilir. Ben böyle birşey kullanmayı tercih ettim. ILog, IDisposable interfacelerinden türeyen Log4NetLogManager adında bir class yazıyoruz. Class ın içeriği aşağıdaki gibi olacaktır. 

 

 

public class Log4NetLogManager : ILog, IDisposable
    {
         private log4net.ILog log4Net;
         public Log4NetLogManager()
        {
            log4net.Config.DOMConfigurator.Configure();
            log4Net = log4net.LogManager.GetLogger(typeof(Log4NetLogManager));
        }
         public void Log(LogModel entry)
        {
            log4Net.Error(entry.ExpMessage, entry.ExceptionModel);
        }
         public void Dispose()
        {
            if (log4Net != null)
                log4Net.Logger.Repository.Shutdown();
        }
    }

 

İşlemlerimiz bu kadar. Bu saatten sonra tek yapmamız gereken bu manager'ı kullanan logic ler yazmaktır. Bir sonraki örneğimizde ASP.Net WebApi tarafında ExceptionHandling olaylarına girip yukarıda yazmış olduğumuz Manager'ı kullanan bir app yazacağız.

 

 

Web Service'de Güvenlik | Soap Header

Bu makalede Web Service Nasıl Yazılır ? makalesinde bahsettiğim üzre Web Service'de Soap Header Kimlik Denetimi yani Username Password ile service'i kullanabilme konusundan bahsediyor olacağım.

 

Web Service'in tanımına değinecek olursa M2M iletişimin önünü açmak ve back-end sistemlerde var olan çeşitli database yada enterprise yapıların diğer platformlarla iletişimini güvenli bir şekilde sağlamak için geliştirilmiş yapılardır, Kısacası farklı platformların haberleşmelerini sağlamak için kullanılan teknolojilere verilen isimdir. Web Service Nasıl Yazılır ? makalesinde local'de bulunan Northwind database'ine bağlanarak Employees tablosundaki çalışanların bilgilerini getiren bir .asmx servis geliştirmiştik, hatırlicak olursanız projeyi kendi bilgisayarımızda çalıştırdığımızda aşağıdaki resimlerde olduğu gibi çok rahat bir şekilde metod isimlerine tıklayıp Çağır butonuna bastığımızda servis çalışıp istenilen verileri bize getiriyordu.

 

 Yukarıda da gördüğümüz gibi her şey sorunsuz çalışıyor ancak anormal olan durum service URL'ini bulan herhangi biri gidip aynı service'i kullanarak database'de kayıtlı bulunan verilere erişebilir ve bu durum hiç bir şirket yada service sahibi tarafında istenilen bir durum değildir.Güvenlik durumunda web service'ler için kullanılabilecek çeşitli yöntemler vardır bunlardan ençok tercih edileni kuşkusuz Soap Header'dır. Soap Header kullanarak service tarafında belirleyeceğimiz UserName ve Password ile Client'ın service'e bu UserName ve Password kullanarak girişine izin vereceğiz.

1.Adım | Proje ve Class'ın Oluşturulması

  • Visual Studio'da "Northwind" isminde bir adet ASP.Net Empty Web Application oluşturuyoruz. Sonrasında proje içerisine aşağıda olduğu gibi 1 adet User adında "SoapHeader"'dan kalıtım alan class oluşturuyoruz ve içerisine service'e login esnasında kullanacağımız attributeleri yazıyoruz.

   

 public class User : SoapHeader
    {
        public string Username;
        public string Password;
    }

 

2.Adım | Service Sayfasının ve WebMethod'un Oluşturulması

  • Bu adımda projemize sağ tıklayıp 1 adet "MyService" adında Web Service sayfası ekliyoruz ve içerisine aşağıda olduğu gibi kullanacağımız WebMethod'larını yazıyoruz

 

public User User;  //Oluşturmuş olduğumuz User class'ından 1 adet User tanımlıyoruz
 
[WebMethod]        
[SoapHeader("User")]  //Güvenlik burda devreye giriyor, WebMethod'a SoapHeader içerisinde bulunan User nesnesi ile giriş yapıldığını belirtiyoruz
public string KullanıcıGirisi()
{ 
   //Client tarafında UserName ve Password bilgileri doğru girildiğinde ve girilmediğinde yapılması gerekenleri belirtiyoruz
   if (User.Username == "caner" && User.Password == "tosuner")
      return "Kullanıcı Girişi Başarılı";
   else
      return "Kullanıcı Girişi Başarısız";
 }

 

 

Bütün bu işlemler sonrasında Projemizin Solution bölmümü sağ tarafta olduğu gibidir

 

 

 3.Adım | Client Tarafından Service'e İstekte Bulunulması

  • Solution'a sağ tıklayıp 1 adet "Client" adında ConsoleApplication oluşturuyoruz ve sonrasında solution'da bulunan iki projenin haberleşebilmesi için Client ta bulunan References'a sağ tıklayıp "Add Service Reference" diyoruz ve açılan penceren Discover'a tıklayıp solution'da bulunan Northwind projesini görmesini sağlayıp Service bir isim Verdikten sonra OK butonuna basıyoruz. Client'ta bulunan Program.cs class'ını açıp Main metodunun içerisini aşağıda olduğu gibi dolduruyoruz.

 

Northwind.ServiceReference1 sc = new Northwind.ServiceReference1 ();  //Referans olarak eklediğimiz service'in ismi
Northwind.User user= new Northwind.User ();   //Servis içerisinde bulunan SoapHeader'dan kalıtım almış nesnemizi seçiyoruz.
 
user.Username = "caner";   //Servis tarafında oluşturduğumuz Username ve Password bilgilerini user nesnesinin attributlerine yazıyoruz
user.Password = "tosuner";
 
string result= sc.KullanıcıGirisi(user);  //KullnıcıGirisi WebMethod'una user nesnesini veriyoruz ve return edilen sonucu ekranda yazıdırıyoruz.
Console.WriteLine(result);
 
Console.ReadKey();

 

Projeyi çalıştırdığımızda UserName ve Password bilgilerini doğru yazdığımızdan aşağıda olduğu gibi Kullanıcı Girişi Başarılı mesajını ekranda görmüş olacağız.