Burak Selim Senyurt(MVP)
Matematik Mühendisi bir .NET Severin Yazıları...

WCF WebHttp Services - Routing

Pazartesi, 8 Mart 2010 08:45 by bsenyurt

Merhaba Arkadaşlar,

Geçtiğimiz aylarda yurdumuzun en güzel şehirlerinden birisi olan İstanbul' da, oldukça soğuk ve kar yağışlı günler geçirdik. Yandaki resimde görülen manzara çalıştığım ARI 1 binasının yürüyüş yolu üzerinde çok da yeni olmayan, dokunmatik ekran, Windows Mobile gibi ileri özellikleri bulunmayan ancak Carl Zeiss mercekli Sony Ericsson K810i marka cep telefonum tarafından çekilmiştir. 3.1 Megapiksel ölçekli çekilen fotoğrafı kaliteli yapan ise elbetteki Carl Zeiss mercek. İnsan böyle zamanlarda eğer şartlar yerindeyse(çay, kahve, sıcak ve sessiz bir ortam, hızlı bir internet bağlantısı) işiyle ilgili pek çok konuda araştırma fırsatı bulabiliyor. Malum böyle havalarda sizde benim gibi evden çıkmamayı veya mesai sonrası şirkette bir kaç saat durmayı tercih edinenlerdenseniz, yapılacaklar listesinin belkide en başında yenilikleri araştırmak vardır diye düşünüyorum. Malum bir süredir de WCF Eco System' in önemli parçalarından birisi olan WebHttp Service' lerini incelemekte olduğumuza göre yine bu alanda ilerleyerek devam edebiliriz. Bu günkü konumuz ise WCF WebHttp servislerinde yönlendirme(Routing) işlemleri olacak.

Pek tabi, bir WCF REST Service Application içerisinde birden fazla WebHttp Service sınıfı konuşlandırılabilir. Bu çoğunlukla tek bir sınıfın var olduğu hallerde zaman içerisinde artan fonksiyon sayısı nedeniyle kavramsal bütünün bozulmasını engellemek amacıyla alınan bir tedbir olarak düşünülebilir. Aslında gerçek hayatta söz konusu kavramsal bütünlüğün ayrıştırılması ile ilişkili pek çok başarılı örnek yer almaktadır. Örneğin Sql Server 2005 ile gelen meşhur Adventure Works veritabanını göz önüne alalım. Burada bildiğiniz üzere çok sayıda tablo çeşitli şemalara(Schema) ayrılmıştır. Production, HumanResource, Sales vb...Böylece veritabanı alan modelinin(Domain Model) kavramsal olarak kolay bir şekilde ayrıştırılması mümkün olmaktadır. İşte benzer durum servis sınıflarının içeriğini oluşturan fonksiyonlar için de geçerli olabilir. Buna göre operasyonların ayrı servis sınıfları altında toplanıyor olması Adventure Works örneğindekine benzer kavramsal bir ayrımın yapılabilmesi manasına gelmektedir. Peki bir servisin yürüttüğü operasyonları sınıf bazında ayrıştırmanın uygulanışında nelere dikkat edilmesi gerekmektedir?

Herşeyden önce WebHttp Service' leri URI üzerinden gelen HTTP taleplerini değerlendirmektedir. Yani servise gelen bir talep, servis sınıfı içerisindeki bir operasyon ile ilişkilendirilmektedir. Bu noktada WCF WebHttp Service' lerinin konuşlandırıldığı Web uygulamasının global.asax içeriğinin büyük önemi vardır. Nitekim bu dosya içerisinde gelen talep için bir yönlendirme yapılması mümkündür. Bu amaçla RegisterRoutes isimli metoddan yaralanılır. Bir başka deyişle servisleri barındıran Web uygulaması ayağa kaldırıldığında, hangi URI taleplerinin hangi servislere yönlendirileceği bellidir. URI içerisinde yer alan operasyonel yönlendirmeler ise ilgili servis sınıfının metodlarına ait WebGet veya WebInvoke nitelikleri yardımıyla belirlenmektedir. Dolayısıyla istemcilerden gelecek olan taleplerin hangi servise yönlendirileceği sorusunun cevabı global.asax dosyası içerisinde verilmektedir. Hemen küçük bir not düşelim; bildiğiniz üzere Web taleplerinin daha etkin bir şekilde yönlendirilebilmesi yeteneği Asp.Net 4.0 ile birlikte gelmektedir. Bu sebepten geliştirilen WCF WebHttp servisinin Asp.Net uyumluluğu modunda(Asp.Net Compatibility Mode) çalıştırılıyor olması önemlidir.

Dilerseniz konuyu daha net kavrayabilmek adına basit bir örnek ile devam edelim. Bu amaçla Visual Studio 2010 Ultimate RC sürümü üzerinden geliştireceğimiz WCF REST Service Application'a ait Solution içeriğini aşağıdaki gibi tasarladığımızı düşünelim.

Görüldüğü üzere CalculationService ve ProductionService isimli iki sınıfımız bulunmaktadır. Bu servis sınıfları içerisinde çok basit iki operasyon yer almakta ve her ikiside HTTP Get metodlarına göre cevap vermektedir. ProductionService sınıfı içerisinde Product tipinden değer döndüren bir arama operasyonu yer almaktadır. Diğer yandan CalculationService sınıfı içerisinde bir ürün fiyatına hangi günde bulunulduğuna göre indirim yapan ve sonuç değerini gösteren bir operasyon yer almaktadır. Yönlendirme teorimize göre servis uygulamasına gelen talepleri CalculationService ve ProductionService tipleri üzerine dağıtmak istiyoruz. Bunun için global.asax dosyasında gerekli kodlamaları yapmamız gerekecektir. Ama öncesinde CalculationService, ProductionService ve Product tipi içeriklerimize bir bakalım.

Sınıf diagramımız;

Product sınıfımız;

namespace Lesson5
{
    public class Product
    {
        public int ProductId { get; set; }
        public string Name { get; set; }
        public decimal ListPrice { get; set; }
    }
}

ProductionService sınıfımız;

using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;

namespace Lesson5
{
    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class ProductionService
    {
        private static List<Product> Products = new List<Product>
        {
            new Product{ ProductId=1023, Name="Microsoft Optical Mouse", ListPrice=34.95M},
            new Product{ ProductId=1045, Name="Logitech Optical Mouse", ListPrice=35.45M},
            new Product{ ProductId=1029, Name="KeyMaster Wireless Keyboard", ListPrice=55.99M},
            new Product{ ProductId=9802, Name="Obi Wan Kneobi Jedi Light Saber", ListPrice=334.45M},
        };

        [WebGet(UriTemplate = "Products/{ProductId}")]
        public Product GetProduct(string ProductId)
        {
            Product prd=null;

            try
            {
                prd = (from p in Products
                       where p.ProductId.ToString() == ProductId
                       select p).First();
            }
            catch
            {          
                // Bir Product bulunamama olasılığına karşın HTTP statü kodu 404 döndürülür
                throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            }

            return prd;
        }
    }
}

CalculationService sınıfımız;

using System;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;

namespace Lesson5
{
    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class CalculationService
    {
        [WebGet(UriTemplate = "Discount/{ListPrice}")]
        public decimal CalculateDiscountListPrice(string ListPrice)
        {
            decimal decreasedListPrice = 0;
            decimal currentListPrice = 0;

            // Eğer string olarak gönderilen ListPrice değeri decimal tipe dönüşütürülemesse BadRequest tipinden bir statü kodu döndürülmesi sağlanır.
            if (!decimal.TryParse(ListPrice, out currentListPrice))
                throw new WebFaultException(System.Net.HttpStatusCode.BadRequest);

            switch (DateTime.Now.DayOfWeek)
            {
                case DayOfWeek.Friday:
                    decreasedListPrice = currentListPrice-(currentListPrice * 0.1M);
                    break;
                case DayOfWeek.Monday:
                    decreasedListPrice = currentListPrice-(currentListPrice * 0.2M);
                    break;
                case DayOfWeek.Saturday:
                    decreasedListPrice = currentListPrice;
                    break;
                case DayOfWeek.Sunday:
                    decreasedListPrice = currentListPrice;
                    break;
                case DayOfWeek.Thursday:
                    decreasedListPrice = currentListPrice-(currentListPrice * 0.3M);
                    break;
                case DayOfWeek.Tuesday:
                    decreasedListPrice = currentListPrice-(currentListPrice * 0.5M);
                    break;
                case DayOfWeek.Wednesday:
                    decreasedListPrice = currentListPrice-(currentListPrice * 0.2M);
                    break;
            }

            return decreasedListPrice;
        }
    }
}

Ve gelelim projemizin en önemli kısmına. Yönlendirme ile ilişkili olarak global.asax dosyasında yer alan RegisterRoutes metodunun içeriğini aşağıdaki gibi değiştirmemiz yeterli olacaktır.

using System;
using System.ServiceModel.Activation;
using System.Web;
using System.Web.Routing;

namespace Lesson5
{
    public class Global
        : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            RegisterRoutes();
        }

        private void RegisterRoutes()
        {
            // WebServiceHostFactory nesnesi örneklenir.
            WebServiceHostFactory hostFactory = new WebServiceHostFactory();

            // Route tablosuna gerekli eşleştirme bilgileri eklenir.
            // Gelen talep Productions içinse ProductionService servis tipi ile ilişkilendirilir.
            // Gelen talep Calculations içinse CalculationService servis tipi ile ilişkilendirilir.
            RouteTable.Routes.Add(new ServiceRoute("Productions", hostFactory, typeof(ProductionService)));
            RouteTable.Routes.Add(new ServiceRoute("Calculations", hostFactory, typeof(CalculationService)));
        }
    }
}

Uygulama başlatıldığında Application_Start metoduna girilecektir. Bu metod içerisinde ise RegisterRoutes fonksiyonu çağırılmaktadır. RegisterRoutes metodu içerisinde dikkat edileceği üzere iki ServiceRoute tipinin örneklenmesi ve bunların RouteTable üzerindeki Tables koleksiyonuna eklenmesi sağlanmaktadır. Buna göre servise gelen talepler aşağıdaki şekilde görüldüğü üzere eşleşen tiplere yönlendirilecektir.

Hemen çalışma zamanı sonuçlarına bir bakalım. Örneğin http://localhost:10860/Productions/Products/1029 talebinde bulunduğumuzda aşağıdaki sonuçlar ile karşılaşırız. Görüldüğü üzere talep ProductionService' e yönlendirilmiştir. (Tabi var olmayan bir ProductID talebi girildiğinde HTTP Status Code 404 NotFound ile karşılaşırız. WebFaultException tipinin kullanımının anlatıldığı yazımızı hatırlayalım lütfen Wink )

Diğer yandan http://localhost:10860/Calculations/Discount/120,45 şeklinde bir talepte bulunduğumuzda ise CalculationService' e yönlendirildiğimizi görebiliriz. (Yine decimal tipe dönüştürülemeyen bir talep gönderildiğinde tahmin edileceği üzere HTTP Status Code 400 Bad Reuqest ile karşılaşırız.)

Sonuç olarak UriTemplate' lerin ilgili servis operasyonları ile eşleştirilmesi çok daha kolaylaştırılmıştır. Öncelikle olarak gelen talebin hangi servis ile ilişkili olduğu noktasında Route Table devreye girmektedir. Ardından talep servise gelir. Servis ise URI içeriğine bakara uygun operasyonun çağırılması ile ilgilenir. Böylece geldik bir konumuzun daha sonuna. Bir sonraki yazımızda görüşünceye dek hepinize mutlu günler dilerim.

Lesson5_RC.rar (24,11 kb) [Örnek Visual Studio 2010 Ultimate Beta 2 Sürümünde geliştirilmiş ancak RC sürümü üzerinde de test edilmiştir]

WCF WebHttp Services - Error Handling

Cuma, 5 Mart 2010 08:10 by bsenyurt

Merhaba Arkadaşlar,

Bu yazımızda WCF Eco System' in bir parçası olan WebHttp Service' lerinde hata yönetimini(Error Management) etkili bir şekilde nasıl ele alabileceğimizi incelemeye çalışıyor olacağız. WCF WebHttp Service' leri üzerinden çağırılan bir servis operasyonundan, istemci tarafına kendi insiyatifimizde hata mesajları gönderilmesini sağlayabiliriz. Üstelik bu mesajları bilinen HTTP durum kodları(HTTP Status Code) çerçevesinde yayınlayabiliriz. Bu tip bir isteğin çeşitli sebepleri olabilir. Hemen bir gerçek hayat senaryosu üzerinden ilerleyerek bu basit konuyu pekiştirmeye çalışalım. Bu amaçla Visual Studio 2010 Ultimate RC sürümü üzerinde geliştirdiğimiz ve  aşağıdaki kod içeriğine sahip bir WCF REST Service Application projemiz olduğunu düşünelim.

using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;

namespace ServerApp
{
    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class AzonBookService
    {
        static List<Book> books = new List<Book>
            {
                new Book{ Id=1002, Name="Programming WCF 4.0",ListPrice=12.00M},
                new Book{Id=9034,Name="Pro Asp.Net 4.0",ListPrice=34.90M},
                new Book{Id=4560,Name="Algebra",ListPrice=122.39M},
                new Book{Id=1200,Name="C# 3.5 Cookbook",ListPrice=14.45M},
                new Book{Id=1201,Name="Pro C# 4.0 and .Net Framework 4.0",ListPrice=34.05M},
                new Book{Id=1201,Name="Beginning Ado.Net Entity Framework",ListPrice=14.55M}
            };

        [WebGet(UriTemplate = "/{firstLetter}")]
        public List<Book> GetBooks(string firstLetter)
        {
            return (from book in books
                   where book.Name.StartsWith(firstLetter)
                   select book).ToList();
        }
    }

    public class Book
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal ListPrice { get; set; }
    }
}

AzonBookService çok basit ve tek bir operasyona sahiptir. GetBooks isimli operasyon HTTP Get metoduna göre hizmet vermekte olup baş harfleri firstLetter parametresin ile gelen değer ile başlayan bir List<Book> koleksiyonunu geriye döndürmektedir. Bu servisi bir tarayıcı uygulama üzerinden test ettiğimizde ve örneğin Pro kelimesi ile başlayan kitapları elde etmek istediğimizde aşağıdaki ekran görütüsündekine benzer sonuçlar ile karşılaşırız.

Bu noktaya kadar zaten herhangibir sorun bulunmamaktadır. Ancak örneğin Ç harfi ile başlayan kitapların listesini elde etmek istediğimizde, aşağıdaki ekran görüntüsü ile karşılaşırız.

Şimdi bu noktada geliştirici olarak bir karar verebiliriz. İstemci tarafına bu şekilde boş bir XML içeriği döndürmemiz halinde, istemci tarafının Ç harfi ile başlayan kitapların olmaması durumunu ele alması gerekmektedir. Yani istemci tarafına ek bir iş yükü getirmiş olabiliriz. Nitekim istemcinin dönen listenin eleman sayısına bakara bir karar vermesi gerekmektedir. Diğer yandan istemciyi bilinçli bir şekilde uyarabiliriz de. Nasıl mı? Servis tarafındaki çalışma zamanında üreteceğimiz ve o anki vakaya uygun bir istisna(Exception) ile. Tabiki bu istisna mesajı servis bazlı bir yapı üzerinden ele alınacağı için istemci tarafına gönderilecek paketin içerisine gömülecektir. WCF WebHttp Service' lerinde bu tip istisnaları ele almak için WCF tarafından bildiğimiz FaultException tipinden türeyen WebFaultException sınıfından yararlanılır.

System.ServiceModel.Web isim alanı altında yer alan WebFaultException tipinin normal ve generic olan versiyonları bulunmaktadır. İlgili tipleri kullanımları son derece basittir. Yukarıdaki senaryomuza göre istemciye aradığı kriterlere uygun bir içerik bulunamadığını bildirmek amacıyla, GetBooks isimli servis operasyonunu aşağıdaki gibi değiştirebiliriz.

[WebGet(UriTemplate = "/{firstLetter}")]
        public List<Book> GetBooks(string firstLetter)
        {
            var result=(from book in books
                   where book.Name.StartsWith(firstLetter)
                   select book).ToList();

            if (result.Count == 0)
                throw new WebFaultException<string>("Talep edilen kelime ile başlayan kitaplar sistemde mevcut değiller.", System.Net.HttpStatusCode.NotFound);

            return result;
        }

Burada dikkat edileceği üzere sonuç listesinin eleman sayısı kontrol edilmiş ve eğer 0 ise WebFaultException tipinden bir istisna mesajı fırlatılması sağlanmıştır. WebFaultException tipinin örneklenmesi sırasında dikkat edilmesi gereken hususlardan biriside HttpStatusCode.NotFound Enum sabiti değerinin verilmesidir. Bu şekilde istemci tarafına hangi HTTP durum kodunun(Status Code) gönderileceği belirlenmektedir. Tahmin edeceğiniz üzere pek çok HTTP Status Code değeri bulunmaktadır.

Aslında tam liste içeriği şudur Wink Accepted, Ambiguous, BadGateway, BadRequest, Conflict, Continue, Created, Expectation Failed, Forbidden, Found, GatewayTimeout, Gone, HttpVersionNotSupported, InternalServerError, LengthRequired, MethodNotAllowed, Moved, MovedPermanently, MultipleChoices, NoContent, NonAuthoritativeInformation, NotAcceptable, NotFound, NotImplemented, NotModified, OK, PartialContent, PaymentRequired, PreconditionFailed, ProxyAuthenticationRequired, Redirect, RedirectKeepVerb, RedirectMethod, RequestedRangeNotSatisfiable, RequestEntityTooLarge, RequestTimeout, RequestUriTooLong, ResetContent, SeeOther, ServiceUnavailable, SwitchingProtocols, TemporaryRedirect, Unauthorized, UnsupportedMediaType, Unused, UseProxy

Operasyonumuzu bu yeni haliyle denediğimizde ise tarayıcı uygulama üzerinde aşağıdaki şekilde görülen çıktı ile karşılaşırız.

ki bu son derece doğaldır.

Nitekim zaten HTTP 404 mesajının istemci tarafına gönderilmesi sağlanmaktadır. WebFaultException<T> sınıfının örneklenmesi sırasında T parametresi de önemlidir. Örneğimizde basit bir string tipi kullanılmıştır. Peki ya kullanıcı tanımlı bir tip buraya dahil edilebilir mi? Gelin hem bu durumu hemde istemci uygulamanın geliştiriciler tarafından yazılması halinde söz konusu hata mesajlarının nasıl ele alındığı örneğimizi güncelleyerek irdelemeye çalışalım. Bu amaçla servisimizi aşağıdaki hale getirelim.

...Kodun diğer kısmı
       
        [WebGet(UriTemplate = "/{firstLetter}")]
        public List<Book> GetBooks(string firstLetter)
        {
            var result=(from book in books
                   where book.Name.StartsWith(firstLetter)
                   select book).ToList();

            if (result.Count == 0)
                throw new WebFaultException<ErrorInformation>(
                    new ErrorInformation { SearchLetter = firstLetter, SearchTime = DateTime.Now, Summary = "Talep edilen kelime ile başlayan kitaplar sistemde mevcut değiller." }
                    , System.Net.HttpStatusCode.NotFound);

            return result;
        }
    }

    public class ErrorInformation
    {
        public string SearchLetter { get; set; }
        public DateTime SearchTime { get; set; }
        public string Summary { get; set; } 
    }

Bu kod parçasında WebHttpException<ErrorInformation> tipinden bir nesne örneklenmiştir. Buna göre istemci tarafına bu içeriğin XML veya JSON formatında gönderilmesi mümkündür. Gelelim istemci tarafının kodlarına. Nitekim bir şekilde servis tarafından gelen içeriği kontrol atlına almamız gerekmekte.

using System;
using Microsoft.Http;

namespace ClientApp
{
    class Program
    {
        static void Main(string[] args)
        {
            using (HttpClient client = new HttpClient("http://localhost:12654/Azon/"))
            {
                HttpResponseMessage response=client.Get("Ç");

                Console.WriteLine("Http Status Code : {0}\n",response.StatusCode.ToString());
                Console.WriteLine("Content : {0}\n",response.Content.ReadAsString());

            }
        }
    }
}

Ç harfi ile başlayan kitapların elde edilmesi için bir talep oluşturulmakta ve bu talep Get metodu yardımıyla servis tarafına gönderilmektedir. Get metodunun çıktısı HttpResponseMessage tipindendir. Elde edilen HttpResponseMessage nesne örneğinin StatusCode özelliği yardımıyla servis tarafında üretilen HttpStatusCode Enum sabitinin değeri elde edilir. Diğer yandan eğer istemci tarafından bir hata oluşuyorsa WebFaultException nesnesinin oluşturulması sırasında kullanılan ErrorInformation nesne örneğinin serileştirilmiş hali Content özelliği üzerinden elde edilebilmektedir. Buna göre çalışma zamanı çıktısı aşağıdaki gibi olacaktır.

Görüldüğü üzere servis operasyonu içerisinde üretilen ErrorInformation bilgisi istemci tarafına XML formatlı olacak şekilde aktarılmıştır. Sonuç olarak olası veya beklenen hatalar karşısında istemci tarafına uygun bir bildirim yapılması HTTP durum kodları bazında mümkündür. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Lesson4_RC.rar (174,57 kb) [Örnek Visual Studio 2010 Ultimate Beta 2 Sürümünde geliştirilmiş ancak RC sürümü üzerinde de test edilmiştir]

WCF WebHttp Services - JSON Formatlı Response Üretmek

Cuma, 19 Şubat 2010 07:50 by bsenyurt

Merhaba Arkadaşlar,

Yandaki Logo size neyi çağırıştırıyor? Aslında bakarsanız çok meşhur olan hafif siklette bir veri değiş tokuş formatının logosunu ifade etmekte. JSON(JavaScript Object Notation). Hatırlayacağınız üzere bir süredir WCF Eco System içerisinde yer alan WCF WebHttp Service alt yapısını incelemeye çalışıyoruz. WCF WebHttp Service' leri eğer istemci tarafından aksi belirtilmezse varsayılan olarak XML formatında çıktı üretmektedir. Ancak istenirse JSON(JavaScript Object Notation) formatında çıktı üretmeside sağlanabilir. Söz konusu çıktı üretim işlemi iki yolla gerçekleştirilebilir. Bilinçli olarak(Excplicitly) veya otomatik olarak. Bu yazımızda söz konusu yolları inceleyerek JSON formatında çıktıları nasıl verebileceğimizi basit bir örnek üzerinden görmeye çalışıyor olacağız. Örnek uygulamamızı bu kez Visual Studio 2010 Ultimate RC ortamı üzerinde geliştirdiğimizi belirtelim. Dolayısıyla ilerleyen sürümde bazı farklılıklar olabilir. Lesson3 isimli WCF REST Service Application uygulamamız içerisinde yer alan servis sınıfı içeriğimiz çok basit olarak aşağıdaki kod parçasından oluşmaktadır.

using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;

namespace Lesson3
{
    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class PersonalityService
    {
        [WebGet(UriTemplate = "AllPersons")]
        public List<Person> GetAllPersons()
        {
            return new List<Person>()
            {
                new Person() { Id = 1, Name = "Burak Selim Şenyurt",Birth=new DateTime(1976,12,1) } ,
                new Person() { Id = 2, Name = "Bill Amca",Birth=new DateTime(1975,4,5) } ,
                new Person() { Id = 3, Name = "Luka Ton-i",Birth=new DateTime(1980,3,4) }
            };
        }
    }

    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime Birth { get; set; }
    }
}

Servisimizde yer alan GetAllPersons isimli operasyon istemci tarafına Person tipinden bir liste içeriği döndürmektedir. Söz konusu operasyon HTTP protokolünün GET metoduna ait talepleri kabul etmektedir. Varsayılan olarak URL üzerinden yapacağımız AllPersons çağrısının sonucu aşağıdaki gibi olacaktır.

Ancak şimdiki hedefimiz bu XML çıktısı yerine JSON çıktısını vermektir. Bunu iki yol ile gerçekleştirebileceğimizden bahsetmiştik. Öncelikle otomatik JSON çıkıtısı üretiminin nasıl gerçekleştirilebileceğine bakalım. Bu amaçla sunucu tarafındaki web.config dosyası içerisinde yer alan webHttpEndpoint içerisindeki standardEndpoint elementinin automaticFormatSelectionEnabled niteliğinin true değere sahip olması yeterlidir. Aynen aşağıda görüldüğü gibi.

<system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
    <standardEndpoints>
      <webHttpEndpoint>
        <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true"/>
      </webHttpEndpoint>
    </standardEndpoints>
  </system.serviceModel>

Peki bu otomatikliğin anlamı nedir? Undecided Nitekim herhangibir yerde JSON çıktısı vereceğimizi belirtmedik. Dolayısıyla birisinin bunu talep ediyor olması gerekmekte. Tahmin edeceğiniz üzere burada sorumluluk istemci tarafına ait. Bir başka deyişle istemci uygulama talebini gönderirken JSON formatında bir içerik istediğini servis tarafına bildirmelidir. Dolayısıyla örneğimize aşağıdaki kodları içeren istemci uygulamayı yazarak devam etmeliyiz.

Not : İstemci uygulama açısından önem arz eden konulardan biriside, HttpClient tipinin kullanımı için gerekli olan WCF REST Starter Kit Preview 2 assmebly' ları ile ReadAsJsonDataContract genişletme metodunun(Extension Methods) kullanımı için gerekli olan System.ServiceModel.Web ve System.Runtime.Serialization assembly' larını referans etmesidir.

Buna göre istemci tarafının kodlarını aşağıdaki şekilde geliştirebiliriz.

using System;
using Microsoft.Http;

namespace ClientApp
{
    class Program
    {
        static void Main(string[] args)
        {
            using (HttpClient client = new HttpClient("http://localhost:2360/"))
            {
                HttpRequestMessage request = new HttpRequestMessage("GET", "AllPersons");               
                request.Headers.Accept.AddString("application/json");
                HttpResponseMessage response = client.Send(request);
                response.EnsureStatusIsSuccessful();
                HttpContent content=response.Content;
                Console.WriteLine(content.ReadAsString());
            }
        }
    }
}

Bu kod parçasında en çok dikkat edilmesi gereken nokta talep ile ilişkili Header kısmına eklenen application/json bilgisidir. Bu durumda HTTP Get metoduna göre yapılan servis çağrısı çıktısının JSON formatında olması istenmektedir. Servis tarafında da gelen isteğe göre bir çıktı üretildiğinden, WCF çalışma zamanı operasyon çıktısını JSON formatına dönüştürecektir. Kodun çalışması sonrasında aşağıdaki ekran çıktısı ile karşılaştığımızı görürüz.

Dikkat edileceği üzere JSON formatında bir çıktı elde edilmiştir.

İstemci tarafına gelen bu çıktının Person tipini içeren bir koleksiyon şeklinde ele alınması istediğimizdeyse HttpContent tipi üzerinden System.Runtime.Serialization.Json isim alanında yer alan ReadAsJsonDataContract genişletme metodunu çağırabiliriz. Tabi burada istemci tarafında Person tipininde bir örneğinin yer aldığını varsayıyoruz ki bunu bildiğiniz üzere WCF REST Starter Kit Preview 2 ile gelen Paste XML As Types seçeneği ile oluşturabiliriz. Eğer hatırlamıyorsanız biraz araştırmaya ne dersiniz? Wink İşte istemci tarafındaki yeni kod içeriğimiz.

using System;
using System.Collections.Generic;
using System.Runtime.Serialization.Json;
using Microsoft.Http;

namespace ClientApp
{
    class Program
    {
        static void Main(string[] args)
        {
            using (HttpClient client = new HttpClient("http://localhost:2360/"))
            {
                HttpRequestMessage request = new HttpRequestMessage("GET", "AllPersons");               
                request.Headers.Accept.AddString("application/json");
                HttpResponseMessage response = client.Send(request);
                response.EnsureStatusIsSuccessful();
                HttpContent content=response.Content;
                //Console.WriteLine(content.ReadAsString());
            
                List<Person> personList=response.Content.ReadAsJsonDataContract<List<Person>>();

                foreach (Person person in personList)
                {
                    Console.WriteLine("{0} {1} {2}",person.Id,person.Name,person.Birth.ToString());
                }
            }
        }
    }
}

Bu durumda çalışma zamanında aşağıdaki sonucu elde ederiz.

Gelelim bilinçli olarak çıktı formatının nasıl belirleneceğine. Öncelikli olarak neden bilinçli bir şekilde format çıktısını söylememiz gerektiğini kavramamızda yarar olduğu kanısındayım. İstemci tarafının her zaman HTTP talebinin Header kısmına müdahale etmesi söz konusu olamayabilir. Böyle bir durumda istemcinin JSON formatında talepte bulunabilmesi de mümkün değildir. Dolayısıyla bu tip bir vakada JSON formatında çıktı verileceğinin bilinçli olarak bildirilmesi gerekmektedir. Peki ya nerede ve nasıl? Cevap: Servis tarafındaki ilgili operasyon içerisinde ve bir parça kod yardımıyla Wink İşte GetAllPersons isimli servis operasyonumuzun bilinçli olarak JSON formatında çıktı veren yeni versiyonu.

using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;

namespace Lesson3
{
    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class PersonalityService
    {
        [WebGet(UriTemplate = "AllPersons?whichFormat={format}")]
        public List<Person> GetAllPersons(string format)
        {
            if (format.ToLower().Equals("json"))
            {
                WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Json;
            }
            return new List<Person>()
            {
                new Person() { Id = 1, Name = "Burak Selim Şenyurt",Birth=new DateTime(1976,12,1) } ,
                new Person() { Id = 2, Name = "Bill Amca",Birth=new DateTime(1975,4,5) } ,
                new Person() { Id = 3, Name = "Luka Ton-i",Birth=new DateTime(1980,3,4) }
            };
        }
    }

    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime Birth { get; set; }
    }
}

İlk dikkat edilmesi gereken nokta, WebGet niteliğinde belirtilen format isimli parametre ile istemciden hangi formatta çıktı almak istendiğinin sorulmasıdır. Eğer json kelimesi yazılmışsa WebOperationContext üzerinden güncel çalışma zamanı içeriğine geçilerek cevap formatının JSON olacağı belirtilir ki buda dikkat edilmesi gereken ikinci noktadır. Tahmin edileceği üzere json dışında bir kelime girildiği takdirde varsayılan XML çıktısının üretilmesi söz konusu olacaktır. Servis operasyonumuzun bu son haline göre Internet Explorer üzerinden http://localhost:2360/AllPersons?whichFormat=json şeklinde bir talepte bulunursak, içeriği kaydetmemiz için bir iletişim penceresi ile karşılaşırız. İçeriği kaydettikten sonra Notepad programı ile açacak olursa aşağıdaki içeriğin üretildiğini görebiliriz.

ki buda tam anlamıyla JSON çıktısıdır. Smile Çıktının JSON veya XML harici formatlarda olması da söz konusudur aslında. Bu formatların nasıl ele alınacağını ise ilerleyen yazılarımızda değerlendirmeye çalışıyor olacağız. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Lesson3_RC.rar (173,29 kb) [Örnek Visual Studio 2010 Ultimate Beta 2 Sürümünde geliştirilmiş ancak RC sürümü üzerinde de test edilmiştir]

WCF WebHttp Services - Client Tarafını Geliştirmek

Cuma, 5 Şubat 2010 09:45 by bsenyurt

Merhaba Arkadaşlar,

Sanırım pek çoğumuz piyangodan veya diğer şans oyunlarından kendilerine tonlarca para çıksa ne yapacağını düşünmüş veya hayal etmiştir. Açıkası kendi adıma hayat etmediğimi dile getirsem yalan söylemiş olurum. Ancak ben pek çoğumuz gibi yan yana bir kaç Ferrari' yi dizmektense bir kaç yere yatırım yapmayı hayal etmişimdir hep. Örneğin dünyanın sayılı bir kaç futbol kulübünün(Barcelona, Manchester United vb...) ve yazılım şirketinin(Microsoft, IBM vb...) hisselerinden satın alır ve şöyle güzel bir fon sepeti oluştururum. Neyse...Sözü niye piyangodan açtığımıza gelince...

Hatırlayacağınız üzere bir önceki yazımızda WCF WebHttp Service' leri ile tanımaya çalışmış ve konuyu pekiştirmek amacıyla basit bir Merhaba Dünya uygulaması geliştirmiştik. Tabi bu örneğimizde HTTP protokolünün yanlızca Get metodunu kullanmıştık. Dolayısıyla operasyonlarımızda sadece WebGet niteliklerinin uygulandığına şahit olduk. Ancak HTTP protokolüne göre Get dışında Post, Put ve Delete metodlarını da kullanabileceğimizi biliyoruz. Dikkat çekici bir diğer noktada örneğimizde Get metoduna göre talepte bulunurken basit bir tarayıcı uygulamadan faydalanmış olmamızdı. Oysaki kendi istemci uygulamamızı yazmak isteyebiliriz. Bu durumda istemci tarafından HTTP protokolünün Get, Post, Put ve Delete metodlarına uygun talepleri nasıl gerçekleştirebiliriz? Aslında olay servis tarafının istediği mesaj paketlerini istemci tarafında oluşturup göndermekten başka bir şey değildir. Yani talebin(Request) içeriğini hazırlamak ve dönen cevabı(Response) değerlendirmek.

İşte bu yazımızda söz konusu durumları ele alaraktan hem Post, Put, Delete metodlarının kullanımına bir örnek verecek hemde istemci tarafını geliştirmeye çalışacağız. Tabi öncesinde servis tarafını hazırlamamız gerekiyor. Bu örneğimizde herhangibir işe yaramasada konuyu anlamamızı kolaylaştıracak bir senaryomuz da olacak. Senaryomuza göre bir Piyango servisi tasarlayacağız. Wink Bu servis, istemcilerin yeni bir piyango bileti üretebilmesine, var olan piyango biletlerini çekebilmelerine, isterlerse biletlerini silmelerine veya güncellemelerine izin veren operasyonlar içerecek. Tabiki bu operasyonlarda HTTP protokolünün Get, Post, Put ve Delete metodları göz önüne alınıyor olacak. Dilerseniz hiç vakit kaybetmeden WCF REST Service Application uygulamasını oluşturarak işe başlayalım. Uygulamamızda bilet bilgilerinin saklanması ve depolanması amacıyla basit text dosyasından yararlandığımızı belirtmek isterim. Diğer taraftan bilet bilgileri için Ticket isimli yardımcı bir sınıfımızda yer almaktadır.

using System;

namespace Lesson2
{
    public class Ticket
    {
        public string Number { get; set; }
        public string Owner { get; set; }
        public DateTime TicketDate { get; set; }
        public bool NewOrUpdated { get; set; }

        public override string ToString()
        {
            return String.Format("{0}|{1}|{2}|Is New? {3}", Number, Owner, TicketDate.ToString(),NewOrUpdated.ToString());
        }
    }
}

LotteryService isimli WCF WebHttp Service içeriği ise aşağıda görüldüğü gibidir.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Web;

namespace Lesson2
{
    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class LotteryService
    {
        string filePath = HttpContext.Current.Server.MapPath("~\\Tickets.txt");

        [WebGet(UriTemplate = "Lottery/{Name}/{LastName}")]
        public List<string> GetMyTickets(string Name,string LastName)
        {
            return (from line in File.ReadAllLines(filePath)
                    where line.Contains(Name + LastName)
                    select line).ToList();
        }

        [WebInvoke(UriTemplate = "Lottery/Create/{Name}/{Surname}", Method = "POST")]
        public Ticket CreateTicket(string Name,string Surname)
        {
            Ticket createdTicket=new Ticket
            {
                Number = Guid.NewGuid().ToString(),
                Owner = String.Format("{0}{1}",Name,Surname),
                TicketDate = DateTime.Now
            };

            File.AppendAllLines(filePath,new String[]{createdTicket.ToString()});
            return createdTicket;
        }

        [WebInvoke(UriTemplate = "Lottery/Update/{TicketNumber}", Method = "PUT")]
        public string UpdateMyTicketNumber(string TicketNumber)
        {
            string ticket = (from line in File.ReadAllLines(filePath)
                         where line.Contains(TicketNumber)
                         select line).First();
            string[] infos=ticket.Split('|');

            string updatedTicket=string.Join("|",Guid.NewGuid().ToString(),infos[1],infos[2],"Is New? ",true.ToString());

            File.AppendAllLines(filePath, new string[]{updatedTicket});

            return updatedTicket;
        }

        [WebInvoke(UriTemplate = "Lottery/Delete/{TicketNumber}", Method = "DELETE")]
        public void DeleteMyTicket(string TicketNumber)
        {
            string[] newLines = (from line in File.ReadAllLines(filePath)
                           where !line.Contains(TicketNumber)
                           select line).ToArray();

            File.WriteAllLines(filePath, newLines);
        }
    }
}

Kod parçamızda HTTP Get,Post,Put ve Delete metodlarının kullanımlarına örnek olması açısından çeşitli servis operasyonlarının yer aldığı görülmektedir. Kritik olan noktalar WebGet ve WebInvoke niteliklerinin nasıl kullanıldığıdır. Servisimizin yardım sayfasına bakıldığında, istemci tarafında oluşturulması gereken Request paketlerinin nasıl olacağıda kolaylıkla görülebilir. Tabi Post ve Put metodlarında bir Request Body kullanılmamıştır. Bir başka deyişle Put ve Post işlemleri için gerekli bilgiler servis tarafına URL satırından gönderilmektedir.

Gelelim istemci tarafına.

İstemciyi basit bir WinForms uygulaması olarak tasarlayacağız. Önemli olan nokta ise, az önce tasarlanan WCF WebHttp Service' ini nasıl kullanabileceğimiz. Sonuçta HTTP Get, Post, Put ve Delete metodlarının istemci tarafından hazırlanması ve gönderilmesi gerekmekte. Üstelik servise ait bir WSDL içeriği ve dolayısıyla Proxy üretimi de söz konusu değil. Bu noktada WebChannelFactory, HttpWebRequest ve WebClient tiplerinden yararlanabileceğimizi biliyoruz. Ne varki WCF Rest Starter Kit Preview 2 ile birlikte gelen HttpClient sınıfı tamda bu tip servislerin tüketilmesi için geliştirilmiş durumda. Elbette bu kit içeriğinin, .Net Framework 4.0' ın final sürümü ile birlikte içeriye doğrudan dahil edileceğini tahmin etmekteyiz. Şimdilik Starter Kit ile gelen tipi kullanacağız. Bu sebepten Windows uygulamamıza gerekli referansları aşağıdaki şekildende görüleceği üzere eklememiz gerekiyor.

Dikkat edilmesi gereken noktalardan biriside istemci uygulamanın hedeflediği Framework profilidir. Söz konusu Starter Kit referansları ile çalışabilmek için istemci tarafının hedef profilinin .Net Framework 4.0 Client Profile değil(ki varsayılanı budur) .Net Framework 4.0 olması gerekmektedir.

Artık istemci için gerekli tüm ön hazırlıklar yapılmıştır. Şimdi dilerseniz servis fonksiyonelliklerini icra edebilmek amacıyla Form içeriğini aşağıdaki gibi düzenleyelim.

Form üzerindeki kontrolleri kullanarak bilet üretebilecek, bir bileti silip güncelleyebilecek yada var olan biletlerimizi görebileceğiz. Tabiki tüm bu fonksiyonellikler LotteryService isimli WCF HttpWeb Service üzerinden gerçekleştiriliyor olacak. İlk olarak bir kişinin sahip olduğu tüm biletleri listlemeye çalışalım. Bu noktada servis tarafındaki GetMyTickets operasyonu için bir çağrı yapılması gerekiyor. Söz konusu çağrı örneğin http://localhost:16088/LotteryService/Lottery/Coni/Vayt şeklinde olabilir. Nitekim WebGet niteliğinde belirtilen URI bilgisi bu şekildedir. Bu durumda istemci tarafında aşağıdaki kodlamayı yapabiliriz.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Xml.Linq;
using Microsoft.Http;

namespace ClientApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnGetMyTickets_Click(object sender, EventArgs e)
        {
            // REST Starter Kit Preview 2' den gelen HttpClient tipi oluşturulur.
            // Parametre olarak WCF WebHttp Service' in base address bilgisi verilir.
            using (HttpClient client = new HttpClient("http://localhost:16088/LotteryService/"))
            {
                // Get talebi için gerekli URI bilgisi oluşturulur
                string requestUri = String.Format("Lottery/{0}/{1}", txtName.Text, txtSurname.Text);
                // Get metodu ile ilgili talep oluşturulur.
                HttpResponseMessage responseMessage=client.Get(requestUri);
                // Operasyonun başarılı olmaması halinde bir exception fırlatılması isteniyorsa EnsureStatusIsSuccessful metodu kullanılmalıdır.
                responseMessage.EnsureStatusIsSuccessful();
                // İçerik XML tipinden geldiği için bunu kod tarafında kolayca ele almak adına ReadAsXElement genişletme metodu(Extension Method) kullanılabilir. Lakin bu metod için System.Xml.Linq isim alanının referans edilmesi gerekir.
                XElement response = responseMessage.Content.ReadAsXElement();
            }
        }
    }
}

Servisin çalıştığını varsaydığımızda yukarıdaki metodun icra edilmesi sonucu debug zamanında aşağıdaki sonuçlara ulaştığımızı görebiliriz. (Tickets.txt dosyasında bazı ticket bilgileri olduğunu  ve Name için Coni, Surname için Vayt bilgilerinin girildiğini varsayıyoruz)

Dikkat edileceği üzere servis tarafındaki operasyonda List<string> tipinden olan operasyon dönüş tipi istemci tarafına, ArrayOfstring ve string isimli alt elementlerden oluşan bir XML içeriği olarak aktarılmıştır. Elbette XElement içeriğinin bu şekilde elde edilebiliyor olması yeterli değildir. Bu içeriğin ListBox kontrolü içerisine serpiştirilmesini de bekliyoruz. Dolayısıyla aşağıdaki kod ilavesini de yapmamız gerekiyor.

XElement response = responseMessage.Content.ReadAsXElement();

lstMyTickets.Items.Clear();
var tickets = from node in response.Elements()
                  select node.Value;

foreach (var ticket in tickets)
{
    lstMyTickets.Items.Add(ticket);
}

Dikkat edileceği üzere basit bir XLINQ sorgusu ile tüm elementlerin Value değerleri çekilmiştir. Elbette bu XLINQ sorgu ifadesini belirleyen kriter, servis tarafından dönen XML içeriğinin şemasıdır. Çalışma zamanında örnek bir kullanıcı için Get sorgusunu gerçekleştirdiğimizde aşağıdaki ekran görüntüsündekine benzer sonuçları elde ettiğimizi görürüz.

Şimdi yeni bir biletin oluşturulması için gerekli kodları yazalım.

private void btnCreateTicket_Click(object sender, EventArgs e)
        {
            using (HttpClient client = new HttpClient("http://localhost:16088/LotteryService/"))
            {
                string requestUri = String.Format("Lottery/Create/{0}/{1}", txtName.Text, txtSurname.Text);

                // Yeni bir Ticket oluşturmak için gerekli istek HTTP Post metoduna göre yapılmaktadır. Bu sebepten HttpClient tipinin Post metodu kullanılmıştır.
                // Gönderilen talepte herhangibir Request Body içeriği olmadığından HttpContent tipinin CreateEmpty metodu kullanılmıştır.
                HttpResponseMessage responseMessage = client.Post(requestUri, HttpContent.CreateEmpty());
                responseMessage.EnsureStatusIsSuccessful();
                // Oluşturulan yeni Ticket bilgisi istemci tarafına yine bir XML içeriği olarak dönmektedir. Üretilen içerik bilgi amaçlı olarak kullanıcıya gösterilir.
                XElement createdTicket = responseMessage.Content.ReadAsXElement();
                MessageBox.Show(createdTicket.ToString());
            }
        }

Get kullanımına benzer olmakla birlikte bu kez HttpClient tipinin Post metodundan yararlanılmaktadır. Post ve Put gibi metodlarda Request Body' sinin olması gerekebilir. Ancak bizim servis operasyonlarımız Request Body kullanmamaktadır. Bu nedenle ilgili parametreler HttpContent.CreateEmpty() metodu ile geçilmektedir. Bu kod parçasına göre çalışma zamanında bir bilet üretmek istediğimizde geriye aşağıdakine benzer sonuçların aktarıldığını görebiliriz.

Peki ya silme ve güncelleme işlemlerinden ne haber? Bu operasyonlar için istemci tarafında aşağıdaki kodları yazmamız yeterli olacaktır.

private void btnDeleteTicket_Click(object sender, EventArgs e)
        {
            // Öncelikle ListBox' ta seçili bir öğe olup olmadığına bakılır
            if (lstMyTickets.SelectedItem != null)
            {
                // Biletin numarası yani GUID bilgisi alınır.
                string ticketNumber = lstMyTickets.SelectedItem.ToString().Substring(0, 36);
                using (HttpClient client = new HttpClient("http://localhost:16088/LotteryService/"))
                {
                    // HTTP Delete metoduna göre bir talepte bulunulur.
                    string requestUri = String.Format("Lottery/Delete/{0}", ticketNumber);
                    // Delete talebi için HttpClient tipinin Delete metodundan yararlanılır. Bu metodun kullanımına göre herhangibir HTTP Request Body içeriği bildirilmesi gerekli değildir.
                    HttpResponseMessage responseMessage = client.Delete(requestUri);
                    responseMessage.EnsureStatusIsSuccessful();
                   
                }
            }
        }

        private void btnUpdateTicket_Click(object sender, EventArgs e)
        {
            if (lstMyTickets.SelectedItem != null)
            {
                string ticketNumber = lstMyTickets.SelectedItem.ToString().Substring(0, 36);
                using (HttpClient client = new HttpClient("http://localhost:16088/LotteryService/"))
                {
                    // Update talebi hazırlanır
                    string requestUri = String.Format("Lottery/Update/{0}", ticketNumber);
                    // Güncelleme isteği aslında HTTP Put metoduna karşılık gelmektedir. Bunun için HttpClient tipinin Put metodundan yararlanılır.
                    HttpResponseMessage responseMessage = client.Put(requestUri, HttpContent.CreateEmpty());
                    responseMessage.EnsureStatusIsSuccessful();
                    // Put metodunun çalıştırılması sonucu üretilen çıktı bu kez string bazlı olacak şekilde bir MessageBox aracılığıyla gösterilir.
                    MessageBox.Show(responseMessage.Content.ReadAsString());
                }
            }
        }

Örneğin var olan bir biletimizi güncellemek istediğimizi düşünelim. Örnek çalışma zamanı görüntüsü aşağıdakine benzer olacaktır.

Tabi uygulamamızın pek çok yerinde bug ve iş mantığı hatası vardır. Üstelik tam anlamıyla bir istisna yönetimide(Exception Handling) yapılmamaktadır. Ancak odaklanmamız gereken yada dikkat etmemiz gereken noktalar WCF WebHttp Service üzerinden Post, Put, Delete operasyonlarının nasıl sunulduğu ve istemci tarafında bunların nasıl ele alındığıdır. Özellikle istemci tarafında kullandığımız tekniklere baktığımızda şu sonuçlara varabiliriz;

  • Herhangibir proxy tipi kullanılmamıştır. Bilindiği üzere servisleri tüketmenin yollarından birisi istemci tarafında gerekli proxy tipinin üretilmesidir. HTTP Get,Post,Put ve Delete metodlarının kullanıldığı teknikte ise sadece paketlerin oluşturulup gönderilmesi ve cevapların değerlendirilmesi gerekmektedir.
  • Sonuçların ilgili formata göre istemci tarafına gönderilmesi söz konusudur. Varsayılan olarak XML tipinden içerik döndürülmektedir. Ancak JSON formatında da dönüşler olabilir.
  • Get metodundan elde edilen XML formatlı içerikler istemci tarafında XElement tipi ile ele alınabilir ve XLINQ kullanılarak ayrıştırılabilir.
  • İstemci tarafından yapılan güncelleştirme çağrıları için Put, ekleme işlemlerine ait talepler için Post, silme işlemlerine ait talepler için Delete metodları kullanılır.
  • Post ve Put metodları parametre olarak eğer gerekliyse bir Body içeriği sunmak zorunda olabilirler. Eğer sunmuyorlarsa HttpContent.CreateEmpty() metodu ile boş içerik gönderileceğinin belirtilmesi gerekir.

Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Lesson2.rar (196,61 kb) [Örnek Visual Studio 2010 Ultimate Beta 2 Sürümünde geliştirilmiş ancak RC sürümü üzerinde de test edilmiştir]

WCF WebHttp Services - Tanışma

Pazartesi, 1 Şubat 2010 14:50 by bsenyurt

Merhaba Arkadaşlar,

Nihayet taşlar yerli yerine oturmaya başladı. 2008 yılında düzenlenen Microsoft PDC' de  tanıtılan sürüm ile başlayan macerada Beta 1, Beta 2 versiyonları derken yavaş yavaş RC, RTM sürümlerinin çıkacağı günlere gelmekteyiz. Elbette hepizimin beklentisi bir an önce stabil bir sürüme kavuşabilmek. Bu günlerde çok doğal olarak .Net Framework 4.0 ve Visual Studio 2010 ürünlerinin sınırlarının daha da netleştiğini görmeye başladık. Her ne kadar henüz yayınlanmış yeni bir sürüm olmasa da, pek çok güncel ve geçerli kaynaktan okuduğumuz kadarı ile bu böyle. Taşların yerli yerine oturmaya başladığı ve herşeyin biraz daha belirginleştiği alanlardan biriside Windows Communication Foundation 4.0.

Hatırlayacağınız üzere WCF Eco System' i anlattığımız yazımızda, WCF alt yapısı üzerine geliştirilen ve amaca yönelik olarak farklılaştırılan servis geliştirme modellerine değinmiştik. Bunlardan biriside WebHttp Services idi. Bu yazımız ile birlikte WebHttp Service' lerini tanımaya çalışacağız. Aslında WCF 3.5 sürümüne kazandırılan Web programlama teknikleri sayesinde zaten uzun bir süredir farkında olduğumuz non-SOAP bazlı bir modelden bahsediyoruz. Bildiğiniz üzere WebGet ve WebInvoke isimli nitelikler(attribute) yardımıyla servis operasyonlarının HTTP Get,Post,Put ve Delete metodlarına cevap verebilecek şekilde tasarlanması mümkün. Ancak zaman ilerledikçe REST(REpresentational State Transfer) modeline göre WCF servislerinin daha kolay geliştirilmesini sağlayan ve WCF 4.0 içerisinde gömülecek yeni özelliklerin bir ön görünümünü bizlere sunan WCF REST Starter Kit ile karşılaştık.

WCF Eco System' in bir parçası olan WebHttp Service' ler, .Net Framework 3.5 ile gelen Web Programlama modeli, REST Starter Kit ile tanıtılan kabiliyetler ve bunlara ek yeni özelliklerin .Net Framework 4.0 içerisinde ele alınmasını sağlayan bir geliştirme alt yapısı olarakta düşünülebilir. Şimdi dilerseniz WebHttp Service' lere bir merhaba demeye çalışalım. Örneğimizi Visual Studio 2010 Ultimate Beta 2 sürümü üzerinden geliştirmeye çalışıyor olacağız. Ancak yazıyı hazırladığım tarihte araştırdığım MSDN, The .NET Endpint vb blog sitelerinde yer alan bilgilere göre Visual Studio 2010' un o anki sürümü üzerinde WebHttp Service' leri için bir proje şablon(project template) bulunmamaktaydı. Bu nedenle öncelikli olarak online template' lerden WebHttp Service için olanları indirmemiz gerekiyor. Bu amaçla Visual Studio 2010 ortamında Tools->Extensions Manager->Online Gallery kısmına geçiş yapıp WCF ile ilişkili olan şablonlardan WCF Rest Service Template' i indirmemiz gerekiyor. Ben 4.0 versiyonunun C# programlama dili destekli olanını indirdim. Son sürümde büyük ihtimalle şu anda online olarak indirdiğimiz bu şablonun ve başka diğer şablonların Visual Studio 2010 içerisine gömülü olarak geleceğini ümit etmekteyim.

Download ve Install işlemlerinin ardından yolumuza devam edebiliriz. Bu amaçla, Visual Studio 2010 ortamında yeni bir proje oluşturup, Web sekmesinde yer alan WCF Rest Service Application şablonunu seçmemiz yeterli olacaktır. Bu şablonun kurulum işlemi sonrasında çıkmaması olasıdır. Bu durumda New Project iletişim kutusunda yer alan Enable the loading per-user extensions bağlantısını kullanarak etkinleştirme işlemini yapmamız yeterli olacaktır. Visual Studio 2010 ortamımızı tekrardan açtığımızda aşağıdaki ekran görüntüsünde olduğu gibi yeni proje şablonunun kullanabilir olduğunu göreceğiz.

HelloWebHttp isimli servis uygulamasına ait Solution içeriğinin ilk etapta otmatik olarak aşağıdaki gibi oluşturulduğunu gözlemleyebiliriz.

Service1.cs isimli örnek dosya içerisinde servis sözleşmesi(Service Contract) yer almaktadır. Bu sözleşme içerisinde yer alan metodlara WebGet ve WebInvoke niteliklerinin(Attributes) uygulandığı görülmektedir. Bu nitelikler bildiğiniz üzere servis operasyonlarına HTTP Post,Get,Put,Delete çağrılarının yapılabilmesi için gereklidir. Sınıf içerisine Get, Post, Put ve Delete metodlarının her biri için örnek nitelik kullanımları serpiştirilmiştir. Ayrıca serileştirilebilir örnek bir tipte yer almaktadır(SampleItem). GetCollection operasyonu SampleItem tipinden generic bir listeyi HTTP Get metoduna göre döndürmektedir. Create servis operasyonu ile yeni bir SampleItem nesnesinin HTTP Post metoduna göre örneklenmesi sağlanır. Tek bir SampleItem nesne örneğinin elde edilmesi için Get isimli servis operasyonunun aşırı yüklenmiş diğer bir versiyonu kullanılmaktadır. Update servis operasyonu ile HTTP Put metoduna göre güncelleme işlemi yapılmakta olup, Delete servis operasyonuda bir SampleItem nesnesinin HTTP Delete metoduna göre silinmesini sağlamaktadır. Solution içerisinde dikkat çekici bazı noktalar bulunmaktadır;

  • Örneğin web.config içeriği. Burada WCF 4.0 ile birlikte gelen basitleştirilmiş konfigurasyon(Simplified Configuration) özelliklerine yer verilmiştir. Bu sebepten daha sade, okunaklı ve fonksiyonel bir konfigurasyon içeriği oluşmuştur.
  • Dikkat çekici bir diğer noktada global.asax dosyasının var olmasıdır. Aslında bu bir web uygulaması olduğu için son derece normaldir. Lakin gözden kaçırılmaması gereken bir gerçek vardır; svc uzantılı bir servis dosyası fiziki olarak yoktur. Çünkü Asp.Net 4.0 Routing özelliği kullanılmaktadır. Çok tabi olarak yönlendirme işlemleri için global.asax dosyasında yapılması gereken bazı işlemler vardır. Bu nedenle hazır olarak global.asax içeriği aşağıdaki gibi üretilmektedir.

  • Yine servis kodlarına baktığımızda OperationContract niteliğinin kullanılmadığını görürüz. Oysaki .Net 3.5 ve WCF Rest Starter Kit sürümlerine baktığımızda WebGet ve WebInvoke nitelikleri dışında OperationContract niteliğininde kullanılması gerektiğini bilmekteyiz. OperationContract servis sözleşmelerinden sunulan operasyonların WCF çalışma zamanına bildirilmesinde rol oynadığı için bu son derece doğaldır. Ne varki WCF WebHttp servislerinde OperationContract niteliği opsiyoneldir.

Şimdi servis uygulamamız üzerinde bir kaç küçük değişiklik yapalım. Öncelikli olarak hayatımızı kolaylaştırmak adına Entity Framework' ten yararlanalım ve meşhur Chinook veritabanını ve işlemleri basit bir biçimde ele almak için sadece Artist tablosunu kullanmak istediğimizi düşünelim. Gerçi bu noktadan sonra biraz WCF Data Service' lere doğru kaymaya başlamış oluyoruz ancak amacımız tabiki HTTP Get,Post,Put ve Delete işlemlerini kendi kontrolümüz altında geliştirmek. 

Not : Tam bu noktada geliştirilen uygulamanın Data Service' ten veya RIA Service' ten ne farkı kaldığı sorusu akla gelebilir. Wink WCF WebHttp servislerinde asıl nokta operasyonun non-SOAP olacak şekilde sunulması(yani HTTP Get,Post,Put,Delete) ayrıca URI, format, protocol gibi bilgilerin tamamen geliştirici kontrolü altında olmasıdır.

Şimdi servis kodlarını aşağıdaki gibi geliştirdiğimizi varsayalım.

using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;

namespace HelloWebHttp
{
    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class HelloService
    {
        [WebGet(UriTemplate = "ArtistList")]
        public List<Artist> GetAllArtists()
        {
            ChinookEntities entites = new ChinookEntities();
            return entites.Artist.OrderByDescending(a => a.Name).ToList();
        }

        [WebGet(UriTemplate = "Artist/{name}")]
        public List<Artist> FindArtists(string name)
        {
            ChinookEntities entities = new ChinookEntities();

            return (from artist in entities.Artist
                    where artist.Name.Contains(name)
                    select artist).ToList();
        }

        [WebGet(UriTemplate = "Artist/InRange?idFirst={firstId}&idSecond={secondId}")]
        public List<Artist> GetArtistsInRange(int firstId, int secondId)
        {
            ChinookEntities entities = new ChinookEntities();
            return (from a in entities.Artist
                   where a.ArtistId >= firstId && a.ArtistId <= secondId
                   select a).ToList();
        }
    }
}

Dikkat edileceği üzere sadece HTTP Get metodlarının çalışmasına yönelik 3 örnek operasyon yer almaktadır. WebGet niteliklerinde UriTemplate özelliklerine atanan değerler yardımıyla HTTP Get taleplerinin nasıl olması gerektiği belirlenmektedir. Servise gelen taleplerin Routing sürecine dahil olması için global.asax.cs kodlarında da aşağıdaki değişiklikleri yapmamız yeterli olacaktır.

using System;
using System.ServiceModel.Activation;
using System.Web;
using System.Web.Routing;

namespace HelloWebHttp
{
    public class Global
        : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            RegisterRoutes();
        }

        private void RegisterRoutes()
        {
            RouteTable.Routes.Add(new ServiceRoute("Chinook", new WebServiceHostFactory(), typeof(HelloService)));
        }
    }
}

Dikkat edileceği üzere http://makineadı:portnumarası/Chinook üzerine gelen talepler sonrasında, WebServiceHostFactory' nin ayağa kaldırılması ve HelloService sınıfının örneklenerek işleme alınmasının sağlanması gerçekleştirilmektedir. Artık testlerimize başlayabiliriz.

Not: Dilerseniz Web uygulamasını IIS üzerinden host ederekte deneyebilirsiniz. Ancak ister Asp.Net Development Server ister IIS olsun, URL satırında RegisterRoutes metodunda yer alan Chinook bilgisini kullanmamız servise ulaşmamız için yeterli olacaktır.

Tabi test derken ilk etapta servis operasyonlarını nasıl çağırabileceğimizi bilemeyebiliriz. Yada bulmak için araştırmaya üşenebiliriz. Embarassed İşte bu amaçla WCF tarafına gelen Auto Help yetenekleri sayesinde çalışma zamanında yardım sayfasına gidebilir ve servis operasyonlarını nasıl çağırabileceğimizi, içeriklerinin ne olacağını görebiliriz.. Aynen aşağıdaki ekran görüntüsünde olduğu gibi.

İlk olarak belirli bir kelimeyi içeren Artist listesini elde etmek istediğimizi düşünelim. Örneğin adında Milton kelimesi geçenleri bulmak istiyoruz. Bu durumda URL satırında Chinook/Artist/Milton yazmamız yeterli olacaktır. Sonuçlar aşağıdaki ekran görüntüsünde olduğu gibidir.

Eğer tüm Artist listesini elde etmek istiyorsak bu durumda URL satırından Chinook/ArtistList bilgisini girmemiz yeterli olacaktır. Bu durumda elde edilen sonuçlar aşağıdaki ekran görüntüsünde olduğu gibidir.

Son olarak ArtistId değer aralığına göre Artist listesini elde etmek istediğimizi düşünelim. Bu durumda URL satırından Chinook/Artist/InRange?idFirst=155&idSecond=158 gibi bir URL satırı girmemiz yeterli olacaktır ki buna göre örneğin ArtistId değerleri 155 ile 158 aralığında olanların listesini aşağıdaki ekran görüntüsünde olduğu gibi elde edebiliriz.

Oldukça basit ve etkili değil mi? WCF WebHttp Service' ler ile ilişkili incelemelerimize devam ediyor olacağız. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

HelloWebHttp.rar (31,23 kb) [Örnek Visual Studio 2010 Ultimate Beta 2 Sürümünde geliştirilmiş ancak RC sürümü üzerinde de test edilmiştir]