https://www.buraksenyurt.com/Burak Selim Şenyurt - WCF WebHttp Services2017-01-18T06:26:42+00:00Matematik Mühendisi Bir Bilgisayar Programcısının NotlarıBurak Selim SenyurtBlogEngine.Net Syndication Generatorhttps://www.buraksenyurt.com/opml.axdBurak Selim SenyurtMatematik Mühendisi Bir Bilgisayar Programcısının Notlarıtr-TRBurak Selim Şenyurt0.0000000.000000https://www.buraksenyurt.com/post/WCF-WebHttp-Service-JSON-jQuery-Ajax-ve-CORS-ile-Yeni-Bir-MaceraWCF WebHttp Service, JSON, jQuery, Ajax ve CORS ile Yeni Bir Macera2014-09-03T08:27:00+00:00bsenyurt<p><a href="https://www.buraksenyurt.com/pics/browser_wars.jpg"><img style="background-image: none; float: right; padding-top: 0px; padding-left: 0px; margin: 4px 0px; display: inline; padding-right: 0px; border: 0px;" title="browser_wars" src="/pics/browser_wars_thumb.jpg" alt="browser_wars" width="300" height="236" align="right" border="0" /></a>Merhaba Arkadaşlar,</p>
<p>Bir süredir şirket içerisinde kullanılacak olan web tabanlı bir .Net uygulamasının geliştirilmesinde görev almaktayım. Uygulama, yürütülen süreç gereği her iterasyon sonunda yeni özellikler eklenmiş ve hataları giderilmiş biçimde Üretim<em>(Production)</em> ortamına taşınmakta.</p>
<p>Projede kaynak sıkıntısı nedeniyle uzun bir süre servis katmanı haricinde kalan arayüz tarafı ile de ilgilenmek zorunda kaldım. Arayüz tarafı ile uğraşırken iş biriminden gelen isteklere göre <strong>CSS<em>(Cascading Style Sheets)</em> </strong>ve bol miktarda <strong>Javascript</strong> kodlamak benim gibi acemiler için epeyce zorlayıcıydı. Lakin en çok zaman kaybettiğim vaka, şirket içinde kullanılmakta olan eski,yeni ve çeşitli tipteki tarayıcıların uyumlu çalışmasının sağlanabilmesiydi. Kimi lokasyonda <strong>Internet Explorer 8</strong>, kimi yerlede <strong>Google Chrome</strong>’ un en güncel sürümü bulunmakta. Hatta global çevrimde <strong>Firefox</strong> standart olarak her bilgisasyarda yüklü geliyor.</p>
<p>Şunu fark ettim ki, tarayıcı savaşları makalelerde okuduğumuzdan çok daha ciddi boyutta. Havada uçuşan standartları farklı farklı yorumlama biçimleri nedeniyle her tarayıcıya uygun standart çözümler üretmek gerçekten zormuş. En azından benim için <img class="wlEmoticon wlEmoticon-smile" style="border-style: none;" src="/pics/wlEmoticon-smile_105.png" alt="Smile" /> Basit bir <strong>CSS</strong>' in <strong>Internet Explorer'</strong> da sorunsuz çalışırken <strong>Chrome</strong>' da problem çıkarttığına, <strong>Chrome</strong>' da dertsiz işleyen bir <strong>Ajax Control Toolkit </strong>kontrolünün, <strong>Firefox</strong>’ un eski bir sürümünde hiç çalışmadığına şahit oldum. Hal böyle olunca çalışma zamanında, tarayıcıların debug kabiliyetleri ile de haşır neşir olmak zorunda kaldım. Sıkıcı mıydı? Hayır <img class="wlEmoticon wlEmoticon-winkingsmile" style="border-style: none;" src="/pics/wlEmoticon-winkingsmile_216.png" alt="Winking smile" /> Aksine benim için farklı ve değer katan deneyimlerdi. İşte bu düşünceler geçtiğimiz günlerde yine internet üzerinde bir şeyler araştırıp öğrenmeye çalışırken kendimi farklı bir macera içerisinde buldum. Sonunda bunu kaleme almanın yararlı olacağını düşündüm ve işte buradayım.</p>
<h1>Senaryo</h1>
<p>Bu yazımızda bir kavram ve terim cümbüşü içerisinde yer alacağımızı söyleyebilirim. Yazacağımız basit bir <strong>WCF</strong> servisini öncelikle <strong>REST</strong> tabanlı çalışır hale getireceğiz. Ardından söz konusu servise <strong>jQuery</strong> kütüphanesinden yararlanarak bir <strong>Ajax</strong> çağrısı gerçekleştireceğiz. Temel hedefimiz ise <strong>HTTP</strong> <strong>Post</strong> metoduna göre bir içeriği tarayıcı üzerinden servise göndermek olacak. Lakin <strong>JSON<em>(JavaScript Object Notation)</em></strong> tipinden bir nesne kullanacağız. Kabaca aşağıdaki çizelge de görülen durumun söz konusu olduğunu söyleyebiliriz.</p>
<p><a href="https://www.buraksenyurt.com/pics/restJquery_7_1.png"><img style="margin: 4px 0px; display: inline;" title="restJquery_7" src="/pics/restJquery_7_thumb_1.png" alt="restJquery_7" width="605" height="302" /></a></p>
<p>Bu toplu senaryo aslına bakılırsa günümüzün popüler pek çok web tabanlı uygulamasında kullanılabilecek türden. Haydi gelin parmaklarımızı sıvayalım...</p>
<h1>Servis Tarafının Geliştirilmesi</h1>
<p>İlk olarak aşağıdaki servis sözleşmesini içeren bir <strong>WCF Service Application</strong> projesi açarak yola çıkabiliriz. Söz konusu projede <strong>IProductService</strong> isimli bir sözleşme<em>(Service Contract)</em> yer alacak.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.ServiceModel;
using System.ServiceModel.Web;
namespace AzonServices
{
[ServiceContract]
public interface IProductService
{
[OperationContract]
[WebInvoke(Method = "POST",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "AddProduct")]
string PostProduct(Product NewProduct);
}
}</pre>
<p>Senaryomuzda sadece <strong>HTTP</strong> <strong>Post</strong> metodunu ele almak istediğimizden basit bir operasyon söz konusu. Önemli olan servis operasyonunun <strong>WebInvoke</strong> niteliği<em>(attribute)</em> ile işaretlenmiş olmasıdır. <strong>WebInvoke</strong> niteliği bu operasyonun <strong>HTTP</strong> tabanlı taleplere cevap verecek şekilde kullanılabileceğini ifade etmektedir.</p>
<p>Niteliğin içerisinde dikkat edileceği üzere bir kaç özelliğin set edildiği görülmektedir. <strong>Method</strong> özelliğine atanan değer ile, operasyonun <strong>HTTP</strong> <strong>Post</strong> taleplerine cevap vereceği belirtilmektedir. <strong>RequestFormat</strong> ve <strong>ResponseFormat</strong> özellikleri ile operasyona gelen ve istemcilere cevap olarak dönen içeriklerin <strong>JSON</strong> formatında serileştirileceği ifade edilir. Son olarak bir <strong>Uri</strong> şablonu atanmıştır. <strong>UriTemplate'</strong> e atanan <strong>AddProduct</strong> ifadesi, istemci tarafının göndereceği<strong> HTTP Post</strong> talebinde kullanılacaktır.</p>
<p>Servis metodu <strong>Product</strong> tipinden bir nesne örneğini alıp geriye <strong>string</strong> tipte içerik döndürecek şekilde tasarlanmıştır. <strong>Product</strong> tipi oldukça basit bir içeriğe sahiptir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">namespace AzonServices
{
public class Product
{
public int ProductId { get; set; }
public string Title { get; set; }
public decimal ListPrice { get; set; }
}
}</pre>
<p>Gelelim <strong>ProductService.svc</strong> öğesinin kodlarına.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Collections.Generic;
namespace AzonServices
{
public class ProductService
: IProductService
{
List<Product> productList = new List<Product>();
public string PostProduct(Product NewProduct)
{
productList.Add(NewProduct);
return Guid.NewGuid().ToString();
}
}
}</pre>
<p><strong>ProductService.svc</strong> içerisinde çok özel bir kod parçası yoktur. Sadece generic bir <strong>List<Product></strong> örneğine, <strong>PostProduct</strong> metoduna gelen <strong>Product</strong> örneğinin eklenmesi işlemi icra edilmektedir. Test sırasında istemcinin doğru bir cevap aldığını kolayca tespit etmek adına metod geriye benzersiz bir <strong>Guid</strong> değeri döndürmektedir.</p>
<h1>EndPoint Bildirimi</h1>
<p>Servis tarafı için önem arz eden konulardan birisi de <strong>EndPoint</strong> tanımlamasıdır. Servis, <strong>REST</strong> tabanlı olacak şekilde çalışabilmelidir. <strong>WCF</strong> bu noktada <strong>WebHttpBinding</strong> isimli <strong>Binding</strong> tipini sağlamaktadır. Bu sebepten<strong> web.config</strong> içerisinde gerekli tanımlamaların yapılması gerekmektedir. Aynen aşağıda görüldüğü gibi.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="AzonServices.ProductService">
<endpoint address=""
binding="webHttpBinding"
contract="AzonServices.IProductService" behaviorConfiguration="webBehavior"></endpoint>
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="webBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
</configuration></pre>
<p><strong>endpoint</strong> elementi içerisinde yer alan <strong>binding</strong> niteliğine <strong>webHttpBinding</strong> atanması haricinde bir de <strong>HTTP</strong> davranışının verilmesi söz konusudur. Bunun için dikkat edileceği üzere bir <strong>endPoint</strong> <strong>Behavior</strong> tanımlaması yapılmış ve <strong>webHttp</strong> değeri eklenmiştir. Eğer bir problem yoksa <strong>ProductService.svc</strong> dosyasının tarayıcı üzerinde aşağıdaki gibi açılması gerekir.</p>
<p><a href="https://www.buraksenyurt.com/pics/restJquery_1_1.png"><img style="margin: 4px 0px; display: inline;" title="restJquery_1" src="/pics/restJquery_1_thumb_1.png" alt="restJquery_1" width="640" height="348" /></a></p>
<blockquote>
<p>Servisin Metadata Publishing özelliği kapalıdır. Bilindiği üzere <strong>REST</strong> tabanlı servislere HTTP protokolü ve metodları ile erişilmektedir. Bu yüzden istemci tarafında bir Proxy nesnesi kullanılmasına gerek yoktur.</p>
</blockquote>
<h1>İstemci Tarafı</h1>
<p>Gelelim istemci uygulamanın geliştirilmesi. Servis tüketicisi bir Web uygulaması olarak inşa edilecektir. Detayları bir kenara bırakıp asıl konuya odaklanmak istediğimizden <strong>Asp.Net Empty Web Application</strong> projesi bizim için biçilmiş kaftandır. <strong>Web</strong> uygulamamızda <strong>jQuery</strong> kullanacağımızdan en azından ilgili <strong>javascript</strong> kütüphanesinin eklenmesi gerekir.</p>
<blockquote>
<p>Bunun için <a href="http://jquery.com/download/">http://jquery.com/download/</a> adresine giderek istediğiniz bir sürümü seçebilirsiniz. Sürüm seçiminde bu sayfada yazılan notlara dikkat etmenizi öneririm. Eğer kurumunuzun tarayıcılar ile ilişkili bazı kuralları varsa ve özellikle eski tarayıcılar ile çalışıyorlarsa uygun jQuery kütüphanesinin seçilmesi doğru olacaktır.</p>
</blockquote>
<p>Ben örnek projemizde <strong>jQuery-2.1.0.min.js</strong> sürümünü kullanmayı tercih ettim. İlgili <strong>Script</strong> dosyasını projeye ekledikten sonra <strong>Default.aspx</strong> sayfasını aşağıdaki gibi geliştirebiliriz.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="ClientApp.Default" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>REST Service Test</title>
<meta http-equiv="X-UA-Compatible" content="IE=10" />
</head>
<body>
<script type="text/javascript" src="Scripts/jquery-2.1.0.min.js">
</script>
<script type="text/javascript">
function AddNewProduct() {
var product = {
"ProductId": 1220,
"Title": "ElCiii 4580 Laptop",
"ListPrice": "1499"
};
$.ajax({
type: "POST",
url: "http://localhost:61954/ProductService.svc/AddProduct",
data: JSON.stringify(product),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (data, status, xmlRequest) {
alert("JSON içeriği "+JSON.stringify(product)+". "+data + " numaralı ürün eklenmiştir");
},
error: function (xmlRequest,status,errorThrown) {
alert(xmlRequest.responseText);
}
});
}
</script>
<form id="form1" runat="server">
<div>
<input type="button" value="Add New Product" onclick="AddNewProduct()" />
</div>
</form>
</body>
</html></pre>
<h1>Çalışma Şekli</h1>
<p>Şimdi web sayfasını biraz inceleyelim. Burada koşullara ve şartlara göre uygulanmış bazı hileler de bulunmaktadır. İlk olarak projeye ilave edilmiş <strong>jQuery</strong> kütüphanesinin kullanılacağı belirtilmiştir. <strong>Form</strong> üzerinde <strong>button</strong> tipinden bir <strong>input</strong> elementi yer almaktadır. İstemci tarafında bu buton tıklandığında ise <strong>AddNewProduct</strong> isimli <strong>javascript</strong> fonksiyonu çalıştırılmaktadır. Peki fonksiyon içerisinde neler olmaktadır?</p>
<p>İlk olarak <strong>product</strong> isimli bir tip oluşturulduğunu ve <strong>ProductId</strong>, <strong>Title</strong>, <strong>ListPrice</strong> özelliklerine bir takım değerler atandığını görebiliriz. Bu tanımlamayı takip eden satırda ise <strong>ajax</strong> fonksiyon çağrısı gerçekleştirilmektedir. <strong>ajax</strong> fonksiyonun pek çok parametresi bulunmaktadır. Örnekte <strong>HTTP Post</strong> çağrısı gerçekleştirileceğinden <strong>type</strong> özelliğine <strong>POST</strong> değeri atanmıştır. <strong>url</strong> özelliği tahmin edileceği üzere <strong>HTTP</strong> <strong>Post</strong> talebinin gönderileceği WCF Servis adresini işaret etmektedir. Bu adres tanımında yer alan <strong>AddProduct</strong> son ekine ayrıca dikkat edilmelidir.</p>
<p>Hatırlanacağı üzere bu bilgi servis operasyonunun <strong>WebInvoke</strong> niteliğinde belirtilmiştir. data kısmında gerçekleştirilen <strong>stringify</strong> çağrısı, parametre olarak aldığı <strong>product</strong> nesne örneğini <strong>JSON</strong> formatına çevirmek üzere kullanılır. Böylece servise gönderilecek olan <strong>JSON </strong>içeriği oluşturulur. <strong>contentType</strong> özelliğine atanan değer ile içerik tipinin <strong>JSON </strong>olacağı ve karakter seti olarak <strong>utf-8</strong> standardının kullanılacağı belirtilmektedir. <strong>dataType</strong> özelliği <strong>POST</strong> işlemi sırasında kullanılan veri tipinin <strong>JSON </strong>olduğunu işaret eder. <strong>success</strong> ve <strong>error</strong> değişkenleri tahmin edileceği üzere çağrının başarılı veya hata olması durumlarnda devreye giren fonksiyonları taşımaktadır. Her iki fonksiyon da standart olarak <strong>XmlHttpRequest</strong> tipini kullanır.</p>
<p>Biz örneğimizde bu fonksiyonellikler içerisinde önemli bir iş yapmıyoruz. Sadece çağrının başarılı olması halinde gönderilen <strong>JSON</strong> içeriğini ve servisden gelen <strong>GUID</strong> değerini bir mesaj kutusu içerisine gösteriyoruz. Pek tabi gelen içeriğin sayfa üzerinde yer alan bir takım kontrollere basılması da düşünülebilir.</p>
<p>Web sayfasında dikkat edilmesi gereken noktalardan birisi de title elementinin hemen altında kullanılan meta tag' dir.</p>
<p><strong><meta http-equiv="X-UA-Compatible" content="IE=10" /></strong></p>
<p>Bunu şöyle ifade etmeye çalışalım. Örneği gerçekleştirdiğimiz sistemde <strong>Internet Explorer</strong>' ın <strong>10</strong> sürümü bulunmakta ve web sayfasının aslında <strong>IE Compatibility Mode</strong>' da çalıştığı görülmektedir. Nitekim bu bildirimin <strong>meta tag</strong> olarak bildirilmemesi halinde istemci tarafında bir <strong>script</strong> hatası ile karşılaşılmaktadır.</p>
<p><a href="https://www.buraksenyurt.com/pics/restJquery_2_1.png"><img style="margin: 4px 0px; display: inline;" title="restJquery_2" src="/pics/restJquery_2_thumb_1.png" alt="restJquery_2" width="432" height="279" /></a></p>
<blockquote>
<p>Bu sorun IE 11' de kendini göstermeyebilir. Ya da jQuery kütüphanesinin daha eski bir sürümü böyle bir hatayı oluşturmayabilir. Hatta bu meta tag açık olduğunda Document Mode' un IE 9, 8 ve 7 olduğu durumlarda kütüphanenin aynı hatayı vermeye devam ettiği de tespit edilmiştir. Bu tarayıcıları anlamak hakikaten zor <img class="wlEmoticon wlEmoticon-confusedsmile" style="border-style: none;" src="/pics/wlEmoticon-confusedsmile_33.png" alt="Confused smile" /></p>
</blockquote>
<h1>Uyumluluk Sonrası Chrome Öncesi ve CORS</h1>
<p>Örneğimizi <strong>Internet Explorer</strong> ile<em>(en azından sistemde var olan sürümü ile)</em> uyumlu hale getirdik diyebiliriz. <strong>Default.aspx</strong> sayfasında <strong>Add New Product</strong> başlıklı butona bastığımızda aşağıdakine benzer bir mesaj kutusu ile karşılaşmamız gerekmektedir.</p>
<p><a href="https://www.buraksenyurt.com/pics/restJquery_3_1.png"><img style="margin: 4px 0px; display: inline;" title="restJquery_3" src="/pics/restJquery_3_thumb_1.png" alt="restJquery_3" width="464" height="295" /></a></p>
<p>Görüldüğü üzere başarılı bir şekilde servis çağrısı yapılmıştır. <strong>JSON</strong> içeriği üretilmiş ve servisden benzersiz bir <strong>GUID</strong> değeri elde edilmiştir. Ne var ki örnek <strong>Chrome</strong>' da çalışmamaktadır <img class="wlEmoticon wlEmoticon-surprisedsmile" style="border-style: none;" src="/pics/wlEmoticon-surprisedsmile_6.png" alt="Surprised smile" /> <em>(Yine örneğin geliştirildiği makinedeki tarayıcı sürüm için böyle bir durum oluştuğunu ifade edelim) </em></p>
<p><a href="https://www.buraksenyurt.com/pics/restJquery_4_1.png"><img style="margin: 4px 0px; display: inline;" title="restJquery_4" src="/pics/restJquery_4_thumb_1.png" alt="restJquery_4" width="345" height="118" /></a></p>
<p>Pek de sevimli olmayan bir hata mesajı <img class="wlEmoticon wlEmoticon-sadsmile" style="border-style: none;" src="/pics/wlEmoticon-sadsmile_16.png" alt="Sad smile" /> Eğer Chrome tarafında <strong>debug</strong> işlemi uygulanırsa aşağıdaki gibi bazı hataların oluştuğuna şahit olunur. İşte buton tıklandıktan sonraki durum.</p>
<p><a href="https://www.buraksenyurt.com/pics/restJquery_5_1.png"><img style="margin: 4px 0px; display: inline;" title="restJquery_5" src="/pics/restJquery_5_thumb_1.png" alt="restJquery_5" width="621" height="477" /></a></p>
<p>3 hata mesajı söz konusudur. Hata mesajlarının ikisi <strong>jQuery</strong> kütüphanesinden gelmektedir ama ana fikir söz konusu metod çağrısına izin verilmemiş olmasıdır. Aslında dikkatli gözler şunu hemen fark edecektir. Web uygulamasının host ediliği <strong>port</strong> ile WCF Service uygulamasının host edildiği <strong>port</strong> birbirinden farklıdır. Bu <strong>Cross Domain</strong> çağrı <strong>Chorme</strong> tarafından işlenmemiştir. Çözüm olarak<em>(ki burada istediğimiz sadece servisin Chrome üzerinden IE' de olduğu gibi çağırılabildiğini görmektir)</em> ilgili servisin ve web uygulamasının aynı domain' de host edilmesi sağlanabilir. Yani <strong>IIS</strong> altına atılmaları halinde her hangibir sorun olmadan çağırılabildikleri görülecektir.</p>
<blockquote>
<p>Modern tarayıcıların bu tip Cross Domain referans çağrılarına izin vermediği bilinmektedir. Servislerin bu noktada çözüm olarak istemciden gelecek olan bu tip Header' ları kabul edecek şekilde tesis edilmesi gerekmektedir. Bu sıkıntı CROS olarak isimlendirilmiştir. Dolayısıyla servis tarafı CORS <em>(Cross-Origin Resource Sharing)</em> özelliğini desteklemelidir. Bir başka deyişle servisin istemciden gelen Header bilgisine göre POST talebini kabul edecek şekilde ayarlanması sorunu çözecektir.</p>
</blockquote>
<h1>Sorunu Büyüttük</h1>
<p>Görüldüğü üzere yeni bir mücade ile karşı karşıyayız. <strong>WCF</strong> servisini <strong>CORS</strong> destekli hale getirmek çözümlerden bir tanesi. Ancak oldukça zahmetli olan bu yola yazımızda değinmeyeceğiz. Yine de ilgilenler <a href="http://enable-cors.org/server_wcf.html">http://enable-cors.org/server_wcf.html</a> adresine uğrayabilirler. Daha basit bir çözüm olarak <strong>WCF Service Application'</strong> ın aslında bir Web uygulaması gibi davranış gösterdiğini düşünerek hareket edeceğiz. Dolayısıyla bir <strong>global.asax</strong> dosyası ve gelen uygulamaya gelen taleplerin yakalandığı olay metodları söz konusudur. Bu noktada Application_BeginRequest metodu içeriğini aşağıdaki kod parçasında görüldüğü gibi yazmamız yeterli olacaktır.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">protected void Application_BeginRequest(object sender, EventArgs e)
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type");
HttpContext.Current.Response.End();
}
}</pre>
<p><strong>BeginRequest</strong> metodu tahmin edileceği üzere <strong>WCF</strong> servisini host ettiğimiz uygulamaya gelecek her talep için devreye girecektir. <strong>jQuery</strong> ile gerçekleştirdiğimiz <strong>ajax</strong> çağrısında <strong>ContentType</strong> <strong>Header</strong> bilgisi kullanılmış ve <strong>POST</strong> metoduna göre talep de bulunulmuştur. <strong>BeginRequest</strong> metodunun yaptığı pratikte bu şekilde gelen istekleri geri çevirmemek ve istemci tarafına da uygun olan <strong>Header</strong> bilgisini göndermektir. Söz konusu değişiklik sonrası uygulamanın <strong>Chrome</strong> üzerinde de sorunsuz bir şekilde çalışabildiği görülecektir.</p>
<p><a href="https://www.buraksenyurt.com/pics/restJquery_6_1.png"><img style="margin: 4px 0px; display: inline;" title="restJquery_6" src="/pics/restJquery_6_thumb_1.png" alt="restJquery_6" width="485" height="195" /></a></p>
<h1>Eksikler</h1>
<p>Elbette senaryomuzda önemli eksiklikler bulunmaktadır. Örneğin,</p>
<ul>
<li>Servis tarafının bir sertifika ile çağırılabileceği durumlarda CORS için nasıl aksiyonlar almak gerekir?</li>
<li>Son uygulanan pratik, tüm tarayıcılar da çalışmakta mıdır? Örneğin <strong>Firefox</strong>' ta. Peki ya mobil cihazda bulunan <strong>Native</strong> kodla geliştirilmiş bir Browser bileşeninde?</li>
<li>Acaba Windows Forms içerisinde kullanına WebBrowser gibi kontrollerde sonuç nasıl olacaktır?</li>
<li>Peki <strong>HTTP</strong> <strong>Get</strong> ile bir <strong>JSON</strong> veri kümesi istemci tarafına nasıl çekilebilir?</li>
<li>Ya veri göndermek için sayfa üzerine konacak kontrollerden nasıl yararlanılabilir?</li>
</ul>
<p>Bu soruların çözümünü, araştırmasını ve uygulanmasını siz değerli okurlarıma bırakıyorum. Bu mücadeleler inanın size önemli saha tecrübeleri kazandıracaktır. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><a href="https://www.buraksenyurt.com/pics/2014%2f2%2fHowTo_RestPostJQuery.rar">HowTo_RestPostJQuery.rar (111,76 kb)</a></p>2014-09-03T08:27:00+00:00wcfweb http servicesrestrest servicesjsonjqueryajaxcorsbsenyurtBu yazımızda bir kavram ve terim cümbüşü içerisinde yer alacağımızı söyleyebilirim. Yazacağımız basit bir WCF servisini öncelikle REST tabanlı çalışır hale getireceğiz. Ardından söz konusu servise jQuery kütüphanesinden yararlanarak bir Ajax çağrısı gerçekleştireceğiz. Temel hedefimiz ise HTTP Post metoduna göre bir içeriği tarayıcı üzerinden servise göndermek olacak. Lakin JSON(JavaScript Object Notation) tipinden bir nesne kullanacağız. Kabaca aşağıdaki çizelge de görülen durumun söz konusu olduğunu söyleyebiliriz.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=e998c39f-2d74-4710-8734-952e459637d13https://www.buraksenyurt.com/trackback.axd?id=e998c39f-2d74-4710-8734-952e459637d1https://www.buraksenyurt.com/post/WCF-WebHttp-Service-JSON-jQuery-Ajax-ve-CORS-ile-Yeni-Bir-Macera#commenthttps://www.buraksenyurt.com/syndication.axd?post=e998c39f-2d74-4710-8734-952e459637d1https://www.buraksenyurt.com/post/Silverlight-JSON-ile-CalismakSilverlight - JSON ile Çalışmak2010-08-13T01:15:00+00:00bsenyurt<p><img style="float: right;" src="/pics/2010%2f4%2fblg177_Giris.jpg" alt="" />Merhaba Arkadaşlar,</p>
<p>Uzun süredir şöyle deliksiz uyuyamıyordum. Malum evde bir afacan var. Pek uyumayı sevmeyen, sürekli hareket halinde olmak isteyen S(h)arp Efe izin verdiğinde, eşim ve ben dinlenmek için çeşitli işlere dalıyoruz. Ben uzun süredir <strong>Bulmacalara </strong>takılmış durumdayım. Bir de şu eski dil karşılıklarını isteyen sorular olmasa. Geçtiğimiz günlerde yine böyle bir boşluk yakalamışken, kendimi bulmacalar arasında yüzerken buluverdim. Ancak bir süre sonra "...eski dildeki karşılığı..." sorularından sıkıldım ve televizyonda neler olduğuna bir akayım dedim.</p>
<p>Televizyonda yandaki resimde görülen adam vardı ve ismi <strong>Jason</strong>' dı. Açıkçası <strong>Jason Statham</strong>' ın fanatiği bir sinemasever olarak bu isim benzerliğinin, böyle korkutucu bir karakter üzerinde olması beni üzmüştü. Nitekim Jason ismini düşününce aklıma gıcır gıcır parlayan <strong>Audi </strong>marka arabalar gelmekteydi. Her neyse...Filme fazla takılmadım ama <strong>Jason, Jason</strong> derken, bu isim <strong>JSON </strong>diye dudaklarımdan süzülmeye başladı. Pek tabi bunun doğal sonucu olarak bilgisayarımın başına oturdum ve <strong>JSON </strong>ile ilişkili bir şeyler yazmaya karar verdim. İşte başlıyoruz <img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /></p>
<p>Bildiğiniz üzere <strong>HTTP </strong>bazlı <strong>WCF </strong>servislerinden(<a class="postheader taggedlink" href="https://www.buraksenyurt.com/admin/app/editor/post/WCF-WebHttp-Services-JSON-Formatli-Response-Uretmek" target="_blank">WCF WebHttp Services - JSON Formatlı Response Üretmek</a>) <strong>JSON(JavaScript Object Notation) </strong>formatında çıktılar yayınlanabilmektedir. Bazı durumlarda istemci tarafı, <strong>JSON </strong>veri içeriği ile çalışmayı tercih edilebilir. Özellikle <strong>XML </strong>ile karşılaştırıldığında, <strong>JSON</strong> formatının daha az yer tutan bir yapıya sahip olması, bu seçimin yapılmasında önemli bir etkendir. Biz bu yazımızda bir <strong>WCF WebHttp Service </strong>tarafından yayınlanan <strong>JSON </strong>formatlı veri çıktısının, örnek bir <strong>Silverlight </strong>istemcisi tarafından nasıl ele alınabileceğini incelemeye çalışıyor olacağız.</p>
<p>Silverlight tarafında <strong>JSON </strong>içeriği ile çalışabilmek adına geliştirilmiş <strong>JsonArray, JsonObject, JsonPrimitive </strong>gibi tipler bulunmaktadır. Bu tipler sayesinde <strong>JSON </strong>veri kümesinde yer alan <strong>string, number, Boolean </strong>gibi veri türleri kod içerisinde ele alınabilir. Ayrıca tek <strong>JSON </strong>nesnesi veya bir <strong>JSON </strong>nesne listesinin ele alınması da sağlanabilir. Bu geliştiriciler için önemlidir. Nitekim Web ortamında gelen <strong>JSON </strong>içeriğinin <strong>Parse </strong>edilme işlemleri ile uğraşılmasına gerek kalmamaktadır.</p>
<p>Dilerseniz hiç vakit kaybetmeden örnek bir <strong>Silverlight </strong>uygulaması üzerinden ilerlemeye çalışalım. İşe ilk olarak <strong>IIS</strong> üzerinde <strong>host </strong>edeceğimiz<strong> WCF Rest Service Application</strong> projesini ve aşağıdaki kod içeriğine sahip <strong>LogService </strong>servis örneğini geliştirerek başlayabiliriz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
namespace TraceLogServiceApplication
{
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall)]
public class LogService
{
[WebGet(UriTemplate = "Logs/All",ResponseFormat=WebMessageFormat.Json)]
public List<Log> GetAllLogs()
{
return new List<Log>()
{
new Log{ Source="Sql Server", Content="Sql servisi başlatıldı", IsCritical=false, Level=5},
new Log{ Source="Sql Server", Content="Sql Agent servisinde hata.", IsCritical=true, Level=1},
new Log{ Source="DTC", Content="Dağıtık Transaction nesnesi üretildi.", IsCritical=false, Level=3},
new Log{ Source="WF Runtime", Content="Süreç persist edildi", IsCritical=true, Level=2}
};
}
}
public class Log
{
public string Source { get; set; }
public string Content { get; set; }
public int Level { get; set; }
public bool IsCritical { get; set; }
}
}</pre>
<p><strong>LogService </strong>içerisinde yer alan <strong>GetAllLogs </strong>isimli servis operasyonu <strong>Log </strong>tipinden bir kaç eleman içeren basit bir <strong>List<Log> </strong>koleksiyonunu geriye döndürmektedir. Çalışma zamanında oluşturulacak olan bu içerik, istemci tarafına <strong>JSON </strong>formatında gönderilecektir. Bunun için dikkat edileceği üzere <strong>ResponseFormat </strong>özelliğinin değeri <strong>WebMessageFormat.Json </strong>sabiti olarak belirlenmiştir. Servisimizi bu haliyle test etmek istediğimizde adres satırından <strong>http://localhost:12043/LogService/Logs/All </strong>gibi bir çağrı yapmamız yeterli olacaktır. Bunun sonucunda aşağıdaki <strong>JSON </strong>içeriği üretilecektir.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false">[{"Content":"Sql servisi başlatıldı","IsCritical":false,"Level":5,"Source":"Sql Server"},{"Content":"Sql Agent servisinde hata.","IsCritical":true,"Level":1,"Source":"Sql Server"},{"Content":"Dağıtık Transaction nesnesi üretildi.","IsCritical":false,"Level":3,"Source":"DTC"},{"Content":"Süreç persist edildi","IsCritical":true,"Level":2,"Source":"WF Runtime"}]</pre>
<p>Bu işlemin ardından servisi <strong>IIS </strong>alınta <strong>Publish </strong>etmemiz yeterlidir. <strong>Publish </strong>ayarlarını aşağıdaki resimde görüldüğü gibi belirleyebiliriz.</p>
<p><img src="/pics/2010%2f4%2fblg177_PublishProfile.gif" alt="" /></p>
<p>Eğer <strong>Publish </strong>işlemi başarılı olduysa<em>(<strong>IIS </strong>üzerinden ilgili uygulamanın <strong>Web Application </strong>olarak set edilmesine</em><em>-Convert to Application seçeneği dikkat ederekten) </em>herhangibir tarayıcı uygulamadan, <strong>http://localhost/TraceLogServiceApplication/LogService/Logs/All </strong>şeklinde bir çağrıda bulunabiliyor olmamız gerekmektedir ki bu çağrının sonucu olarakta, yukarıdaki <strong>JSON </strong>içeriğine tekrardan ulaşabiliyor olmalıyız.</p>
<p><a href="https://www.buraksenyurt.com/pics/2010%2f4%2fTraceLogServiceApplication.rar">TraceLogServiceApplication.rar (30,47 kb)</a> <strong>[Örnek Visual Studio 2010 Ultimate sürümü üzerinde test edilmiştir]</strong></p>
<p>Tabi yapmamız gereken bir işlem daha bulunmaktadır. Hatırlayacağınız üzere <strong>Silverlight </strong>istemcileri için <strong>Cross-Domain Policy </strong>sorunsalı mevcuttur. Bu nedenle <strong>IIS </strong>üzerinde daha önceki yazılarda değindiğimiz <strong>ClientAccessPolicy.xml </strong>dosyasının içeriğini aşağıdaki gibi düzenlememiz ve <strong>TraceLogServiceApplication </strong>için gerekli <strong>garanti haklarını(grant-to)</strong> belirlememiz gerekmektedir.</p>
<p><img src="/pics/2010%2f4%2fblg177_CAPFile.gif" alt="" /></p>
<p>Artık <strong>Silverlight 4.0</strong> tabanlı istemci uygulamamızı geliştirmeye başlayabiliriz. Bu amaçla, <strong>JsonConsumer </strong>isimli <strong>Silverlight </strong>uygulamamız içerisindeki <strong>MaingPage.xaml </strong>ve kod içerikleri aşağıdaki gibi geliştirilebilir.</p>
<p><strong>MainPage.xaml içeriği;</strong></p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><UserControl x:Class="JsonConsumer.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
<Grid x:Name="LayoutRoot" Background="White">
<Button Content="Get All Logs" Height="23" HorizontalAlignment="Left" Margin="24,20,0,0" Name="GetLogsButton" VerticalAlignment="Top" Width="75" Click="GetLogsButton_Click" />
<sdk:DataGrid AutoGenerateColumns="True" ItemsSource="{Binding}" Height="204" HorizontalAlignment="Left" Margin="24,56,0,0" Name="LogsDataGrid" VerticalAlignment="Top" Width="347"/>
</Grid>
</UserControl></pre>
<p><strong>MainPage.xaml.cs içeriği;</strong></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.IO;
using System.Json;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
// JsonArray, JsonObject gibi tiplerin kullanılabilmesi için Silverlight projesine System.Json.dll assembly' ının referans edilmesi gerekmektedir.
namespace JsonConsumer
{
public partial class MainPage
: UserControl
{
WebClient client = null;
public MainPage()
{
InitializeComponent();
client= new WebClient();
client.OpenReadCompleted += new OpenReadCompletedEventHandler(client_OpenReadCompleted);
}
private void GetLogsButton_Click(object sender, RoutedEventArgs e)
{
client.OpenReadAsync(new Uri("http://localhost/TraceLogServiceApplication/LogService/Logs/All"));
}
void client_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
Stream responseStream = e.Result;
// JsonArray sınıfının static Load metodu, Http Web servisine yapılan talep sonrası dönen Stream örneğini alır.
// Load metodu JsonValue tipinden bir referans döndürmektedir ve dizi olarak ele alabilmek için JsonArray tipine bilinçli bir dönüşüm yapılmıştır.
JsonArray logs=(JsonArray)JsonArray.Load(responseStream);
// Elde edilen JSON verisinden IsCritical değeri true olanlar çekilir ve LogInfo isimli tip içerisinde toplanır.
var criticialLogs = from log in logs
where log["IsCritical"]
select new LogInfo
{
Content=log["Content"].ToString(),
Source=log["Source"].ToString(),
Level=log["Level"]
};
// Elde edilen veri kümesi DataGrid kontrolüne veri kaynağı olarak gösterilir
LogsDataGrid.DataContext = criticialLogs;
}
}
// Servis tarafındaki Log tipinin istemci tarafındaki karşılığı
public class LogInfo
{
public string Source { get; set; }
public string Content { get; set; }
public int Level { get; set; }
public bool IsCritical { get; set; }
}
}</pre>
<p>Hatırlayacağınız üzere <strong>WCF WebHttp Service</strong> örneklerine yapılacak olan istemci çağrıları için <strong>WebClient </strong>tipinden yararlanılmaktadır. Bu amaçla<strong> Button </strong>kontrolüne basıldığında, asenkron olarak söz konusu servise bir talepte bulunulmaktadır<strong>(OpenReadAsync)</strong>. Talep sonuçlandığında ise geri bildirim olay metodu devreye girmektedir<strong>(OpenReadCompleted)</strong>. İşte bu olay metodu içerisinde <strong>JSON </strong>veri içeriğinin ele alınması için gerekli işlemler gerçekleştirilmektedir.</p>
<p>Bu metoda ait kod parçasındaki en büyük yardımıcı <strong>JsonArray </strong>tipi ve <strong>Load </strong>fonksiyonudur . Bu fonksiyon, parametre olarak <strong>LogService </strong>isimli <strong>WCF WebHttp Servisine </strong>gönderilen talep sonucu, istemci tarafına indirilen <strong>Stream</strong> referansını kullanmaktadır. Sonuç daha sonradan basit bir <strong>LINQ </strong>sorgusu ile değerlendirilmiş ve örnek olarak kritik seviyedeki log bilgilerinin değerlendirilmesi amaçlanmıştır. Uygulamanın çalışma zamanı görüntüsü aşağıdaki gibi olacaktır.</p>
<p><img src="/pics/2010%2f4%2fblg177_Runtime.gif" alt="" /></p>
<p>Görüldüğü gibi <strong>JSON </strong>formatındaki içerik <strong>Silverlight </strong>tarafında başarılı bir şekilde ele alınmış ve <strong>veri bağlı bir kontrol(DataGrid)</strong> ile ilişkilendirilebilmiştir. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><a href="https://www.buraksenyurt.com/pics/2010%2f4%2fJsonConsumer.rar">JsonConsumer.rar (1,92 mb)</a> <strong>[Örnek Visual Studio 2010 Ultimate sürümü üzerinde test edilmiştir]</strong></p>2010-08-13T01:15:00+00:00silverlightwcfwcf webhttp servicesjsonbsenyurtSilverlight tarafında JSON içeriği ile çalışabilmek adına geliştirilmiş JsonArray, JsonObject, JsonPrimitive gibi tipler bulunmaktadır. Bu tipler sayesinde JSON veri kümesinde yer alan string, number, Boolean gibi veri türleri kod içerisinde ele alınabilir. Ayrıca tek JSON nesnesi veya bir JSON nesne listesinin ele alınması da sağlanabilir. Bu geliştiriciler için önemlidir. Nitekim Web ortamında gelen JSON içeriğinin Parse edilme işlemleri ile uğraşılmasına gerek kalmamaktadır.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=3c19fb81-50f0-4a9c-91ee-e93549217c2f2https://www.buraksenyurt.com/trackback.axd?id=3c19fb81-50f0-4a9c-91ee-e93549217c2fhttps://www.buraksenyurt.com/post/Silverlight-JSON-ile-Calismak#commenthttps://www.buraksenyurt.com/syndication.axd?post=3c19fb81-50f0-4a9c-91ee-e93549217c2fhttps://www.buraksenyurt.com/post/Silverlight-Tarafinda-HTTP-Bazli-Servisleri-KullanmakSilverlight Tarafında HTTP Bazli Servisleri Kullanmak2010-07-12T00:55:00+00:00bsenyurt<p><img style="float: right;" src="/pics/2010%2f4%2fblg175_Giris.jpg" alt="" />Merhaba Arkadaşlar,</p>
<p>Eğitmenlik yaptığım yıllarda <strong>Microsoft</strong>' un ders kitaplarında yer alan <strong>LAB </strong>çalışmalarını mümkün mertebe yapmaya ve yaptırmaya çalışırdım. Hatta çoğu zaman eğitimlere hazırlanırken sık sık bu lab çalışmalarını kendim yapar ve hatta ek ilaveler ile daha da eğlenceli hale getirmeye çalışırdım. Tabi bazen elimizde lab yapacağımız kitaplarımız olmazdı ki o ayrı bir hikaye.</p>
<p>Lab çalışmaları öğrencinin adım adım yapması gerekenleri söylerek, konunun en yalın haliyle anlaşılmasını sağlamakta önemli rol oynamaktadır. Lab çalışmalarındakine benzer konu anlatımları benimde özümsediğim ve faydalı bulduğum öğrenme tekniklerinden birisidir. İşte bu yazımızda da bu kültüre uymaya çalışarak ilerlemeye çalışıyor olacağız. Hedefimiz <strong>Silverlight </strong>uygulamalarından, <strong>HTTP </strong>tabanlı taleplere göre operasyonel hizmetlerde bulunan servisleri nasıl kullanabileceğimizi, en yalın haliyle görmek. Haydi o zaman lab için gerekli materyalleri değerlendirerek yola koyulalım.</p>
<p><strong>Adım 0 : Mevzumuz</strong></p>
<p>Bilindiği üzere bazı servisler <strong>HTTP </strong>protokolü üzerinden <strong>GET, POST, PUT</strong> veya <strong>DELETE </strong>metod çağrıları ile kullanılabilmektedir. Bu anlamda <strong>WCF Eco System</strong> içerisinde yer alan <a title="WCF WebHttp Services" href="https://www.buraksenyurt.com/archive.aspx#WCF-WebHttp-Services" target="_blank">WebHTTP servisleri</a>, söz konusu tipteki hizmetleri sunmak üzere WCF alt yapısı üzerine oturmuş bir model sunmaktadır. Çok doğal olarak <strong>Silverlight </strong>tabanlı istemciler de bu servislerin tüketicileri olabilirler. Bu tip servislerin kullanıldığı senaryolarda istemci tarafında herhangibir <strong>Proxy </strong>tipi söz konusu olmadığı için, <strong>HTTP GET,POST,PUT </strong>veya <strong>DELETE </strong>metodlarının manuel olarak hazırlanması ve gönderilmesi gerekmektedir. <strong>Silverlight </strong>tarafında bu işlemler için <strong>WebClient </strong>veya <strong>HttpWebRequest </strong>tiplerinden yararlanılabilmektedir. Biz bu yazımızda <strong>WebClient </strong>tipinden yararlanarak, <strong>IIS(Internet Information Services)</strong> üzerinde konuşlandırılmış basit bir <strong>WebHttp Service</strong> örneğinin nasıl kullanılabileceğini incelemeye çalışıyor olacağız.</p>
<p><strong>Adım 1 : WCF Rest Application Uygulaması ve Entity Data Model' in Oluşturulması<br /></strong></p>
<p>İşe ilk olarak <strong>WCF Rest Service Application</strong> şablonunda bir proje oluşturarak başlayabiliriz. Bildiğiniz üzere bu proje şablonu<strong>(Project Template)</strong> hali hazırda yüklü değilse <strong>Online Template</strong>' ler arasından <strong>install </strong>etmeniz gerekmektedir. Söz konusu örnekte <strong>Chinook </strong>veritabanında yer alan ve çok basit olarak ilerlemek istediğimizden sadece <strong>Album </strong>tablosunu içeren bir <strong>Entity Data Model </strong>kullanabiliriz. Aşağıdaki şekilde örneğimizde kullanmakta olduğumuz Entity Data Model yer almaktadır.</p>
<p><img src="/pics/2010%2f4%2fblg175_Edm.gif" alt="" /></p>
<p> </p>
<p><strong>Adım 2 : WebHttp Service Örneğinin Geliştirilmesi</strong></p>
<p><strong>Entities </strong>isimli <strong>WCF WebHttp Service</strong> sınıfımızın içeriğini ise aşağıdaki gibi düzenlediğimizi düşünebiliriz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
namespace ChinookDataPortal
{
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class Entities
{
[WebGet(UriTemplate = "Albums/All")]
public List<Album> GetAlbums()
{
List<Album> albums = null;
ChinookEntities entities = new ChinookEntities();
albums=(from albm in entities.Albums
orderby albm.Title
select albm).ToList();
return albums;
}
[WebGet(UriTemplate="Albums/{firstLetter}")]
public List<Album> GetAlbumsByFirstLetter(string firstLetter)
{
List<Album> albums = null;
ChinookEntities entities = new ChinookEntities();
albums = (from albm in entities.Albums
where albm.Title.ToLower().StartsWith(firstLetter.ToLower())
orderby albm.Title
select albm).ToList();
return albums;
}
}
}</pre>
<p>Servis tipimiz iki operasyon içermekte olup her ikiside <strong>HTTP Get </strong>çağrılarına cevap verecek şekilde düzenlenmişlerdir. <strong>GetAlbums</strong> metoduna yapılan çağrılarda servis <strong>URL </strong>adresine <strong>Albums/All </strong>takısı eklenmelidir. Diğer yandan ilk harflerine göre albümleri listeleyen <strong>GetAlbumsByFirstLetter </strong>metodu, <strong>URL </strong>adresine <strong>Albums/{firstLetter} </strong>bilgisinin eklenmesini beklemektedir. Her iki metod <strong>ChinookEntities </strong>tipini kullanmakta ve basit <strong>LINQ </strong>sorguları ile sonuç üretmektedir. Servisimizi bu şekilde geliştirdikten sonra IIS altına Publish ederek devam edebiliriz.</p>
<p><strong>Adım 3 : IIS Publish</strong></p>
<p>Publish işlemleri için aşağıdaki şekilde görülen Profile ayarlarını kullanabilirsiniz.</p>
<p><img src="/pics/2010%2f4%2fblg175_PublishProfile.gif" alt="" /></p>
<p>Bu ayarlara göre servisimizin <strong>IIS </strong>üzerinde yer alan <strong>Default Web Site</strong> isimli <strong>Application Pool </strong>altına dağıtılacağı belirtilmiş olunur.</p>
<p><em><strong>Not: IIS </strong>üzerinden <strong>Convert To Application </strong>işlemini yapmanız gerekebilir.</em></p>
<p>Sonuç olarak <strong>IIS </strong>içerisinde aşağıdaki gibi servisin üretilmiş olması gerekmektedir. Bu arada örneği geliştirdiğimiz makinede <strong>Windows 7 Enterprise </strong>işletim sisteminin ve <strong>IIS 7.5.7600.16385 </strong>sürümünün olduğunu belirtelim.</p>
<p><img src="/pics/2010%2f4%2fblg175_IIS.gif" alt="" /></p>
<p>Bu noktadan sonra Silverlight uygulamasının geliştirilmesi aşamına geçilecektir. Ancak öncelikle gerekli testleri yapılmasında yarar vardır.</p>
<p><a href="https://www.buraksenyurt.com/pics/2010%2f4%2fChinookDataPortal.rar">ChinookDataPortal.rar (46,54 kb)</a><strong> [Örnek Visual Studio 2010 Ultimate RC ortamında geliştirilmiş ve test edilmiştir]</strong></p>
<p><strong>Adım 4 : WebHttp Service Test<br /></strong></p>
<p><strong>Silverlight </strong>tarafındaki uygulamamızı geliştirmeden önce servisimizi <strong>IIS </strong>üzerinden test etmemizde ve çalıştığından emin olmamızda yarar olacağı kanısındayım. İlk olarak yardım sayfasına ulaşıp ulaşamadığımızı öğrenelim. Bilindiği üzere WebHttp Service örnekleri aksi belirtilmedikçe hazır bir yardım sayfası sunmaktadır. Bu amaçla tarayıcı uygulamadan <strong>http://localhost/ChinookDataPortal/Entities/help </strong>şeklinde bir talepte bulunduğumuzda, aşağıdaki ekran çıktısı ile karşılaşmış olmalıyız.</p>
<p><img src="/pics/2010%2f4%2fblg175_HelpPage.gif" alt="" /></p>
<p>Yardım sayfasının çalışıyor olması dışında servis tarafında yer alan operasyonel metodların da test edilmesinde yarar vardır. Örneğin tüm albümleri elde etmek için<strong> http://localhost/ChinookDataPortal/Entities/Albums/All </strong>şeklinde talepte bulunduğumuzda, aşağıdaki ekran görüntüsünde yer alan sonuçları elde etmiş olmamız gerekmektedir. Tabi veri içeriklerinde değişiklikler söz konusu olabilir. Ancak XML çıktısının şematik yapısının benzer olması gerekmektedir.</p>
<p><img src="/pics/2010%2f4%2fblg175_AllAlbums.gif" alt="" /></p>
<p>Son olarak örneğin <strong>Cake </strong>adı ile başlayan albümleri çekmek istediğimizi ve bu amaçla <strong>URL </strong>satırından <strong>http://localhost/ChinookDataPortal/Entities/Albums/Cake </strong>şeklinde bir talep gönderdiğimizi düşünelim. Bu durumda ekran çıktısının aşağıdakine benzer olması gerekmektedir.</p>
<p><img src="/pics/2010%2f4%2fblg175_ByFirstLetter.gif" alt="" /></p>
<p>Eğer bu sonuçları elde edebiliyorsak servisimizin çalıştığını ve <strong>Sliverlight </strong>tarafı için kullanılabilir olduğunu söyleyebiliriz. Lakin dikkat etmemiz gereken bir nokta daha vardır.</p>
<p><strong>Adım 5 : Client Access Policy Ayarları</strong></p>
<p><strong>Silverlight </strong>uygulamamızın farklı bir <strong>Domain </strong>içerisinde <strong>host </strong>edilmesine karşılık, <strong>IIS </strong>üzerinde gerekli<strong> Client Access Policy</strong> ayarlarının bulunması gerekmektedir. Bu nedenle<strong> IIS root </strong>klasörü altında yer alması gereken <strong>ClientAccessPolicy.xml </strong>dosyasının içeriğini aşağıdaki gibi düzenleyebiliriz.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><?xml version="1.0" encoding ="utf-8"?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from>
<domain uri="*" />
</allow-from>
<grant-to>
<resource path="/WorldWeatherService" include-subpaths="true"/>
<resource path="/ChinookDataPortal" include-subpaths="true"/>
</grant-to>
</policy>
</cross-domain-access>
</access-policy></pre>
<p>Burada görüleceği üzere ChinookDataPortal ve alt yollarına erişim izni verilmiştir. Artık <strong>Silverlight </strong>tarafını geliştirmeye başlayabiliriz.</p>
<p><strong>Adım 6: Silverlight Application Projesinin Oluşturulması</strong></p>
<p>Bu amaçla <strong>Visual Studio 2010</strong> ortamında <strong>ConsumingHTTPBasedServices </strong>isimi ve <strong>Silverlight 4.0</strong> tabanlı bir <strong>Application </strong>oluşturduğumuzu düşünelim. Söz konusu uygulamada RIA Service kullanılmayacağı için bu seçeneği pasif olarak bırakabiliriz. Bu işlem sonucu oluşturulan <strong>MainPage </strong>sayfasına ait <strong>XAML </strong>içeriğini ise aşağıdaki gibi geliştirebiliriz.</p>
<p><strong>Adım 7 : MainPage.Xaml içeriği ve Kodun Yazılması</strong></p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><UserControl x:Class="ConsumingHTTPBasedServices.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="517">
<Grid x:Name="LayoutRoot" Background="White">
<ListBox Height="203" HorizontalAlignment="Left" Margin="8,70,0,0" Name="AlbumListBox" VerticalAlignment="Top" Width="497" />
<StackPanel Height="54" HorizontalAlignment="Left" Margin="9,10,0,0" Name="ButtonsStackPanel" VerticalAlignment="Top" Width="496" Orientation="Horizontal" />
</Grid>
</UserControl></pre>
<p><strong>MainPage </strong>içerisinde yer alan <strong>ListBox </strong>kontrolü içeriği<strong> A' dan Z' ye harfler</strong> ile doldurulacaktır. Herhangibir harfe basıldığında, <strong>WebHttp Service</strong>' imiz için bir <strong>HTTP Get </strong>talebi oluşturulacak ve sonuçların <strong>ListBox </strong>içerisinde gösterilmesi sağlanacaktır. Bu amaçla kod içeriğini aşağıdaki gibi geliştirmemiz yeterlidir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Linq;
using System.Net;
using System.Windows.Controls;
using System.Xml;
using System.Xml.Linq;
namespace ConsumingHTTPBasedServices
{
public partial class MainPage
: UserControl
{
// WebHttp Servisine basit HTTP metodları ile talepte bulunabilmemizi sağlayan WebClient nesnesi tanımlanır
WebClient client;
public MainPage()
{
InitializeComponent();
// WebClient nesnesi örneklenir
client=new WebClient();
// Belirtilen URL adresine yapılan talep sonucu gerçekleşecek okuma işlemi tamamlandığında(bir başka deyişle veri istemci tarafında indirildiğinde) devreye girecek olan olay metodu tanımlanır.
client.OpenReadCompleted += new OpenReadCompletedEventHandler(client_OpenReadCompleted);
// A...Z Button üretimleri gerçekleştirilir
for (int i = 65; i < 91; i++)
{
Button btn = new Button();
btn.Width = 18;
btn.Height = 18;
btn.FontSize = 10;
btn.Content = ((char)i).ToString();
ButtonsStackPanel.Children.Add(btn);
// Herhangibir Button tıklandığında
btn.Click += (o, e) =>
{
// Önce WebHttp Service' ne doğur yapılacak HTTP Get talebi için gerekli URI oluşturulur
Uri address = new Uri(String.Format("http://localhost/ChinookDataPortal/Entities/Albums/{0}", ((Button)o).Content));
// Belirtilen URI talebi asenkron olarak çalışan OpenReadAsycn metodu ile gönderilir
client.OpenReadAsync(address);
};
}
}
// URI ile belirtilen adres talebi gerçekleştirilip ilgili veri içeriği istemci tarafına indirildikten sonra devreye giren olay metodudur
void client_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
// İçerik bir Stream olarak gelmektedir ve tasarlanan ChinookDataPortal WebHttp Servisi varsayılan olarak XML içerik göndermektedir.
// Bu sebepten Stream XmlReader ile okunur
XmlReader xReader = XmlReader.Create(e.Result);
// XLINQ sorgusunun yapılabilmesi için XElement.Load metodu parametre olarak Stream' i kullanan XmlReader nesne örneğini alır
XElement xElement = XElement.Load(xReader);
// XLINQ sorgusu ile Title elementleri çekilir. XName.Get metodunun ikinci parametre XML Namespace' inin adıdır.
var titles = from x in xElement.Elements().Elements(XName.Get("Title", "http://schemas.datacontract.org/2004/07/ChinookDataPortal"))
select x.Value;
// Çekilen veri içeriği ListBox kontrolünün ItemsSource özelliğine bağlanır
AlbumListBox.ItemsSource = titles;
}
}
}</pre>
<blockquote>
<p>XElement tiplerini kullanabilmek ve XLINQ sorgularını yazabilmek için, Silverlight uygulamasına(ConsumingHTTPBasedServices.Web uygulamasına değil) System.Xml.Linq.dll Assembly' ının referans edilmesi gerekmektedir.</p>
</blockquote>
<p><strong>Adım 8 : Silverlight Uygulamasının Test Edilmesi</strong></p>
<p>Dilerseniz uygulamanın çalışma zamanı sonuçlarına hemen bakalım. Böylece çalışma zamanı testlerini yapmış oluruz. Örneğin <strong>A</strong> başlıklı <strong>Button </strong>kontrolüne bastığımızda, aşağıdaki ekran görüntüsündekine benzer sonuçları almış olmalıyız. Yani Title alanındakilerden A harfi ile başlayanların listesinin elde edilebiliyor olması gerekmektedir.</p>
<p><img src="/pics/2010%2f4%2fblg175_Runtime1.gif" alt="" /></p>
<p>Görüldüğü üzere <strong>ListBox </strong>içeriği baş harfi A olan albüm adları ile doldurulmuştur. Hemen bu işlemin arkasından örneğin <strong>C</strong> başlıklı <strong>Button </strong>kontrolüne basarsak aşağıdaki sonuçlar ile karşılaştığımız görürüz.</p>
<p><img src="/pics/2010%2f4%2fblg175_Runtime2.gif" alt="" /></p>
<p>Süper değil mi? <img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /></p>
<p><strong>Özet</strong></p>
<p>Tabi bu örnekte dikkat edilmesi gereken noktalardan birisi de, istemci tarafında herhangibir <strong>Proxy </strong>tipinin olmayışıdır. Bunun yerine <strong>HTTP Get</strong> metodu ile talepte bulunulmuş ve elde edilen <strong>Stream </strong>üzerindeki <strong>XML </strong>içeriği değerlendirilmiştir. Diğer yandan çok doğal olarak <strong>Servis </strong>tarafında kullanılan <strong>Entity Data Model</strong> içerisindeki tiplerin istemci tarafındaki karşılıkları <span style="text-decoration: underline;">bulunmamaktadır</span>. Eğer bu tiplerin istemci tarafında ele alınması arzu edilirse açık bir şekilde oluşturulmaları gerekecektir. Tabi böyle bir senaryoda gelen <strong>XML </strong>veya <strong>JSON </strong>tipindeki içeriğinde ilgili tiplere dönüştürülmesi gibi bir işlem söz konusu olacaktır.</p>
<p><strong>Ödev</strong> <img title="Smile" src="/editors/tiny_mce3/plugins/emotions/img/smiley-smile.gif" alt="Smile" border="0" /></p>
<ol>
<li>Servisin <strong>XML </strong>yerine <strong>JSON(JavaScript Object Notation)</strong> formatında bir çıktı vermesi halinde, <strong>Silverlight </strong>tarafında gerekli olan kod düzenlemelerini yapınız.</li>
<li>Servis üzerinden <strong>HTTP Put </strong>metod ile güncelleme işlemi yapabilmenizi sağlayacak bir geliştirmeyi aynı örnek üzerinden yapmaya çalışınız.</li>
</ol>
<p>Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><a href="https://www.buraksenyurt.com/pics/2010%2f4%2fConsumingHTTPBasedServices_RTM.rar">ConsumingHTTPBasedServices_RTM.rar (273,75 kb)</a><strong>[Örnek Visual Studio 2010 Ultimate RC Sürümü üzerinde geliştirişmiş ve RTM sürümü üzerinde test edilmiştir]</strong></p>2010-07-12T00:55:00+00:00wcf webhttp servicessilverlight 4.0silverlightwcfhttpgetpostputdeletewcf eco systembsenyurtBilindiği üzere bazı servisler HTTP protokolü üzerinden GET, POST, PUT veya DELETE metod çağrıları ile kullanılabilmektedir. Bu anlamda WCF Eco System içerisinde yer alan WebHTTP servisleri, söz konusu tipteki hizmetleri sunmak üzere WCF alt yapısı üzerine oturmuş bir model sunmaktadır. Çok doğal olarak Silverlight tabanlı istemciler de bu servislerin tüketicileri olabilirler.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=3cbb8ca2-248d-4823-b7b4-d37b2bca42040https://www.buraksenyurt.com/trackback.axd?id=3cbb8ca2-248d-4823-b7b4-d37b2bca4204https://www.buraksenyurt.com/post/Silverlight-Tarafinda-HTTP-Bazli-Servisleri-Kullanmak#commenthttps://www.buraksenyurt.com/syndication.axd?post=3cbb8ca2-248d-4823-b7b4-d37b2bca4204https://www.buraksenyurt.com/post/WCF-Web-Http-Services-ETagsWCF Web Http Services - ETags2010-04-09T05:05:00+00:00bsenyurt<p><img style="float: right;" src="/pics/2010%2f2%2fblg159_Giris.jpg" alt="" />Merhaba Arkadaşlar,</p>
<p><strong>WCF WebHttp Service</strong>' leri ile ilişkili yazılarımıza kaldığımız yerden devam ediyoruz. Bu yazımızda <strong>ETag(Entity Tag)</strong> kullanarak sunucu ile istemci arasındaki veri trafiğini nasıl azaltabileceğimizi incelemeye çalışacağız. Öncelikle istemci ile servis arasındaki iletişimi düşünerek ilerlemeye çalışalım. İstemci, sunucu üzerinde yer alan bir operasyon için talepte bulunduğunda bir cevap üretilecek ve buna bağlı bir içerik verisi istemci tarafına indirilecektir.</p>
<p>Bu süreç tipik olarak<strong> Request-Response</strong> senaryosundan farklı bir işleyiş değildir. İstemci sonraki bir zaman diliminde aynı operasyona yeni bir talepte bulunduğunda ise, üretilecek olan sunucu <strong>cevabının(Response)</strong> bir öncekine göre hiç değişmemiş olma ihtimalide bulunmaktadır. Eğer istemci tarafı bir şekilde gönderdiği talebin karşılığı olan cevabın değişmediğini anlayabilirse ve kendisinde bu içerik zaten tampon alanda duruyorsa, aynı içeriğin sunucudan istemci tarafına bir kere daha indirilmesine gerek yoktur. İşte <strong>ETag</strong> takısının devreye girdiği nokta burasıdır.</p>
<p>Peki <strong><a title="ETag Nedir?" href="http://en.wikipedia.org/wiki/HTTP_ETag">ETag(Entity Tag)</a> </strong>tam olarak nedir? <strong>Entity Tag</strong>, sunucudan istemci tarafına gönderilen <strong>Response </strong>paketlerinin <strong>Header </strong>kısmında kullanılabilen bir takıdır. Bu takı yardımıyla bilginin değişikliğe uğrayıp uğramadığı kolayca anlaşılabilir. Bu ayrım bize performans açısından bir kazanım sağlayabilir. Öyleki, sunucu aynı <strong>ETag </strong>verisine sahip iki <strong>Response </strong>ürettiğinde, aslında istemcinin talebinin karşılığının bir öncekisi ile aynı olduğu sonucuna varılabilir. Bu noktada istemcinin içeriği tampon bir bölgede tuttuğu düşünüldüğünde, bir önceki ile aynı olan veri içeriğini sunucudan indirmesine gerek kalmayacaktır. Böyle bir durumda sunucun istemci tarafına <strong>HTTP 304 Not Modified </strong>bilgisi göndermesi söz konusudur. Tabi burada <strong>ETag </strong>içerisine yazılacak verinin nasıl üretileceği de önemlidir. Genellikle <strong>Entity </strong>ile alakalı olaraktan son güncelleme zamanı veya <strong>checksum </strong>kullanılabilir. Hatta <strong>SQL </strong>veritabanında kullanılabilen <strong>Timestamp </strong>tipide <strong>ETag </strong>verisi olarak ciddi anlamda düşünülebilir. Sonuç itibariyle <strong>ETag </strong>kavramının <strong>Caching </strong>modeli için çok önemli bir kriter olduğunu söyleyebiliriz.</p>
<p>Şimdi konuyu aşağıdaki içeriğe sahip bir <strong>WebHttp Service</strong> örneği üzerinden değerlendirmeye çalışalım.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
namespace Lesson2
{
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class ProductService
{
static List<Product> products = new List<Product>
{
new Product{ ProductId="1",Name="Zedojen 500 Gb HDD", Version=Guid.NewGuid()},
new Product{ ProductId="2",Name="Zedojen 750 Gb HDD", Version=Guid.NewGuid()},
new Product{ ProductId="3",Name="Leveno Laptop XK 5301", Version=Guid.NewGuid()}
};
[WebGet(UriTemplate = "Products/{productId}")]
public Product GetProduct(string productId)
{
var product = (from p in products
where p.ProductId == productId
select p).FirstOrDefault();
// Eğer bir Product nesne örneği mevcutsa...
if (product != null)
{
// İstemciden gelen paket Header' ındaki If-None-Match değeri alınır ve sunucu tarafında bulunan Product nesne örneğinin Version özelliğinin değeri ile kıyaslanır. Eğer aynı ise bu istemci tarafına HTTP 304 Not Modified döndürüleceği anlamına gelir.
WebOperationContext.Current.IncomingRequest.CheckConditionalRetrieve(product.Version);
// Response içerisinde yer alan HTTP Header içerisindeki ETag değeri sunucu tarafından bulunan Product nesne örneğinin Version özelliği ile set edilir.
WebOperationContext.Current.OutgoingResponse.SetETag(product.Version);
}
return product;
}
}
public class Product
{
public string ProductId { get; set; }
public string Name { get; set; }
public Guid Version { get; set; }
}
}</pre>
<p>Servisimizde yer alan <strong>GetProduct </strong>isimli operasyon geriye <strong>Product </strong>tipinden bir nesne içeriği döndürmektedir. <strong>Product </strong>tipinin en önemli özelliklerinden birisi ise <strong>Guid </strong>tipinden olan <strong>Version</strong>' dur. Burada veritabanında yer alan bir ürünün <strong>ETag </strong>için kullanılabilecek veri tipi simüle edilmeye çalışılmaktadır. Yazımızın başında da belirttiğimiz gibi tablo bazlı kaynağın söz konusu olması halinde, <strong>Guid </strong>yerine <strong>Timestamp </strong>tipinden bir alan da tercih edilebilir.</p>
<p><strong>GetProduct </strong>metodu içerisinde <strong>Exception </strong>kontrolü yapılmamaktadır. Daha çok üzerinde durmak istediğimiz nokta <strong>ETag </strong>veri kontrolü ve üretimidir. Bu nedenle <strong>WebOperationContext.Current</strong> üzerinden çağırılan <strong>CheckConditionalRetrieve </strong>ve <strong>SetETag </strong>metodlarına konsantre olmamızda yarar vardır. <strong>CheckConditionalRetrieve </strong>metodu istemciden gelen talebe ait içerikteki <strong>If-None-Match </strong>değeri kontrolünü yapmaktadır. Eğer istemci aynı içeriği bir kere daha talep etmişse, bu durumda istediği <strong>Product </strong>tipinin <strong>Version </strong>değeri ile gönderdiği <strong>If-None-Match </strong>değeri aynı olmalıdır. Tabiki buradaki örnek senaryomuzda şu an için veri tarafında yer alan <strong>Version </strong>alanının değişmediğini düşünüyoruz.</p>
<p>Özellikle tarih bazlı olarak tutulan veri içeriklerinde ve örneğin son güncelleme tarihinin <strong>ETag</strong> olarak kullanıldığı durumlarda ya da herhangibir değişiklik sonrası ilgili versiyon kontrolü alanlarının değerlerinin değiştirildiği hallerde, sunucu tarafından istemciye doğru <strong>veri indirilmesi(Download)</strong> işlemi yinelenecektir. <strong>SetETag </strong>metodu ise ilk gelen talep sonrası veya içeriğin istemciye indirilmesi gerektiği talep sonrası, <strong>Response</strong>' a ait içeriğe o anki <strong>Product </strong>nesne örneğinin <strong>Version </strong>değerini atayacaktır. Her iki metodunda farklı tipte<strong> aşırı yüklenmiş(Overload)</strong> versiyonları bulunmaktadır. Bu versiyonlardan birisi de örneğimizde ele aldığımız <strong>Guid </strong>veri tipi ile çalışanıdır. Dilerseniz durumu daha iyi anlamak için hemen testlerimize başlayalım. Örneğimizi tarayıcı uygulama üzerinden talep ettiğimizde ilk etapta aşağıdaki örnek sonuçlar ile karşılaşırız.</p>
<p><img src="/pics/2010%2f2%2fblg159_Runtime.gif" alt="" /></p>
<blockquote>
<p>Burada ipv4.fiddler:1000 şeklindeki kök adres kullanımı mutlaka dikkatinizi çekmiştir. Bunu Fiddler üzerinden örneğimize ait HTTP paketlerini debug edebilmek için kullandığımızı belirtmek isterim.</p>
</blockquote>
<p>Örnekte <strong>ProductId </strong>değeri <strong>1 </strong>olan ürüne ait bilgilerin elde edildiği görülmektedir. Bundan sonra <strong>1 numaralı ürünü tekrardan talep edecek olursak Fiddler </strong>tarafından aşağıdaki <strong>HTTP </strong>hareketliliklerinin yakalandığını görebiliriz.</p>
<p><img src="/pics/2010%2f2%2fblg159_Fiddler1.gif" alt="" /></p>
<p>Dikkatinizi çeken bir şey var mı? <img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> İlk talep sonrasında sunucudan istemiye<strong> HTTP 200 Ok</strong> bilgisi dönmüş ve <strong>237 Byte</strong>' lık bir <strong>Body </strong>içeriği indirilmiştir. Diğer yandan aynı talebin ikinci kez yapılması sonrasında istemci tarafına <strong>HTTP 304 Not Modified</strong> mesajının döndürüldüğü görülmektedir. Üstelik ikinci talep sonrası <strong>Body </strong>içeriği <strong>0 byte </strong>uzunluğundadır. Volaaa!!! <img title="Laughing" src="/editors/tiny_mce3/plugins/emotions/img/smiley-laughing.gif" alt="Laughing" border="0" /> Yani ikinci talebin ilki ile aynı veri üretimine sahip olduğu anlaşılmış ve bu sebepten üretilen paketin istemci tarafına yeniden indirilmesine gerek kalınmamıştır. Örneğimize göre <strong>byte </strong>seviyesinde bu çok önemli bir performans kazanımına neden <span style="text-decoration: underline;">olmamaktadır</span>.</p>
<p>Ne varki<strong> video, resim, müzik</strong> gibi büyük boyutlu <strong>binary </strong>içeriklerin yer aldığı paketlerde <strong>304 </strong>döndürülmesinin büyük önemi vardır. Nitekim az önce üretilip istemci tarafına indirilen büyük boyutlu içeriğin, ikinci talep sonrası zaten istemci tarafındaki tamponda duran versiyonu ile aynı olması nedeniyle, yeniden gönderilmesi durumu ortadan kaldırılmakta ve böylece istemci ile sunucu arasındaki ağ trafiğinden akan veri boyutu minimize edilmektedir.</p>
<p>Şimdi <strong>Fiddler </strong>aracı yardımıyla paketlerin içeriklerine biraz daha yakından bakalım. İlk talep sonrası istemcinin gönderdiği içerik aşağıdaki gibidir.</p>
<pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false">GET http://127.0.0.1:1000/Adventures/Products/1 HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, */*
Accept-Language: tr
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; OfficeLiveConnector.1.4; OfficeLivePatch.1.3; .NET4.0C; .NET4.0E; MS-RTC LM 8)
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Host: 127.0.0.1:1000</pre>
<p>Bu talebe karşılık sunucunu cevabı ise aşağıdaki gibi olacaktır.</p>
<pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false">HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Fri, 26 Feb 2010 09:43:00 GMT
X-AspNet-Version: 4.0.30128
Content-Length: 237
ETag: "2c1cd636-3058-4985-8f10-3d3cb8c9e5fa"
Cache-Control: private
Content-Type: application/xml; charset=utf-8
Connection: Close
<Product xmlns="http://schemas.datacontract.org/2004/07/Lesson2" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Name>Zedojen 500 Gb HDD</Name><ProductId>1</ProductId><Version>2c1cd636-3058-4985-8f10-3d3cb8c9e5fa</Version></Product></pre>
<p>Dikkat edileceği üzere <strong>Response </strong>içerisinde bir <strong>ETag </strong>değeri olduğu görülmektedir. Sizce bu değerin <strong>1</strong> numaralı <strong>Product</strong>' ın güncel <strong>Version </strong>değeri ile aynı olması bir tesadüf müdür? <img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> Ayrıca içeriğin uzunluğu <strong>237 byte' </strong>tır.</p>
<p>Gelelim ikinci talebe. İstemci tarafından sunucuya gönderilen ikinci talebin içeriği aşağıdaki gibidir.</p>
<pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false">GET http://127.0.0.1:1000/Adventures/Products/1 HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, */*
Accept-Language: tr
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; OfficeLiveConnector.1.4; OfficeLivePatch.1.3; .NET4.0C; .NET4.0E; MS-RTC LM 8)
Accept-Encoding: gzip, deflate
If-None-Match: "2c1cd636-3058-4985-8f10-3d3cb8c9e5fa"
Host: 127.0.0.1:1000
Connection: Keep-Alive</pre>
<p>Dikkat edileceği üzere<strong> Guid </strong>değerine sahip olan<strong> If-None-Match</strong> isimli element bulunmaktadır. Bu değer biraz önceki talep sonucu istemciye gönderilen <strong>ETag </strong>değeridir aslında. Şimdi bu noktada sunucu üzerinde yer alan <strong>1 </strong>numaralı ürünün içeriğinin değiştirilmediği ve bu nedenle <strong>Guid </strong>değerinin de aynı olduğu düşünülmektedir. Bu sebepten sunucu tarafından istemciye gönderilen cevabın içeriği aşağıdaki gibi olacaktır.</p>
<pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false">HTTP/1.1 304 Not Modified
Server: ASP.NET Development Server/10.0.0.0
Date: Fri, 26 Feb 2010 09:43:05 GMT
X-AspNet-Version: 4.0.30128
ETag: "2c1cd636-3058-4985-8f10-3d3cb8c9e5fa"
Cache-Control: private
Connection: Close</pre>
<p>Her hangibir<strong> içerik(Content)</strong> yoktur. Hatta<strong> 0 byte</strong> uzunluğunda bir <strong>Content </strong>mevcuttur. Ama daha önemlisi yine <strong>ETag </strong>elementi vardır ve <strong>Guid </strong>değerini içermektedir. Ayrıca <strong>HTTP 304 Not Modified</strong> bilgisinin döndürüldüğü dikkatlerden kaçmamalıdır.</p>
<p>Peki sunucu tarafındaki <strong>Product </strong>içeriğinde ve dolayısıyla <strong>Version </strong>değerinde bir değişme olursa? Bir veritabanı örneği geliştirmediğimiz için bu durumu şu şekilde simüle edebiliriz<br />; <strong>1 </strong>numaralı <strong>ProductId </strong>değerine sahip ürünün adını <strong>Visual Studio 2010 </strong>ortamında değiştirip örneği tekrardan <strong>build </strong>ederek. Yeniden build işlemi sonucu <strong>static </strong>olarak tanımlanan <strong>List<Product> </strong>koleksiyon içeriğinin üretimi <span style="text-decoration: underline;">yinelenecektir</span>. Bu da yeni <strong>Guid </strong>değerlerinin üretimi anlamına gelmektedir. Bu durumda servis operasyonuna yeniden talepte bulunursak aşağıdaki cevabı aldığımız görürüz.</p>
<pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false">HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Fri, 26 Feb 2010 11:52:38 GMT
X-AspNet-Version: 4.0.30128
Content-Length: 237
ETag: "2fadf1be-d5c3-4fe0-a9c6-ecf20437ffe4"
Cache-Control: private
Content-Type: application/xml; charset=utf-8
Connection: Close
<Product xmlns="http://schemas.datacontract.org/2004/07/Lesson2" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Name>Zedojen 250 Gb HDD</Name><ProductId>1</ProductId><Version>2fadf1be-d5c3-4fe0-a9c6-ecf20437ffe4</Version></Product></pre>
<p>Dikkat edileceği üzere <strong>Guid </strong>değeri bir öncekinden farklıdır ve istemciye<strong> HTTP 200 Ok</strong> koduyla <strong>Product </strong>içeriği tekrardan gönderilmiştir. Tabi bunun sonrasında 1 numaralı ürünü yeninden talep edersek yine HTTP 304 Not Modified durumu ile karşılaşırız.</p>
<p>Buraya kadar her şey iyi gitti. Ancak testlerimizi farkettiğiniz üzere <strong>Internet Explorer </strong>gibi tarayıcı uygulamalar üzerinden gerçekleştirdik. Oysaki istemci uygulamayı biz yazıyorsa <strong>ETag </strong>kullanımı için de yapmamız gereken ekstra işlemler söz konusudur. Bu amaçla az önce geliştirdiğimiz servis uygulamasını test edeceğimiz basit bir <strong>Console Application</strong> geliştirdiğimizi düşünelim. Kod içeriğini aşağıdaki gibi yazmamız <strong>ETag </strong>desteği için yeterli olacaktır.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using Microsoft.Http;
using Microsoft.Http.Headers;
namespace Client
{
class Program
{
static void Main(string[] args)
{
Uri serviceAddress = new Uri(@"http://ipv4.fiddler:1000/Adventures/");
using (HttpClient client = new HttpClient(serviceAddress))
{
EntityTag eTag=null;
Process(client, "1",ref eTag);
Process(client, "1",ref eTag);
}
}
static void Process(HttpClient client, string ProductId,ref EntityTag ETag)
{
Console.WriteLine("***{0} için Talep***\n",ProductId);
// Talebin hazırlanması ve gönderilmesi
using (HttpRequestMessage request = new HttpRequestMessage("GET", "Products/"+ProductId))
{
// Metoda referans olarak gelen EntityTag tipinden olan ETag değerine bakılır. Eğer null değil ise ki ilk talep sonrası sunucu tarafından ürünün Version değeri ile doldurulacaktır; bu durumda Header kısmına If-None-Match değerinin eklenmesi sağlanır.
if (ETag != null)
request.Headers.IfNoneMatch.Add(ETag);
// If-None-Match değeri içeren talep gönderilir
using (HttpResponseMessage response = client.Send(request))
{
// ETag değeri gelen cevaptan alınır ve ref tipinden olan metod parametresine aktarılır. Böylece Process metoduna yapılacak olan sonraki çağrılarda aynı ETag değerinin taşınması kolaylaşmaktadır.
ETag = response.Headers.ETag;
// Sonuçlar ekran yazdırılır.
Console.WriteLine("StatusCode : {0}\n", response.StatusCode);
Console.WriteLine("Content : {0}\n",response.Content.ReadAsString());
Console.WriteLine("ETag Değeri : {0}\n", ETag.Tag);
}
}
Console.WriteLine("*******");
}
}
}</pre>
<p>Uygulamamızı çalıştırdığımızd aşağıdaki sonuçlar ile karşılaşırız.</p>
<p><img src="/pics/2010%2f2%2fblg159_Runtime2.gif" alt="" /></p>
<p>Görüldüğü gibi, ikinci talep sonrasında istemci tarafına <strong>HTTP 304 Not Modified </strong>bilgisi ve <strong>0 Byte </strong>uzunluğunda içerik gönderilmiştir. Böylece<strong> WCF WebHttp Service</strong>' leri ile ilişkili bir yazımızın daha sonuna geldik. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><a href="https://www.buraksenyurt.com/pics/2010%2f2%2fLesson9_RC.rar">Lesson9_RC.rar (175,04 kb)</a><strong> [Örnek Visual Studio 2010 Ultimate RC sürümü üzerinde geliştirilmiş ve test edilmiştir]<br /></strong></p>2010-04-09T05:05:00+00:00wcf 4.0wcfwcf webhttp servicesrestbsenyurtWCF WebHttp Service' leri ile ilişkili yazılarımıza kaldığımız yerden devam ediyoruz. Bu yazımızda ETag(Entity Tag) kullanarak sunucu ile istemci arasındaki veri trafiğini nasıl azaltabileceğimizi incelemeye çalışacağız. Öncelikle istemci ile servis arasındaki iletişimi düşünerek ilerlemeye çalışalım. İstemci, sunucu üzerinde yer alan bir operasyon için talepte bulunduğunda bir cevap üretilecek ve buna bağlı bir içerik verisi istemci tarafına indirilecektir.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=05b202f7-c32a-4359-8c3f-22e9ea1578c92https://www.buraksenyurt.com/trackback.axd?id=05b202f7-c32a-4359-8c3f-22e9ea1578c9https://www.buraksenyurt.com/post/WCF-Web-Http-Services-ETags#commenthttps://www.buraksenyurt.com/syndication.axd?post=05b202f7-c32a-4359-8c3f-22e9ea1578c9https://www.buraksenyurt.com/post/WCF-WebHttp-Services-Using-Custom-MessageWCF WebHttp Services - Özel Formatta Mesaj Döndürmek2010-04-08T07:39:00+00:00bsenyurt<p><img style="float: right;" src="/pics/2010%2f2%2fblg146_Giris.jpg" alt="" />Merhaba Arkadaşlar,</p>
<p>Bu yazımızda bizleri uzun, zorlu ve yorucu bir macera bekliyor. Şimdiden söylemek isterim ki yanınızda tatlı<em>(Mesela kolalı jelibon olabilir)</em>, tuzlu yiyecek bir şeyler, boğaz kuruluğunuzu giderecek içecekler veya daha fazla oksijen çekmenizi sağlayacak sakızlar olsun. Unutmadan birde aspirin. Baş ağrısı için <img title="Laughing" src="/editors/tiny_mce3/plugins/emotions/img/smiley-laughing.gif" alt="Laughing" border="0" /> Gelelim bu günkü konumuza.</p>
<p>Bu yazımızda, son günlerde sıklıkla üzerinde durduğumuz <strong>WCF WebHttp Service'</strong> lerinde, istemciden gelen root adres bazlı taleplerin nasıl karşılanacağını ve özel formatta mesajların nasıl döndürüleceğini incelemeye çalışıyor olacağız. Ancak işe başlamadan önce ihtiyacın ne olduğundan bahsetmemizde yarar var. Bu amaçla bir <strong>Web</strong> uygulaması üzerinden <strong>host </strong>edilen birden fazla <strong>WebHttp</strong> servisimiz olduğunu düşünerek ilerleyelim. Bu servisler içerisinde de örneğin<strong> HTTP Get</strong> taleplerinin karışılığında çeşitli tipte koleksiyonları döndüren operasyonlarımız olduğunu farz edelim. Bu durumda <strong>global.asax</strong> dosyasındaki kodlarda yönlendirme tablosuna ekleyeceğimiz adres bilgilerine göre, gelen talepleri uygun olan servislere yöndermemiz mümkün olacaktır. Bunu zaten daha önceki bir yazımızda incelemiştik.</p>
<p>Söz gelimi <strong>http://makineadı:port</strong><strong> numarası/CompanyServices/AdventureWorks/Products</strong> ile <strong>http://makineadı:port numarası/CompanyServices/Chinook/Albums</strong> gibi iki talep gönderildiğini düşünelim. Bu taleplerin aynı web uygulamasından host edilen iki farklı servis tipi tarafından değerlendirildiği bir durumda, doğru yönlendirme tekniği ile uygun olan servis ve operasyonunun çağırılması mümkündür. Oysaki istemciler <strong>http://makineadı:port/CompanyServices/</strong> adresine de talepte bulunulabilir. Böyle bir durumda ne olur? Gelin bunu açıklamak için aşağıdaki örnek servis sınıflarını içeren bir <strong>WCF REST Service Application </strong>projemiz olduğunu düşünelim.</p>
<p><strong>AdventureWorksService</strong> <strong>sınıfı;</strong></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
namespace Lesson8
{
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class AdventureWorksService
{
[WebGet(UriTemplate = "Products")]
public List<Product> GetProducts()
{
return new List<Product>
{
new Product{ Id=1, Name="Büyüteç", ListPrice=1.24M},
new Product{ Id=2, Name="Stabilo Pen 68", ListPrice=2.35M},
new Product{ Id=3, Name="Temizleme Spreyi", ListPrice=4.19M}
};
}
}
}</pre>
<p>Söz konusu sınıf içerisinde yer alan <strong>GetProducts</strong> isimli operasyon geriye <strong>Product</strong> tipinden bir ürün listesi döndürmekle görevlendirilmiştir. Tahmin edeceğiniz üzere şu anda kendi kendimizi yetiştirmeye çalıştığımızdan sadece anlamsız bir liste üretimi söz konusudur.</p>
<p><strong>ChinookService sınıfı;</strong></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
namespace Lesson8
{
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class ChinookService
{
[WebGet(UriTemplate = "Artists")]
public List<Artist> GetArtists()
{
return new List<Artist>
{
new Artist{ Id=1, Name="Ayrosimit",IsGroup=true},
new Artist{ Id=2, Name="Megadet",IsGroup=true},
new Artist{ Id=3, Name="Metalika",IsGroup=true},
new Artist{ Id=4, Name="Co Satriani",IsGroup=false}
};
}
}
}</pre>
<p><strong>ChinookService</strong> sınıfıda, <strong>AdventureWorksService</strong> tipine benzer bir şekilde ama bu kez <strong>Artist</strong> tipinden liste döndüren tek bir operasyon içermektedir. Buraya kadar zaten bir sorun bulunmamaktadır. Ancak aynı web uygulamasında birden fazla servisi host etmek istediğimizde, <strong>RouteTable</strong> nesnesinin <strong>Routes </strong>koleksiyonu içerisinde gerekli düzenlemelerin de yapılması gerekmektedir. Bu nedenle <strong>global.asax.cs</strong> içeriğinin aşağıdaki gibi olduğunu düşünebiliriz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.ServiceModel.Activation;
using System.Web;
using System.Web.Routing;
namespace Lesson8
{
public class Global
: HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
RegisterRoutes();
}
private void RegisterRoutes()
{
RouteTable.Routes.Add(new ServiceRoute("AdventureWorks", new WebServiceHostFactory(), typeof(AdventureWorksService)));
RouteTable.Routes.Add(new ServiceRoute("Chinook", new WebServiceHostFactory(), typeof(ChinookService)));
}
}
}</pre>
<p>Buna göre istemciden gelecek olan <strong>http://localhost:10843/CompanyServices/AdventureWorks/Products </strong>ve <strong>http://localhost:10843/CompanyServices/Chinook/Artists </strong>talepleri sorunsuz bir şekilde karşılanacaktır. Ancak doğrudan <strong>Web </strong>uygulamasının <strong>Root </strong>adresine yapılan <strong>http://localhost:10843/CompanyServices/</strong> gibi bir talepte aşağıdaki ekran görüntüsü ile karşılaşılacaktır.</p>
<p><img src="/pics/2010%2f2%2fblg146_Begining.gif" alt="" /></p>
<p>Elbette <strong>Help</strong> sayfalarına gidilerek servislere nasıl talepte bulunulabileceği öğrenilebilir. Ancak elimizde iki servis olduğundan söz konusu yardım sayfalarına gitmek için <strong>http://localhost:10843/CompanyServices/AdventureWorks/help </strong>veya <strong>http://localhost:10843/CompanyServices/Chinook/help </strong>gibi taleplerinin gönderilmesi gerekmektedir.</p>
<p>Sanıyorum ki nihayet ne yapmak istediğimize gelebildik. İstediğimiz şey <strong>http://localhost:10843/CompanyServices/ </strong>adresine yapılan talep ile Web uygulamasından sunulan servisleri bildirmek olacak. Üstelik bu talebe karşılık dönecek mesajın içeriğini kendimiz tasarlayacağız. Yapabilir miyiz? Evet yapabiliriz. Çünkü gerekli tüm tipler <strong>Framework</strong> içerisinde çoktandır mevcutlar. Özetle talebe uygun bir formatta<em>(XML, JSON, ATOM gibi)</em> kendi veri yayınımızı yapacağımızı ifade edebiliriz.</p>
<p>İşe ilk olarak yeni bir servis sınıfını geliştirerek başlamamız gerekiyor. Bu sınıf içerisinde yer alan operasyonumuz geriye, <strong>System.ServiceModel.Channels </strong>isim alanında yer alan <strong>Message </strong>tipinden bir değer döndürüyor olacak. İşte <strong>EntranceService</strong> isimli yeni sınıfımızın içeriği;</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Channels;
using System.ServiceModel.Syndication;
using System.ServiceModel.Web;
namespace Lesson8
{
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class EntranceService
{
// İstemci tarafına sunulacak olan kaynakların listesi Resource tipinden generic bir koleksiyonda tutulur.
public static readonly List<Resource> Resources = new List<Resource>
{
new Resource{ Name="Chinook List", Description="Chinook nesneleri listesi", RequestUri=new Uri("Chinook",UriKind.Relative),HelpUri=new Uri("Chinook/help",UriKind.Relative)},
new Resource{ Name="Adventure Works List", Description="Adventure Works nesneleri listesi", RequestUri=new Uri("AdventureWorks",UriKind.Relative),HelpUri=new Uri("AdventureWorks/help",UriKind.Relative)}
};
[WebGet(UriTemplate="")]
public Message GetResources()
{
WebOperationContext optContext = WebOperationContext.Current;
IncomingWebRequestContext incomingRequest = optContext.IncomingRequest;
string mType=string.Empty;
foreach (var acceptType in incomingRequest.GetAcceptHeaderElements())
{
// Gelen talebin Header bilgisinde yer alan MediaType değerine göre geriye Xml, Json veya Atom formatında bir içerik döndürülmesi sağlanır.
mType= acceptType.MediaType.ToLower();
if (mType == "application/xml" || mType == "text/xml")
return optContext.CreateXmlResponse(Resources); // Xml formatında dönüş
else if (mType == "application/json")
return optContext.CreateJsonResponse(Resources);
else if (mType == "application/atom+xml") // Json formatında dönüş
{
// Atom formatında dönüş için SyndicationFeed nesnesinin örneklenmesi gerekmektedir. Resource değişkeninin işaret ettiği koleksiyon bu nesne örneği içerisindeki Items koleksiyonuna atanır
return optContext.CreateAtom10Response(
new SyndicationFeed(
"Company Services Resources",
"Adventure Works & Chinook Kaynakları",
new Uri("", UriKind.Relative),
Resources.Select(r => new SyndicationItem(r.Name, r.Description, r.RequestUri)
)));
}
}
// Varsayılan olarak çıktı XML formatında verilir
return optContext.CreateXmlResponse(Resources);
}
}
// Servisten sunulan servislerin birer kaynak olduğu düşünüldüğünde bu servislere ait ad, açıklama, root Uri ve help page uri bilgilerinin saklandığı tip
public class Resource
{
public string Name { get; set; }
public string Description { get; set; }
public Uri RequestUri { get; set; }
public Uri HelpUri { get; set; }
}
}</pre>
<p><strong>GetResources</strong> isimli servis operasyonunun en önemli özelliği <strong>WebGet</strong> niteliğinde boş bir template kullanılması ve tabiki geriye <strong>Message</strong> tipinden bir değer döndürmesidir. Servis operasyonu dikkatlice incelendiğinde, istemciden gelecek olan taleplere ait <strong>Header</strong> kısımlarında yer alan mesaj formatı bilgisine göre bir çıktı üretildiği görülebilir. Buna göre standart olarak <strong>XML, JSON</strong> ve <strong>ATOM</strong> formatlarında bir üretim söz konusudur. <strong>ATOM</strong> formatındaki çıktının hazırlanması sırasında bir <strong>SyndicationFeed</strong> nesnesinin örneklendiğine dikkat edilmelidir. Tüm bu formatlama işlemlerinde o anki <strong>Web</strong> içeriği referansını taşıyan <strong>WebOperationContext</strong> tipine ait <strong>Create...</strong> metodlarından yararlanılmaktadır.</p>
<p>Tabi işimiz bu servis sınıfını yazmakla bitmiş değil. Fark ettiğiniz üzere üçüncü bir servis sınıfımız oldu ve bu sınıfa gelen talepler <strong>UriTemplate</strong> bilgisine göre <strong>Web</strong> uygulamasının <strong>Root </strong>adresine yapılmakta. Dolayısıyla boş template için gerekli yönlendirme bilgisinin<strong> global.asax.cs</strong> içerisinde bildirilmesi gerekiyor. Aynen aşağıda olduğu gibi.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.ServiceModel.Activation;
using System.Web;
using System.Web.Routing;
namespace Lesson8
{
public class Global
: HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
RegisterRoutes();
}
private void RegisterRoutes()
{
RouteTable.Routes.Add(new ServiceRoute("", new WebServiceHostFactory(), typeof(EntranceService)));
RouteTable.Routes.Add(new ServiceRoute("AdventureWorks", new WebServiceHostFactory(), typeof(AdventureWorksService)));
RouteTable.Routes.Add(new ServiceRoute("Chinook", new WebServiceHostFactory(), typeof(ChinookService)));
}
}
}</pre>
<p>Buna göre örnek bir tarayıcı uygulama üzerinden <strong>http://localhost:10843/CompanyServices/</strong> adresine yapacağımız bir talebin sonucu aşağıdaki gibi olacaktır.</p>
<p><img src="/pics/2010%2f2%2fblg146_Last.gif" alt="" /></p>
<p>Her şey yolunda görünüyor. Ancak ufak bir pürüz var. <strong>EntranceService</strong> tipine boş <strong>Uri</strong> bilgisi üzerinden bir başka deyişle <strong>Web</strong> uygulamasına ait <strong>Root</strong> adresten gidilebildiği için, <strong>http://localhost:10843/CompanyServices/help</strong> şeklinde gönderilen bir talepte aşağıdaki ekran görüntüsü ile karşılaşılacaktır.</p>
<p><img src="/pics/2010%2f2%2fblg146_HelpPage.gif" alt="" /></p>
<p>Oysaki bu <strong>Help</strong> sayfasının çıkmasına pekte gerek yoktur. Bu bir zorunluluk değildir ama olmasının da bir anlamı yoktur. Dolayısıyla pasif hale getirmemiz gerekmektedir. Bu amaçla <strong>WCF 4.0</strong> ile birlikte konfigurasyon dosyasına getirilen yeniliklerden yararlanarak gerekli sonucu elde edebiliriz. Tek yapmamız gereken<strong> Web.config</strong> dosyasını aşağıdaki gibi düzenlemek.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</modules>
</system.webServer>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<standardEndpoints>
<webHttpEndpoint>
<!-- Diğer servislerin help sayfalarının disable olmaması için ilk standartEndpoint elementine dokunulmamalıdır-->
<standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true"/>
<standardEndpoint name="EntranceServiceEndPoint"/><!-- Varsayılan olarak helpEnabled özelliği false değere sahiptir-->
</webHttpEndpoint>
</standardEndpoints>
<services>
<service name="Lesson8.EntranceService">
<endpoint contract="Lesson8.EntranceService" kind="webHttpEndpoint" endpointConfiguration="EntranceServiceEndPoint"/>
</service>
</services>
</system.serviceModel>
</configuration></pre>
<p>Zaten varsayılan <strong>web.config</strong> dosyası içeriğine göre, tüm servis talepleri otomatik olarak standart bir <strong>Endpoint </strong>tipine yönlendirilir. Ancak senaryomuza göre ana adres üzerinden yapılan yardım sayfası talebi geçersiz olmalı, diğerleri ile kullanılabilir durumda kalmalıdır. Bu nedenle <strong>EntranceService</strong> isimli hizmet için de bir <strong>Endpoint</strong> tanımlaması yapılmış ve <strong>webHttpEndpoint</strong> içerisindeki farklı bir ayara yönlendirilmiştir. Buradaki düzenlemeye göre <strong>EntranceService</strong> dışındaki tüm servislerin help sayfalarına ulaşılabilmektedir. Ancak <strong>EntranceService</strong> için help sayfası <span style="text-decoration: underline;">gösterilmemektedir</span>. Buna göre <strong>http://localhost:10843/CompanyServices/help</strong> adresine bir talepte bulunulduğunda aşağıdaki ekran görüntüsü ile karşılaşılacaktır.</p>
<p><img src="/pics/2010%2f2%2fblg146_HelpDisabled.gif" alt="" /></p>
<p>Servis tarafında pek çok işimizi hallettik. Ancak test etmemiz gereken bir husus daha var. İstemcinin, <strong>XML</strong> dışında <strong>ATOM</strong> veya <strong>JSON</strong> formatlı mesaj taleplerine karşılık olarak nasıl sonuçlar alacağı. Nitekim tarayıcı üzerinden yaptığımız taleplerde standart olarak <strong>XML</strong> çıktısı aldığımızı gördük ve biliyoruz. Peki ya diğer formatlar? Bu durumu test etmek için yine basit bir <strong>Console</strong> uygulaması geliştiriyor olacağız. Her zamanki gibi <strong>HttpClient</strong> tipinden yararlanacağız. Bu sebepten <strong>REST Starter Kit</strong> ile gelen <strong>Microsoft.Http </strong>ve <strong>Microsoft.Http.Extensions Assembly </strong>referanslarını eklemeyi unutmayalım. İşte <strong>Console</strong> uygulamamıza ait kodlarımız.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using Microsoft.Http;
namespace ClientApp
{
class Program
{
static void Main(string[] args)
{
using (HttpClient client = new HttpClient("http://localhost:10843/CompanyServices/"))
{
Execute(client,"application/json"); // Json formatında talep gönderilir
Execute(client, "application/atom+xml"); // atom formatında talep gönderilir
Execute(client, "noFormat"); // Olmayan bir format için talep gönderilir
}
}
private static void Execute(HttpClient client,string acceptFormat)
{
// HttpRequestMessage nesnesi örneklenirken kullanılan ikinci parametreye göre EntranceService tarafından karşılanacak bir talep oluşturulur
using (HttpRequestMessage requestMessage = new HttpRequestMessage("GET", String.Empty))
{
// Accept özelliğinin Add metodu yardımıyla Header' a eklenen bilgiye göre hangi formatta mesaj istendiğin belirtilir.
requestMessage.Headers.Accept.Add(acceptFormat);
using (HttpResponseMessage responseMesssage = client.Send(requestMessage))
{
Console.WriteLine("\n{0}\n", responseMesssage.Content.ReadAsString());
}
}
}
}
}</pre>
<p>Kodlarımızı çalıştırdığımızda aşağıdaki ekran görüntüsünde yer alan sonuçları elde ederiz.</p>
<p><img src="/pics/2010%2f2%2fblg146_RuntimeLast.gif" alt="" /></p>
<p>Vuuuvvvv!!! Şu anda masamdaki şekerlerin oranına bakıyorum da...Baya bir tüketmişim. <img title="Sealed" src="/editors/tiny_mce3/plugins/emotions/img/smiley-sealed.gif" alt="Sealed" border="0" /> Artık dinlenmeye çekilmenin vakti geldi sanırım. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><a href="https://www.buraksenyurt.com/pics/2010%2f2%2fLesson8_RC.rar">Lesson8_RC.rar (181,52 kb)</a> <strong>[Örnek Visual Studio 2010 </strong><strong>Ultimate </strong><strong>Beta 2 Sürümünde geliştirilmiş ancak RC sürümü üzerinde de test edilmiştir]</strong></p>2010-04-08T07:39:00+00:00wcfwcf 4.0webhttp servicesrestfulnon soapwcf webhttp servicesbsenyurtBu yazımızda, son günlerde sıklıkla üzerinde durduğumuz WCF WebHttp Service' lerinde, istemciden gelen root adres bazlı taleplerin nasıl karşılanacağını ve özel formatta mesajların nasıl döndürüleceğini incelemeye çalışıyor olacağız.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=b61f85f1-8c35-4899-942f-589119e179ac0https://www.buraksenyurt.com/trackback.axd?id=b61f85f1-8c35-4899-942f-589119e179achttps://www.buraksenyurt.com/post/WCF-WebHttp-Services-Using-Custom-Message#commenthttps://www.buraksenyurt.com/syndication.axd?post=b61f85f1-8c35-4899-942f-589119e179ac