Asp.net Web Api ve Content Negotiation Kavramı

20 Mar

Günümüzde artık internet dünyası sadece kişisel bilgisayarlarda internet tarayıcıları tarafından görüntülenebilen web sitelerinden oluşmuyor. Mobil cihazlar, Televizyonlar, Akıllı Evler Aletleri v.b gibi birçok cihaz artık internet aleminde kaynak tüketimine dahil olmuş durumdadır. Şüphesiz bu giderek daha da genişleyecek. Bu çeşitlilik karşısında artık internet dünyasına sunulan bilgilerin nasıl temsil edildiği öne çıkmıştır. Örneğin bir cihaz JSON veri tipiyle işlem yaparken diğeri XML veri tipini tercih ediyor olabilir. HTTP üzerinden kaynak tüketimi göz önüne alındığında verilerin temsili(representation) content negotaition kavramı ile bütünleşiyor.

Bir önceki yazıda HTTP protokolüne özel bir kavram olan Content Negotiation kavramından genel olarak bahsetmiştik. Bu yazıda ise content negotiation konusunu Asp.Net Web api tarafından incelemeye çalışacağız.

Asp.net Web Api teknolojisi Content Negotiation ile uyumlu çalışmaktadır. Ancak bu kavram Web Api teknolojisinin bir parçası gibi anlaşılmamalıdır. Content Negotation kavramı W3C(World Wide Web Consortium) tarafından tanımlanmış bir internet içerik standartıdır. RFC(Request For Comment) döküman numarası 2616‘dır.

Content Negotiation kavramı Asp.net Web Api tarafında, kullanıcıların talebine göre verinin hangi biçimde sunulacağını belirlemek için kullanılan sunucu odaklı bir çözüm yöntemi olarak kullanılmaktadır. Kullanıcılar talep ettikleri ortam döküman türlerini HTTP header bilgisi olarak belirleyebilirler. Talepteki Header bilgisi Web Api tarafından değerlendirilerek uygun şekilde bir cevap oluşturulur ve HTTP protokolü üzerinden kullanıcıya sunulur.

Web Api bir kullanıcının hangi niyetle kaynak talep ettiğini anlamak için talebin(HttpRequest) Header bilgilerini kontrol eder. Header bilgilerinde içerik ile ilgili kısımlar şu şekilde sıralanabilir:

  • Content-Type: API Burada belirtilen formatta veri sunar.
  • Accept: Kullanıcı burada kabul ettiği veri türlerini belirtir. (application/json gibi)
  • Accept-Charset:Kabul edilen karakter kodlaması. (UTF-8 gibi)
  • Accept-Language: Kabul edilen dil seçeneği. (en-us, tr-tr gibi)
  • Accept-Encoding:Kabul edilen içerik kodlaması. (gzip gibi)

Web Api uygulamasına Fiddlr gibi bir araç yardımıyla sorgulamalar yaparak durumu inceleyebiliriz.

Örneğin http://localhost:15063/api/values için bir talep gönderdiğimizde,

fidd1
Header bildirimi yok

Web Api için oluşturulan talepte içerik bilgisine ait bir bildirim yapılmadı. Ancak sonucun JSON formatında geldiğini gözlemliyoruz. Bunun sebebi Web Api uygulamasının varsayılan olarak JSON formatında veri oluşturmasıdır.

Şimdi içerik talebini JSON değilde XML şeklinde istemeyi deneyelim. Bu durumda Content-Type: application/xml şeklinde bir Header bildirimi yapmamız gerekmektedir.

Content-Tyle:application/xml
Content-Tyle:application/xml

Dikat edecek olursak aynı URI üzerinden aynı kaynağa eriştiğimiz halde dönen cevap XML formatı şeklinde oldu. Yani kaynağın temsil ediliş şeklini HTTP Content Negotiation ile değiştirmiş olduk.

Farklı formatlarda veri alma talebini bir MVC uygulaması veya Web Froms ile yapmış olsaydık ya farklı URI üzerinden (sayfa veya action metod) oluşturacaktık, ya da aynı URI adresine parametre olarak kaynak türünü xml şeklinde belirtmiş olmamız gerekebilirdi.

Kullanıcılar Web Api uygulamalarına hangi türde veri kabul ettiklerini Accept header bilgisi ile bildirirler. Örneğin üç farklı formatta veri kabul ettiğimizi bildirecek olursak: Accept: application/xml, application/json, text/javascript

Accept Header
Accept Header

Bu durumda varsayılan değer olan JSON formatı şeklinde veri döner. application/json seçeneğini kaldırırsak bu sefer de XML veri döner.

Web Api tarafında medya tiplerine göre uygun içerik üretmek için Media Type Formatter sınıfları oluşturulur ve akışa dahil edilir. Eğer JSON veya XML gibi türler dışında kendi özel türümüzü oluşturmak istiyorsak System.Net.Http.Formatting.MediaTypeFormatter sınıfından türeteceğimiz sınıflarda istediğimiz veri formatlarını oluşturabiliriz.

 

HTTP Content Negotiation Kavramı

16 Mar

Content Negotiation işleyişi HTTP protokolüne özgü bir kavramdır. Anlam olarak tercüme edecek olursak, client ve server arasında yapılan bir içerik anlaşması veya müzakeresidir diyebiliriz.  Amacı, aynı URI ile farklı döküman türlerinde içerik sunabilmektir. Yani daha genel bir ifadeyle kaynak gösterim şeklinin kullanıcılar tarafından belirlenmesi diyebiliriz. Content Negotiation ile ilgili resmi dökümanlar buradaki w3.org sayfasında bulunmaktadır.

Content Negotaition 1.1
Content Negotaition 1.1

Kullanıcıların sunucu kaynaklarına ulaşmak için kullandıkları internet tarayıcıları kendi yetenek türlerine göre kaynak türünü seçmek için HTTP protokolünde belirlenmiş olan kurallara uygun talepte bulunurlar. Örneğin bir X tarayıcısı JPEG dökümanlarını işleyebilecek yeteneği yoksa fakat PNG dökümanlarını işleyebiliyorsa sorgu sırasında bu isteğini HTTP Accept Header (istek başlığı) bilgisi olarak sunucuya iletir. Örneğin Accept istek başlığı ile sunucuya kullanıcının medya türü tercihini Accept:image/png şeklinde belirtir.

Burada Accept istek başlıkları superset/subset biçiminde temsil edilmektedir. Örneğin Accep:image/png örneğinde “image superset’tir, “png ise subset’tir.

Örnek medya türleri için Accept Header bilgileri:

  • Accept: application/json
  • Accept: image/png
  • Accept: image/*
  • Accept: text/xml

Accept istek başlığında birden fazla tercih de belirtilebilir. Örneğin:

Accept: text/html, text/xml, image/jpeg, */*

Burada talebi gönderen taraf text/html, text/xml, image/jpeg ile açıkça belirtilmiş medya türlerinin tercih ettiğini, bunun yanında */* ile farklı medya türleri varsa onları da kabul edebileceğini  ifade etmektedir. Burada sunulan bir dizi medya türü vardır. Bu medya türlerine üstünlük katsayısı vererek öncelik tercihi yapmak da mümkündür. Örneğin:

Accept: text/html, text/xml, image/jpeg, */* ; q=0.01

Üstünlük katsayısı 1.0 ve 0.0 arasında bir değer alır ve “q” ile gösterilir. Burada text/html gibi üstünlük katsayısı belirtilmemiş türlerin katsayısı otomatik olarak 1.0 atanır. Ancak */* kalıbı 0.01 gibi düşük bir öncelik olarak belirlenmiştir.

İnternet tarayıcıları medya tiplerine göre kendi öncelik katsayılarını belirlerler. İnternet tarayıcıları kullanmadan fiddler gibi bir araç yardımıyla HTTP talepleri oluşturup, Header seçeneklerini kendimiz belirleyebiliriz.

Bir kaynaktan gelen dökümanın medya türü farklı olabileceği gibi dil seçimi de farklı olabilmektedir. Aynı şekilde Accept Header bilgisinde istediğimiz dili belirtebiliriz. Örneğin, Accept-Language: tr şeklinde.

İçerikle ilgili bir diğer Header bilgisi ise Content-Type şeklinde belirtilen ve kaynağa erişmek isteyen kullanıcının hangi medya türünde döküman istediğini bildiren içerik bilgisidir. Örneğin Content-Type:application/json şeklinde belirlenen bir Header bilgisi kullanıcının JSON veri istediğini belirtmektedir.

Günümüzde kaynak kullanımı sadece tarayıcılar tarafından değil, mobil uygulamalar tarafından da ağırlıklı olarak kullanıldığı göz önüne alındığında Content Negotiation kavramı özellikle REST servisler oluşturulurken dikkat edilmesi gereken konulardan bir tanesidir. Çünkü REST servisleri HTTP tabanlı çalıştıkları için servis geliştiriciler olarak HTTP dünyasını ve kurallarını iyi tanımamız gerekmektedir. Bu sayede neyi neden kullandığımızı bilerek ilerleyebiliriz.

.Net Framework Gelişim Tarihine Genel Bakış

7 Mar

Microsoft .Net Framework ile ilgili tarihi geçmişe bir göz atarak eskiden günümüze(2015) yani 4.5.1 framework sürümüne kadar nasıl bir gelişimin olduğunu genel hatlarıyla incelemeye çalışalım.

.Net Framework 1.0: 2002 yılında duyurulmuş ilk versiyonsdur. Visual Studio .Net geliştirme aracı ile birlikte sunulmuştur.

.Net Framework 1.1: 2003 yılında duyurulan bu versiyon ile birlikte Visual Studio 2003 geliştirme aracı da piyasaya sürülmüştür. .Net Framework 1.1 ile birlikte ilk defa Asp.Net Mobile control araçları denenmiştir. Ayrıca birden fazla ve farklı Framework sürümünü aynı bilgisayarda çalıştırabilme özelliği olan side-by-side özelliği eklenmiştir. Güvenlik ile ilgili yeni gelişmeler eklenmiştir.

.Net Framework 2.0: 2005 yılında Visual Studio 2005 geliştirme aracıyla birlikte duyurulmuştur. Bu framework ile birlikte Generic, Nullable tipleri, IPV6 desteği ve CLR 2.0 gibi önemli özellikler eklenmiştir.

.Net Framework 3.0: 2006 yılında duyurulmuş olup Visula Strudio 2005 üzerinden devam etmiştir. Bu sürüm ile birlikte WCF(Windows Communication Framework), WPF(Windows Presentation Framework), WF(Workflow Foundation) gibi yenilikler geliştiricilerle buluşturulmuştur. Bu yenilikler ile iletişim, sunum  ve iş akışları gibi ayrımlar ilk defa belirgin bir şekilde altyapı olarak yapılmış oldu.

.Net Framework 3.5: 2008 yılında Visual Studio 2008 gelitirme aracı ile beraber duyurulmuştur. LINQ ve Addin/Plugin Model gibi özellikler eklenmiştir.

.Net Framework 4.0: 2010 yılında Visual Studio 2010 il birlikte duyurulan bu sürümde bir çok yeni özellik eklenmiştir. Parallel Computing, Code Contracts, Lazy Initializations, Dynamic Language RuntimeIn-process side-by-side hostingBackground garbage collection ve CLR 4.0 şeklinde önemli eklemeler yapılmıştır.

.Net Framework 4.5: 2012 yılında Visual Sturio 2012 ile beraber duyurulmuştur. Paralel Programlama üzerinde yeni gelişmeler async/await yöntemi, asenkron işlemler, 64-bit platformlarda 2 gigabayt’tan (GB) büyük diziler için destek, Sıkıştırılmış dosyaların boyutunu azaltmak için Zip sıkıştırma işlevinde geliştirmeler yapılmıştır.

.Net Framework 4.5.1: 2013 yılında Visual Studio 2013 ile birlikte duyurulmuştur. Derlemeler için otomatik bağlama yeniden yönlendirme, .NET Framework güncelleştirmelerinden sonra daha hızlı uygulama başlatma, çok çekirdekli JIT geliştirmeleri ve ASP.NET uygulama askıya alma gibi ek performans artışları yapılmıştır. Sunucularda arka plan çöp toplama işlemi ile daha iyi performans sağlanmıştır.

Yazılım Proje Dizin Yapısı Oluşturmak

4 Mar

Projelerimizi oluştururken kendimize bir dizin standardı belirleyip, belirlediğimiz bu düzene sadık kalarak ilerlemek işlerimizi bir nebze kolaylaştıracaktır. Şu an için kodlama standartları gibi geleneksel bir standart yayınlanmamış olsa da programcıların yaygın olarak kullandığı bir dizin yapısı vardır.  Bu dizin yapısına göre kaynak kodlar, dış kaynak kütüphaneleri ve dökümantasyon gibi kavramlar ayrı ayrı tutulmak üzere belirlenmiştir.

Proje Dizin Yapısı
Proje Dizin Yapısı

Bir çok açık kaynak projeyi indirip incelediğimizde bu yapıya benzer bir dizin ağacına rastlarız. Bu ağaçta belirtilen kavramlar:

  • doc: Kod ile ilgili dökümanların bulunduğu dizindir. Geliştiricinin karşılaştığı sorunlar, çözümler, resimler, ip ucu olabilecek kısa yollar v.s bu dizinde bulunur.
  • lib: Proje için gerekli olan dış kaynak kütüphaneleri bu dizinde bulunur. Örneğin Nuget paketleri, Github üzerinden indirilip referans alınabiliecek başka kaynak kodlar.
  • src: Porjemizin kaynak kodunu barındırdığımız yer bu dizindir. Örneğin Visual Studio Solution veya Eclipse projesini bu dizine olşutururuz.
Proje ve Kaynak Kod
Proje ve Kaynak Kod Yapısı

Yukarıda lib dizininde dış kaynaklı kütüphanelerin bulunacağını belirtmiştik. Burada Visual Studio ile çalışanların Nuget paketlerini otomatik olarak lib dizinine indirebilmesi için gerekli bir ayar yapılmalıdır. Proje kaynak kod dizini içerisine yani yukarıda ki şemada src olarak adlandırılmış olan dizine nuget.config adında bir ayar dosyası oluşturulmalıdır . Bu ayar dosyasına şu satırları eklemek yeterlidir.

<settings>
    <repositoryPath>..\lib</repositoryPath>
</settings>

Artık Nuget Package Manager tarafından indirilen dosyalar doğrudan lib dizinine kaydedilir.

Bu yapıda oluşturulan çalışmaları versiyon kontrol sistemlerine derli toplu bir şekilde aktarmak mümkün hale gelmektedir.

NOT: Bu ayarlar Visual Studio 2013 üzerinde başarıyla denenmiştir. Sonraki çıkacak versiyonlarda bu ayarların değişme olasılığı vardır.

Domain Driven Design ne zaman uygulanmalıdır

28 Şub

Bir önceki yazının konusu Domain Driven Design(DDD) konulu bir kitap tanıtımıydı. Bu yazıda DDD yaklaşımının hangi durumlarda kullanılması gerektiğini bir kaç cümleyle özetlemeye çalışacağım. Yaklaşımın özelliklerini daha ilerki yazılarda fırsat buldukça anlatmaya çalışacağım.

Domain Driven Desing(DDD) kavramı karmaşık problemlerin küçük parçalara bölünerek ele alınmasını amaçlayan bir yaklaşım tarzıdır. Karmaşık problemlerin parçalara ayrılması da aslında çözülmesi gereken bir problemdir. Taleplerin alınması, iş kurallarının
belirlenmesi ve taleplerin yazılım terminolojisine uygun hale getirilmesi sürecin başlıca gereksinimlerdir. DDD birbiriyle ilişkili iş kurallarının ele alındığı bir yöntem olması sebebiyle çok geniş kapsamlı kurumsal yazılımlar geliştirirken uygulanması gereken bir yaklaşımdır. Örneğin hava yolu rezervasyon sistemleri, e-ticaret uygulamaları, bir devlet kurumu bünyesindeki otomasyon sistemleri şeklinde örnekler verilebilir. İş kurallarının sık olarak değiştiği ve taleplerin sürekli artacağı öngörülen yazılım sistemleri için yazılımın geleceğine aydınlık bir altyapı tasarlamak amacıyla uygulanabilir. Örneğin kanun ve yönetmeliklerin çok sık değiştiği  kurumlarda geliştirilen yazılımları gibi. Ancak karmaşık iş kuralları olmayan basit sistemler için DDD uygulamak uygun olmaz. Örneğin blog sayfaları veya ürün tanıtım amaçlı web projeleri için DDD uygulamak uygun değildir. Bu tür uygulamalar sadece  veri erişimi ve görüntülemesi amaçlanmıştır. Bu tür uygulamaları veriyi kullanıcı ara yüzüne direk aktaran teknikler ile geliştirmek mantıklı bir seçim olacaktır. Aksi taktirde  basit olan bir çözümü, karmaşık hale getirmiş oluruz.

Kitap – Domain Driven Design

27 Şub
Domain Driven Design
Domain Driven Design

Kitap tavsiye kategorisinin bu bölümünde Domain Driven Desing Tackling Complexity in the Heart of Software isimli kitaptan bahsedeceğiz.

Kitap Eric Evans tarafından kaleme alınmış olup dili ingilizcedir. Basılı kitabı buradan temin etmek mümkündür.

Kitapta karmaşık yapıdaki yazılımlar geliştirirken nasıl bir yazılım tasarımının uygulanması gerektiği konusu ele alınmıştır. Bu tasarım Domain Driven Design diye adlandırılmıştır.

Domain Driven Design yaklaşımı, problemin çözümünün domain diye adlandırılan bir alanda yapılması esasına dayanmaktadır. Burada problem, bir hava yolları rezervasyon sistemi veya bir e-ticaret sistemi tasarlamak olabilir. Bu noktada müşteri ve programcı karşı karşıya gelmektedir. Müşteri gereksinimleri, Domain Expert denen kişiler tarafından iyi bir şekilde tespit edilerek programcının anlayacağı bir şekle dönüştürülür. Programcı ve Müşteri arasında  sağlıklı bir iletişimin kurulabilmesi için Ubiquitous Language diye adlandırılan ortak bir dil oluşturulur. Gerekli iletişim sağlandıktan sonra sistem programcılar tarafından geliştirilmeye başlar.

Domain Driven Design yaklaşımı bize genel olarak dört temel katman üzerine inşaa önerir. Bunlar:

  • Presentation layer
  • Application layer
  • Domain layer
  • Infrastructure layer

Problemin çözümü için gerekli model Domain Layer üzerinde kurulur. Diğer katmanlar sunum ve iletişim katmanları olarak kullanılır.

Eric Evans’a göre Domain-Driven Design yaklaşımı, yazılım dünyasının popüler konularından birisi oldu ve kapsamlı yazılım projelerinde kullanılıp faydası görüldükçe yaygınlaşacak. Bunun yanında her projede Domain Driven Design yaklaşımını uygulamak yanlış olur. Sadece gerektiğinde kullanmak uygun olacaktır.

Asp.Net Web Api Message Handler Yapısı

21 Şub

Asp.Net Web Api 2 çatısı bünyesinde, Message Handler tipleri HTTP talebini alarak geriye yine HTTP cevap döndüren somut sınıflardır. Message Handler tipleri Web Api iletim hattında(pipeline) adeta bir zincir misali dizilmiş şekilde bulunur. Bu zincirdeki ilk handler talebi alır yapması gereken işlemi bitirdikten sonra talebi bir sonraki handler sınıfına aktarır. Bazı handler sınıfları talebi bir sonraki handler sınıfına aktarmaya gerek duymadan akışı kesip cevabı kullanıcıya döndürebilir. Bu işleyiş şekli Web Api tarafında delegating handler olarak adlandırılır. Asp.Net Web Api 2 versiyonuna ait mesaj yaşam döngüsüne ait şema buradaki posterde sunulmuştur.

Web Api iletim akışına dahil olan mevcut message handler tiplerine örnek verecek olursak:

  • HttpServer, talepleri alır.
  • HttpRoutingDispatcher, route kuralına göre talebi ilgili kaynağa yönlendirir.
  • HttpControllerDispatcher, talebi ilgili controller sınıfına gönderir.

Kendi oluşturduğumuz bir handler tipini akışa dahil etmemiz mümkündür. Örneğin, gelen HTTP talebi eğer PDF tipinde bir dosya istiyorsa cevap olarak yetkisiz kullanıcı şeklinde bir mesaj döndürebiliriz.

Server Handler

Server Handler

Resimde ifade edildiği üzere MessageHandler1 ve MessageHandler2 tipleri geliştiriciler tarafından özel olarak oluşturularak akışa dahil edilmiştir.

Kendi özel message handler tiplerimizi oluştururken, System.Net.Http.DelegatingHandler soyut(abstract) sınıfından bir sınıf türetmeliyiz. Daha sonra bu sınıfın SendAsync() metodunu override ederek talebi değerlendirebiliriz.

public class MessageHandler1 : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(
          HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Talebin kontrol edildiği alan.

        var response = await base.SendAsync(request, cancellationToken);

        // Cevabın kontrol edildiği alan. (await, base.sendAsync() işlemi bitene kadar süreci bekletir)

        return response;
    }
}

SendAsync() metodu HTTP talebini  HttpRequestMessage tipinde alır ve geriye HttpResponseMessage tipinde asenkron olarak bir mesaj döndürür. base.SendAsync() metosu ise  mesajı Web Api akışındaki mevcut handler tipine aktarır. Bu durumda kullanıcıya dönen mesaj, dahili Web Api handler tipi tarafından sunulur.

Eğer Web Api akışındaki handler tiplerine işi bırakmadan kullanıcıya mesajı kendimiz vermek istiyorsak aşağıdaki gibi bir yöntem izlememiz gerekmektedir.

public class MessageHandler2 : DelegatingHandler
{
      protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
     {
        // HttpResponseMessage tipinde bir cevap oluşturulur.
        var response = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent("Hello!")
        };

        // Not: TaskCompletionSource sınıfı bir Task oluşturur ve sarmalar ancak delege almaz.
        var tsc = new TaskCompletionSource<HttpResponseMessage>();
        tsc.SetResult(response);
        return tsc.Task;
     }
}

webapi_handlers_04

Bu kodda dikkat edersek base.SendAsync() metodunu kullanmadık. HttpResponseMessage tipinde bir cevabı kendimiz oluşturarak ve kullanıcıya sunmuş olduk.

Oluşturduğumuz message handler tiplerini akışa dahil etmek için HttpConfiguration sınıfının MessageHandlers kolleksiyonuna eklememiz gerekmektedir. Asp.Net MVC 4 web uygulamasında bu işlem şu şekilde yapılabilir:

public static class WebApiConfig
{
       public static void Register(HttpConfiguration config)
       {
          config.MessageHandlers.Add(new MessageHandler1());
          config.MessageHandlers.Add(new MessageHandler2());
       }
}

Message handler eklemeleri yapılırken sıralama önemlidir. Çünkü akışta çalışma sıralaması bu şeklide olacaktır. Çalışma sıralamasını görmek için handler sınıfları içine Debug.WriteLine() medodu ile mesajlar yazdırıp debug modunda sıralamayı görebiliriz.

Eğer Web Api uygulaması self hosting yöntemiyle barındırılıyorsa konfigurasyon ayarlaması şu şekilde olacaktır:

var config = new HttpSelfHostConfiguration("http://localhost");
config.MessageHandlers.Add(new MessageHandler1());
config.MessageHandlers.Add(new MessageHandler2());

Bu şekilde eklenen message handler tipleri global olarak eklenmektedir ve sunucuya gelen her talebi etkiler. Ancak bu istenmeyen bir durum olabilir. Bir diğer yöntem ise talep edilen kaynağa göre Route ayarlaması sırasında message handler tiplerinin akışa dahil edilmesidir.

public static class WebApiConfig
{
     public static void Register(HttpConfiguration config)
     {
         config.Routes.MapHttpRoute(
             name: "Route1",
             routeTemplate: "api/{controller}/{id}",
             defaults: new { id = RouteParameter.Optional }
         );

         config.Routes.MapHttpRoute(
             name: "Route2",
             routeTemplate: "api/{controller}/{id}",
             defaults: new { id = RouteParameter.Optional },
             constraints: null,
             handler: new MessageHandler2() // per-route message handler
         );

         config.MessageHandlers.Add(new MessageHandler1());  // global message handler
     }
}

Bu örneğe göre eğer gelen talebe ait URI, Route2 ile eşleşirse talep MessageHandler2  tarafına yönlendirilir. Aşağıdaki diyagramda akış yönlendirmesi grafiksel olarak gösterilmiştir.

webapi_handlers_05

 

Talep önce global olarak tanımlanan MessageHandler1 üzerinden geçip ardından HttpRouteDispatcher tarafından belirlenen kurala göre yönlendirilmektedir. Burada dikkat edecek olursak MessageHandler2 kullanıldığında Web Api yapısındaki mevcut HttpControllerDispatcher tipi devre dışı kalmıştır. Bu nedenle Route2 ile eşleşen talepler hiçbir controller sınıfına uğramazlar. Bu sayede kendi özel tiplerimizle controller görevi gören mekanizmalar oluşturabiliriz.

Eğer route mekanizmasından geçen talebin tekrar bir HttpControllerDispatcher üzerinden geçmesini istiyorsak şu şekilde bir ayarlama yapmamız gerekir:

// List of delegating handlers.
DelegatingHandler[] handlers = new DelegatingHandler[] {
      new MessageHandler3()
};

// Create a message handler chain with an end-point.
var routeHandlers = HttpClientFactory.CreatePipeline(
                  new HttpControllerDispatcher(config), handlers);

config.Routes.MapHttpRoute(
       name: "Route2",
       routeTemplate: "api2/{controller}/{id}",
       defaults: new { id = RouteParameter.Optional },
       constraints: null,
       handler: routeHandlers
);

Bu yapıya ilişkin diyagram aşağıdaki gibidir.

webapi_handlers_06

 

Kaynak: http://www.asp.net/web-api/overview/advanced/http-message-handlers

Asp.Net Web Api ile Kullanıcı Doğrulama ve Yetkilendirme

15 Şub

Asp.Net Web Api ile oluşturulan uygulamalar dış dünyaya tamamen açık olmayacaksa yani bazı kaynaklar sadece kayıtlı kullanıcılara sunulacaksa bu noktada Api güvenliğinin sağlanması gerekmektedir. Api güvenliği temelde, sistem kullanıcıların kim olduğunu ve hangi kaynaklara erişebileceğini belirleyen bir mekanizmanın oluşturulması ile sağlanabilir.

Güvenlik mekanizmalarının oluşturulmasında kullanılan iki temel kavram vardır. Bunlar:

  • Kullanıcı Doğrulama (Authentication), bir kullanıcının kim olduğunu belirleme işlemidir.
  • Kullanıcı Yetkilendirme (Authorization), belirlenen kullanıcıların hangi kaynaklara erişeceğini belirleme işlemidir.

Kullanıcı Doğrulama (Authentication)

Web Api çatısı, kullanıcı doğrulama işleminin host tarafından gerçekleştiğini varsayar. Örneğin IIS üzerinde host edilmiş bir uygulama, ilgili HTTP modülü tarafından doğrulanır. Kendi yazdığımız bir HTTP modülü üzerinde de doğrulama işlemini gerçekleştirebiliriz. Ancak alternatif olarak doğrulama işlemi Web Api HTTP Message Handler tarafından da gerçekleştirilebilir. Bu yöntemde sorumluluk Web Api message handler tarafına yüklenmiş olur. Bu durum aşağıdaki sebeplerden dolayı geliştiricide bazı çekinceler meydana getirebilir:

  • HTTP modülleri, Asp.Net iletim hattı(pipeline) boyunca tüm talepleri kontrol eder, ancak Web Api message handler tipleri sadece Web Api üzerine yönlendirilen talepleri kontrol eder.
  • Geliştirici isterse Route mekanizmasına göre doğrulama yapabilir.
  • HTTP modülleri IIS için özeldir, message handler tipleri ise Web Api host ortamı için özeldir. Web host veya self host.
  • HTTP modülleri IIS logging ve denetim sürecine dahildir.
  • HTTP modülleri, iletim akışın başlamadan önce çalışır. Eğer message handler kullanırsanız handler çalışana kadar kimlik bilgileri belirlenmez.

Hangi yöntem kullanılmalı konusunda kısaca şunu söylemek gerekir ki: Eğer web host kullanılacaksa HTTP modül kullanmak iyi bir seçim olacaktır. Ancak self host kullanılacaksa Web Api message handler kullanmak uygun olacaktır.

Eğer kullanıcı doğrulama işlemini kendi özel sınıfınız yardımıyla gerçekleştirdiyseniz iki farklı noktaya kullanıcı bilgilerini kaydetmelisiniz. Bunlar:

  • Thread.CurrentPrincipal
  • HttpContext.Current.User
private void SetPrincipal(IPrincipal principal)
{
   Thread.CurrentPrincipal = principal;
   if (HttpContext.Current != null)
   {
       HttpContext.Current.User = principal;
   }
}

Self host durumunda Thread.CurrentPrincipal kullanılır, Web host durumunda ise HttpContext.Current.User kullanılır.

Kullanıcı Yetkilendirme (Authorization)

Yetkilendirme işlemi, iletim hattında(pipeline) doğrulama işleminden sonra gelir. Yetkilendirme sırasında önce kullanıcının doğrulanmış olup olmadığı kontrol edilir. Yetkilendirme işlemi Controller veya Action seviyesinde gerçekleştirilebilir. Akışta yetkilendirme filitreleri controller ve action metodlarından önce çalışır.

webapi_auth01

Web Api uygulamalarında yetkilendirme işlemi filtreler akışa dahil edilir. Varsayılan olarak AuthorizeAttribute filtresi vardır. Filtreler iletim hattında ki akışa 3 şekilde dahil edilebilir.

  • Global seviyede
  • Controller seviyesinde
  • Action metod seviyesinde

Filtre aşağıdaki şekilde global seviyede akışa dahil edilebilir.

public static void Register(HttpConfiguration config)
{
    config.Filters.Add(new AuthorizeAttribute());
}

Eğer filtreler global olarak akışa dahil edilirse tüm talepler doğrulama işlemi için kontrol edilir. Eğer doğrulanmamış ise kullanıcıya Http 401 hata kodu döndürülür.

Controller seviyesinde sınırlandırma yapmak için filtre, attribute olarak kullanılır.

[Authorize]
public class ValuesController : ApiController
{
     public HttpResponseMessage Get(int id) { ... }
     public HttpResponseMessage Post() { ... }
}

Action seviyesinde sınırlandırma yapmak için de aynı attribute kullanılır.


public class ValuesController : ApiController
{
     [Authorize]
     public HttpResponseMessage Get(int id) { ... }
}

Bir controller sınıfı Authorize attribute ile işaretlendiğinde içerisindeki tüm action metodları  sınırlı erişime dahil olur. Ancak bazı action metodları sınırlamadan çıkarmak için AllowAnonymous attribute kullanılabilir.

[Authorize]
public class ValuesController : ApiController
{
    [AllowAnonymous]
    public HttpResponseMessage Get(int id) { ... }
    public HttpResponseMessage Post() { ... }
}

Authorize attribute kullanırken kullanıcı adı ve rol adı belirlemek de mümkündür. Bu şekilde sadece ilgili rol veya kullanıcıya izim verilmiş olur. Örnek:

[Authorize(Users="Bayram")]
public class ValuesController : ApiController{}

[Authorize(Roles="Admin")]
public class ValuesController : ApiController{}

Özel Yetkilendirme

Kendi özel sınıflarımızla yetkilendirme işlemini gerçekleştirmek istiyorsak oluşturacağımız sınıfı aşağıdaki tiplerden birinden türetmek gerekir.

  • AuthorizeAttribute, geçerli kullanıcı ve rolüne göre dayalı doğrulama için kullanılır.
  • AuthorizationFilterAttribute, senkron bir yetkilendirme için kullanılır. Geçerli kullanıcı olmasına gerek yoktur.
  • IAuthorizationFilter, asenkron bir yetkilendirme için kullanılır. Örneğin dosya sistemi veya network kullanıdığı durumlarda.

webapi_auth02

Kaynak: http://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api

 

Blog temasında değişiklik yapıldı

14 Şub

WordPress tarafında default temalar arasında olan ve benim de uzun yıllardır kullandığım iNove temasını değiştirmeye karar verdim. iNove teması worpress resmi sitesinde hala yer almakta. Ancak güncelliğini yitirmiş durumda.

iNove Tema WordPress
Eski iNove Tema WordPress

Bu temanın yerine yine wordpress resmi sitesinde yer alan The Box adlı temayı kullanmaya karar verdim.

 

EntityFramework 6 ve Postgresql Kullanımı

6 Şub

PostgreSQL veritabanına .NET tarafından erişebilmek için Npgsql adında bir provider geliştirilmiştir. Bu provider aracılığı ile veri tabanına sorgular atmak mümkündür.

Ancak günümüzde ORM araçlarının yaygınlaşmasıyla birlikte provider kullanarak veri tabanına erişim yerine ORM araçları tercih edilmektedir. Microsoft .NET tarafında kullanılan ORM aracı Entity Framework‘tür.

Entity Framework kullanarak PostgreSQL veritabanına erişmek için Npgsql paketlerini Nuget üzerinden indirmek gerekmektedir. İndirme işlemi için aşağıdaki komutlar bize yardımcı olacaktır.

Install-Package EntityFramework
Install-Package Npgsql.EntityFramework

Şu anda EntityFramework 6 sürümü piyasaya sürülmüş durumdadır. Bunu da bir not olarak belirtmekte fayda var.

EF ve Npgsql
Entity Framework ve Npgsql

App.config veya Web.config dosyalarında gerekli ayarlamaların yapılması gerekmektedir.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
      <section name="entityFramework"
               type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
               requirePermission="false" />
    </configSections>
    <connectionStrings>
       <add name="ChinookContext"
            connectionString="Server=10.0.1.7;Database=deneme;User Id=denuser;Password=denpass;"
            providerName="Npgsql" />
     <providers>
       <provider
        invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
       <provider invariantName="Npgsql" type="Npgsql.NpgsqlServices, Npgsql" />
     </providers>
   </connectionStrings>
   <entityFramework>
     <defaultConnectionFactory
        type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
     <providers>
          <provider
              invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
          <provider invariantName="Npgsql" type="Npgsql.NpgsqlServices, Npgsql.EntityFramework" />
     </providers>
   </entityFramework>
   <system.data>
     <DbProviderFactories>
        <add name="Npgsql Data Provider"
          invariant="Npgsql"
          support="FF"
          description="Data Provider for PostgreSQL"
          type="Npgsql.NpgsqlFactory, Npgsql" />
        </DbProviderFactories>
    </system.data>
    <runtime>
       <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <dependentAssembly>
       <assemblyIdentity name="Npgsql" publicKeyToken="5d8b90d52f46fda7" culture="neutral" />
       <bindingRedirect oldVersion="0.0.0.0-2.0.14.3" newVersion="2.0.14.3" />
       </dependentAssembly>
       </assemblyBinding>
   </runtime>
</configuration>

Geri kalan işlem klasik Code First yaklaşımının uygulanışı şeklindedir. Öncelikle tablolarımızı temsil eden sınıfları oluşturmalıyız. Bu örnekte Book ve Author sınıfları oluşturulmuştur.

  public class Book
  {
      public string Name { get; set; }
      public virtual Author Author { get; set; }
  }

  public class Author
  {
      public string Name { get; set; }
      public virtual ICollection<Book> Books { get; set; }
  }

Daha sonra Entity Framework için gerekli olan DbContext oluşturmak gerekir.

  public class ChinookContext : DbContext
  {
      public DbSet<Book> Books { get; set; }
      public DbSet<Book> Authors { get; set; }

      protected override void OnModelCreating(DbModelBuilder modelBuilder)
      {
           modelBuilder.Entity<Book>().ToTable("Book", "public");
           modelBuilder.Entity<Author>().ToTable("Author", "public");

          modelBuilder.Conventions.Remove<StoreGeneratedIdentityKeyConvention>();
      }
 }

DbContext sınıfında DbModelBuilder nesnesinin Entity özelliğinde tabloların hangi  şemaya ait olduğunu belirtmemiz gerekmektedir. Aksi taktirde provider, varsayılan olarak “dbo” şemasını arayacaktır. Bu durum bize Exception olarak geri dönebilir.

Bir sonraki yazıda görüşmek dileğiyle.