Değerli Okurlarım Merhabalar,
Servis tabanlı uygulamalarda en önemli
noktalardan biriside aradaki bilgi transferlerinin nasıl ve ne şekilde
gerçekleştirildiğidir. Gerçek şuki, bu bilgi transferinin oluşma şekli
çoğu zaman geliştiricinin gözünden kaçan yada çok fazla ilgilenmediği
bir konu olmaktadır. Nitekim çoğu servis geliştirme aracı buradaki söz konusu
içeriğin hazırlanmasını , gönderilmesini veya alınmasını otomatikleştirmektedir. Özellikle
Windows
Communication Foundation tarafında, bilginin istemci ve servis
arasındaki dolaşımında bağlayıcı tiplerin(Binding Type) seçilmesi
ile zaten arka tarafta ne şekilde bir haberleşme olacağı ve paketlerin
nasıl hazırlanacağı belirlenmiş olur. Aslında servis ve istemci
tarafında mesaj bazlı bir iletişim olduğu son derece açıktır. Farklı
platformlar üzerinde koşan servislerin haberleşmeleri yada farklı
tipteki istemci uygulamaların servisleri kullanabilmeleri gerektiğinde
ise,
aradaki haberleşmenin bir standart üzerinde ve esnek olması beklenir. Bu
nedenle özellikle SOAP bazlı web servisleri göz önüne alındığında
mesajın tipi ve içeriğide bellidir. İşte burada SOAP(Simpe Object
Access Protocol) tarzı mesajlardan söz edilebilir. Tipik olarak
SOAP mesajları bir zarf olarak temsil edilmekte(SOAP Envelope) ve
Header, Body isimli iki parçadan oluşmaktadır. Aşağıdaki şekilde bu
içerik temsil edilmeye çalışılmıştır.

Peki bu mesajların makalemize konu
olmasının sebebi nedir? Bilindiği üzere WCF mimarisinde çeşitli tipte
sözleşmeler(Contracts) söz konusudur. Örneğin servislerin ne iş
yaptığının, nasıl fonksiyonellikler sunduğunun ifade edilmesinde Sevis
Sözleşmeleri(Service Contracts) kullanılmaktadır. Benzer şekilde
istemci tarafına aktarılacak serileştirilebilir(Serializable) tipler söz
konusu ise Veri Sözleşmeleri(Data Contracts) tanımlanır. Yine
istemci tarafına aktarılacak istisna mesajlarının çeşitli durumlar için
özelleştirilmesi düşünüldüğünde Hata Sözleşmeleri(Fault Contracts)
kullanılır. Ancak bu sözleşme çeşitleri dışında birde Mesaj
Sözleşmeleri(Message Contracts) bulunmaktadır. İşte bu yazımızın
konusuda budur.
Yazımıza servis odaklı uygulamalarda
mesajların yerini konumlandırmaya çalışarak başladık. Özellikle SOAP
tabanlı bu mesajlar gerektiğinde özel olarak tasarlanabilirler. WCF
tarafında bunu gerçekleştirebilmek için Mesaj Sözleşmelerinden
yararlanılır. Mesaj sözleşmelerinin ne zaman kullanılacağına karar
verilmesi genellikle zordur. Farklı platformlar için destek verebilme imkanı(Interoperability)
ve mesaj kontrolü çoğunlukla karar vermeyi kolaylaştırmaktadır.
Gerçektende servis tarafından istemciye gönderilecek veya alınacak
mesajların farklı platformlara destek verebilecek şekilde tasarlanması
gerektiği durumlarda özel Mesaj Sözleşmeleri göz önüne
alınabilir. Diğer
taraftan Mesaj Sözleşmeleri ile taşınacak bilginin değişik parçalarının
SOAP paketinin Header veya Body kısmına ayrıştırılması ve bu sayede de,
gerekli olmayan parçaların mesaj ile birlikte taşınmaması
sağlanabilmektedir. Bu tam anlamıyla aradaki mesajlaşmanın kontrol
altına alınması anlamına gelmektedir. Hatta, istemci ve servislerin
belirli olduğu vakalarda, arada özel bir mesaj formatına göre veri
içeriğinin taşınmasıda mümkün olabilir. Diğer taraftan göz ardı
edilmemesi gereken bir noktada, mesaj seviyesinde güvenliktir. Mesaj
Sözleşmeleri kullanılırken bir tipin SOAP zarfının içerisindeki yayılımı
belirlenebildiği gibi(hangi kısımları Header' da olacak vb...)
verinin şifrelenmeside(Encryption) özelleştirilebilir. Böylece vakaya göre bir mesaj
deseninin oluşturulması ve kullanılması mümkün olabilmektedir.
 |
Çoğu durumda Mesaj Sözleşmeleri
yerine Veri Sözleşmelerininde aynı işi yapıyor olduğu görülür.
Ancak genel kanıya göre, eğer bir tip n sayıda mesaj içerisinde
kullanılacaksa(yani reusable type olarak düşünülebilirse)
Veri
Sözleşmesi olarak tanımlanması önerilmektedir. Ancak tip(type)
sadece istek/cevap(Request/Respone) modeline göre bir kereliğine
kullanılıyorsa, Mesaj Sözleşmesi olacak şekilde tanımlanır. |
Mesaj sözleşmelerinin uygulanması son
derece kolaydır. Ancak dikkat edilmesi gereken noktalar vardır.
Herşeyden önce MessageContract, MessageHeader, MessageBodyMember,
MessageHeaderArray gibi niteliklerinden(attributes)
yararlanılarak Mesaj Sözleşmesi tanımlanabilmektedir. Bununla birlikte
servis operasyonlarında Mesaj Sözleşmelerinin kullanılması
söz konusu ise metod yapısında uyulması gereken kurallar vardır. Buna
göre metod desenleri aşağıdaki örnekler olduğu gibi olmalıdır. Bu
tablodaki örnek kullanımlarda yer alan ProductOrderResponse ve ProductOrderRequest isimli tipler
örnek Mesaj
Sözleşmesi sınıflarıdır.
| Geçerli Mesaj
Sözleşme Kullanımları |
[OperationContract]
ProductOrderResponse CompleteOrderProcess(ProductOrderRequest
request); |
Operasyonun dönüş tipi ve
parametresi Mesaj Sözleşmesi tipindendir. |
[OperationContract]
ProductOrderResponse CompleteOrderProcess(); |
Operasyon parametre
almamakta ve Mesaj Sözleşmesi tipinden referans döndürmektedir. |
[OperationContract]
void CompleteOrderPrococes2(ProductOrderRequest
request); |
Operasyon Mesaj Sözleşmesi
tipinden parametre almakta ama değer döndürmemektedir. |
| Geçersiz Mesaj
Sözleşme Kullanımları |
[OperationContract]
int CompleteOrderProcess(ProductOrderRequest
request); |
Parametrenin Mesaj
Sözleşmesi olduğu durumlarda dönüş tipi olarak Mesaj Sözleşmesi
harici bir tip kullanılamaz. Exception üretir. |
[OperationContract]
void ComplteOrderProcess(ProductOrderRequest request1,
ProductOrderRequest request2); |
Birden fazla Mesaj
Sözleşmesi parametre olarak kullanılamaz. Exception üretir. |
Bu kısa teorik bilgileri devam
ettireceğiz ancak dilerseniz basit bir örnek üzerinden ilerleyerek devam
edelim. Öncelikli olarak bir WCF Sınıf Kütüphanesi projesi oluşturduğumuzu
düşünelim. Bu projemizde yer alacak olan tiplerin sınıf diygramındaki
görüntüsü aşağıdaki gibi tasarlanabilir.

Sınıf diyagramı(Class
Diagram) gözümüzü korkutmasın.
Senaryomuz aslında sadece Mesaj Sözleşmelerinin nasıl kullanılacağını
göstermeye yönelik olduğundan çok anlamlı olmayan operasyonlar
içermekte. Bu yüzden örnek olarak bir sipariş sürecine özel mesajları
tasarladığımız bir durum söz konusu. Kullanılan tiplerin içerikleri
sırasıyla aşağıdaki gibidir;
Product Sınıfı. (Veri Sözleşmesi-Data
Contract olarak
tanımlanmıştır)
using System;
using
System.Runtime.Serialization;
namespace ProductTransferLib
{
[DataContract(Namespace =
"http://Northwind/ProductTransferService/Product")]
public class Product
{
[DataMember(Order=0)]
public int ProductId { get; set; }
[DataMember(Order=1)]
public string Name { get; set; }
[DataMember(Order=2)]
public double ListPrice { get; set; }
[DataMember(Order=3)]
public DateTime OrderDate { get; set;
}
}
} |
CustomerNumber yapısı-struct.(Veri Sözleşmesi-Data
Contract
olarak tanımlanmıştır)
using
System.Runtime.Serialization;
namespace ProductTransferLib
{
[DataContract(Namespace =
"http://Northwind/ProductTransferService/CustomerNumber")]
public struct CustomerNumber
{
[DataMember]
public char Region { get; set; }
[DataMember]
public int Number { get; set; }
[DataMember]
public string LastName { get; set; }
}
} |
Receiver Sınıfı(Veri
Sözleşmesi olarak
tanımlanmıştır)
using
System.Runtime.Serialization;
namespace ProductTransferLib
{
[DataContract(Namespace="http://Northwind/ProductTransferService/Receiver")]
public class Receiver
{
[DataMember]
public int ReceiverId { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public CustomerNumber Number { get;
set; }
[DataMember]
public int RequestedProductCount {
get; set; }
}
} |
Sender Sınıfı.(Veri Sözleşmesi olarak
tanımlanmıştır)
using
System.Runtime.Serialization;
namespace ProductTransferLib
{
[DataContract(Namespace =
"http://Northwind/ProductTransferService/Sender")]
public class Sender
{
[DataMember]
public int SenderId { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public CustomerNumber SenderNumber {
get; set; }
}
} |
RequestStatus Enum sabiti.(Veri
Sözleşmesi olarak tanımlanmıştır. Enum sabiti söz konusu olduğu
için değerler DataMember yerine EnumMember isimli nitelik ile
işaretlenmiştir.)
using
System.Runtime.Serialization;
namespace ProductTransferLib
{
[DataContract(Namespace =
"http://Northwind/ProductTransferService/RequestStatus")]
public enum RequestStatus
{
[EnumMember]
Ok,
[EnumMember]
Error,
[EnumMember]
Waiting
}
} |
Buraya kadar tanımladığımız tipler
içerisinde sınıf, yapı ve enum sabiti tipleri söz konusudur. Bu tipler
birer Veri Sözleşmesi olarak tanımlanmıştır ve Mesaj
Sözleşmeleri
içerisinde ele alınmaktadır. Yazımızın konusu olan Mesaj Sözleşmelerinden iki adet tanımlanmalıdır. Bu tanımlamalardan birisi
istek(Request) diğer ise cevap(Response) içeriklerinin yapısını işaret
etmektedir. Bir başka deyişle, istemciden servise gelecek veya geriye
döndürülecek olan SOAP zarflarının içerikleri kod yardımıyla
belirlenmektedir. İstemci tarafından gelecek olan taleplere ait Mesaj
Sözleşmesi aşağıdaki kod parçasında olduğu gibi tanımlanmıştır.
using System;
using
System.ServiceModel;
namespace ProductTransferLib
{
[MessageContract]
public class ProductOrderRequest
{
#region Header Kısmına yazılacak
özellikler
[MessageHeader]
public
Guid
OrderNumber { get; set; }
[MessageHeader]
public
DateTime
OrderDate { get; set; }
[MessageHeader]
public
Product
OrderedProduct { get; set; }
#endregion
#region Body kısmına yazılacak
özellikler
[MessageBodyMember(ProtectionLevel=System.Net.Security.ProtectionLevel.None)]
// ProtectionLevel için varsayılan değre None' dur.
public
Sender
OrderSender { get; set; }
[MessageBodyMember]
public
Receiver[]
Receivers { get; set; }
#endregion
}
} |
ProductOrderRequest isimli sınıf bir
Mesaj Sözleşmesi olacak şekilde tanımlanmıştır. Bu nedenle
MessageContract niteliği ile imzalanmıştır. Bu nitelik sadece sınıf(Class)
veya yapılara(Structs) uygulanabilir. Yazımızın başında
mesajın Header ve Body kısımlarından bahsetmiştik. Header kısmında
taşınacak olan alan(Field) veya özellikleri(Property) belirtmek için
MessageHeader
niteliği kullanılmaktadır. Örnektende görüldüğü gibi, Header kısmında
Guid, DateTime gibi bilinen tipler dışında Product isimli geliştirici
tanımlı bir sınıfada yer verilmiştir. Söz konusu tipler mesaj içerisine
alınırken serileştirilmektedir. Bu nedenle Product sınıfı ve
diğer geliştirici tanımlı tipler birer Veri Sözleşmesi olarak tanımlanmıştır.
Body kısmında yer alacak özellik
veya alanlar ise MessageBodyMember niteliği ile tanımlanırlar. Yine
Body
kısmındada, Sender ve Receiver isimli geliştirici tanımlı
Veri
Sözleşmelerine yer verilmektedir. Özellikle Receiver tipinden bir
Array
kullanıldığınada dikkat edilmelidir.
 |
Header veya Body kısımlarında
Array' ler kullanılıyorsa MessageHeader ve MessageBodyMember
nitelikleri bu dizilerin elemanlarını bir elementin alt
elementleri(Child Element) olacak şekilde konumlandırır. Örneğin;
<diziAdi>
<diziElemanTipiAdi>içeriği</diziElemanTipiAdi>
<diziElemanTipiAdi>içeriği</diziElemanTipiAdi>
</diziAdi>
Ancak istenirse her bir dizi
elemanının ayrı birer boğum olarak ele alınması sağlanabilir.
Bunun için MessageHeaderArray niteliği kullanılır.
<diziAdi>içeriği</diziAdi>
<diziAdi>içeriği</diziAdi>
Yanlız bu nitelik sadece
dizilere uygulanabilir. Bir başka deyişle koleksiyonlara
uygulanamamaktadır.
Eğer SOAP içeriğinde
byte
tipinden bir diziye yer verilmişse MessageHeader veya
MessageBodyMember niteliklerinin kullanılması halinde bunlar
doğrudan Base64 tipine dönüştürülürler.
Ancak, eğer MessageHeaderArray niteliği kullanılıyorsa, ele
alınan serileştirme tipine göre(DataContractSerializer,
XmlSerializer gibi) bir aktarım gerçekleştirilir.
|
MessageHeader ve MessageBodyMember
niteliklerinde yer alan ProtectionLevel özelliği kullanılarak dijital
olarak imzalama(Sign) veya şifreleme(Encryption) sağlanabilir.
ProtectionLevel özelliği System.Net.Security.ProtectionLevel enum sabiti
tipinden bir değer alabilir. Bu değerler None, EncryptAndSign,
Sign
olabilir. Varsayılan değeri None' dur. Sign seçilirse dijital imzalama
söz konusudur. EncryptAndSign seçilirsede şifreleme ve dijital
imzalama
söz konusudur. Elbette None dışındaki değerlerin işe yaraması için
WCF çalışma ortamına yönelik olaraktan gerekli Binding ve Behavior ayarlamalarının yapılması gerekir. Aksi durumda
çalışma zamanında doğrulama işlemi sırasında bir istisnası alınır. ProtectionLevel,
Header kısmında her bir eleman için
ayrı ayrı uygulanmaktadır. Body kısmı söz konusu olduğunda ise kaç
eleman olursa olsun hepsi için aynı ProtectionLevel seviyesi söz konusudur. Buna
göre MessageBodyMember niteliği içinde seviyesi yüksek olan
ProtectionLevel değeri, diğerleri içinde uygulanır. Söz gelimi 3 farklı
MessageBodyMember için sırasıyla None, EncryptAndSign,
Sign değerleri
belirlenmişse, tüm mesaj gövdesi için EncrptyAndSign seçeneği göz önüne
alınmaktadır.
 |
SOAP ile ilişkili web servisi
standartlarında 1.1 versiyonu için Actor ve 1.2 için
Role adı
verilen bir özellik yer almaktadır. Bu özelliğin değerini WCF
tarafında ele almak için MessageHeader niteliğinin Actor
özelliği kullanılır. Bunun dışında MustUnderstand ve Relay
özelliklerindende yararlanılarak, SOAP standarlarına göre bazı
niteliklerin mesajlaşma süreçlerine kazandırılması da sağlanabilir. |
İstemciye gönderilecek cevap mesajının
içeriği ise ProductOrderResponse isimli Mesaj Sözleşmesi ile tanımlanmaktadır.
using
System.ServiceModel;
using System;
namespace ProductTransferLib
{
[MessageContract]
public class ProductOrderResponse
{
[MessageBodyMember]
public
RequestStatus
Status { get; set; }
[MessageBodyMember]
public
DateTime
ProcessDate{ get; set; }
[MessageBodyMember]
public
byte[]
OrderPicture { get; set; } // Burada byte[] tipinden bir dizi
söz konusu olduğu için SOAP body' si içerisinde Base64 tipinden
bir kodlama(encoding) söz konusu olacaktır
[MessageHeader]
public
int
OrderdProductCount { get; set; }
}
} |
ProductOrderResponse tipi Mesaj
Sözleşmesi olarak tanımlanırken amaç istemci tarafına gönderilecek olan
SOAP mesajının Header ve Body kısımlarında neler olacağına karar
verilmesidir. Dikkat edileceği üzere Body kısmında RequestStatus
enum
sabiti, DateTime ve byte[] dizisi tipinden bir içerik yer almaktadır. Diğer
taraftan Header kısmında ise örnek olarak int veri tipinden bir değer
döndürülmektedir.
 |
Bir tipin hem MessageContract
hemde DataContract olacak şekilde tanımlanması da
mümkündür. Böyle bir vakada, WCF çalışma zamanında servis
operasyonları uygulanırken, söz konusu tip için Mesaj Sözleşmesi
kriterleri göz önüne alınmaktadır. |
Artık istemci ve servis arasında
dolaşacak olan SOAP mesajlarına ait içerikler tanımlanmıştır.
Dolayısıyla bu mesajlaşma modelini kullanacak bir Servis Sözleşmesi ve
uygulayıcı sınıfı tasarlanabilir. Dikkat edileceği üzere Servis
Sözleşmesinde yer alan CompleteOrderProcess isimli operasyonun dönüş
tipi ve parametresi birer Mesaj Sözleşmesidir.
using System.ServiceModel;
namespace ProductTransferLib
{
[ServiceContract(
Name="ProductTransferService"
,Namespace="http://Northwind/ProductTransferService")]
public interface IProductTransferService
{
[OperationContract]
ProductOrderResponse
CompleteOrderProcess(ProductOrderRequest
request);
}
} |
Operasyonun uygulanışı içinse aşağıdaki
gibi bir kod örneği geliştirilebilir.
using System;
using System.IO;
namespace ProductTransferLib
{
public class ProductTransferService
:IProductTransferService
{
#region IProductTransferService Members
public
ProductOrderResponse
CompleteOrderProcess(ProductOrderRequest
request)
{
DateTime requestDate =
request.OrderDate;
Guid requestOrderNumber =
request.OrderNumber;
Sender requestSender =
request.OrderSender;
Receiver[] requestReceivers =
request.Receivers;
int orderedProductCount = 0;
foreach (Receiver receiver in
requestReceivers)
{
orderedProductCount += receiver.RequestedProductCount;
}
// Not : XP_HDD.gif resminin byte
içeriğinin dizi boyutu istemci tarafına gönderilebilecek
varsayılan dizi limini aşabilir. Bu nedenle istemci tarafındaki
konfigurasyon ayarlarında maxArrayLength değerinin bilinçli
olarak arttırılması gerekebilir.
return
new
ProductOrderResponse
{
ProcessDate=DateTime.Now,
Status=
RequestStatus.Ok,
OrderPicture=File.ReadAllBytes(System.Environment.CurrentDirectory
+ "\\XP_HDD.gif"),
OrderdProductCount=orderedProductCount
};
}
#endregion
}
} |
Burada request değişkeninden
yararlanılarak istemci tarafından gelen SOAP paketindeki mesaj içeriği
ele alınmakta ve kullanılmaktadır. Sembolik olarak paket içerisinde
gelen Receivers dizisindeki her bir Receiver nesne
örneğinin sipariş sayısının toplamı tespit edilmektedir. Ayrıca örnek byte[]
içeriği
döndürülmesi için küçük bir resim dosyasından(XP_HDD.gif) yararlanılmaktadır. İşlemin tarihi,
durumu, sipariş ile ilişkili resim ve toplam sipariş sayısı bilgileri
kullanılaraktanda bir cevap mesajı oluşturulmakta ve istemci tarafına
gönderilmektedir.
 |
SOAP mesajlarının
içerikleri aslında XML tabanlıdır. Bu içeriği yönetirken
Mesaj Sözleşmeleri, nesne tabanlı bir modeli ele
alabilmemizi sağlamaktadır. Bir başka deyişle, kod tarafında
XML yapısı ile uğraşmak yerine, nesne tabanlı bir modeli
kullanarak mesaj içeriğini kolayca oluşturabilmemiz olanaklı
hale gelmektedir ki bu geliştirme süreci için önemli bir
avantajdır. |
Bu işlemlerin tamamlanmasının ardından
servis kütüphanesini Host edecek basit bir uygulama geliştirilebilir.
Biz örneğimizde her zaman olduğu gibi, sunucu ve istemci tarafları için
birer Console uygulaması geliştiriyor olacağız. Sunucu uygulama kodları
ve konfigurasyon içeriği aşağıdaki kod parçalarında olduğu gibi tanımlanabilirler.
Sunucu uygulama kodları;
using System;
using
System.ServiceModel;
using
ProductTransferLib;
namespace ServerApp
{
class Program
{
static void Main(string[] args)
{
ServiceHost host =
new ServiceHost(typeof(ProductTransferService));
host.Open();
Console.WriteLine("Servis dinlemede.\nKapatmak için bir tuşa
basınız.");
Console.ReadLine();
host.Close();
}
}
} |
Sunucu tarafı konfigurasyon içeriği;
<?xml
version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="ProductTransferServiceBehavior">
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceMetadata />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service
behaviorConfiguration="ProductTransferServiceBehavior"
name="ProductTransferLib.ProductTransferService">
<endpoint address="" binding="basicHttpBinding"
bindingConfiguration=""
name="ProductTransferServiceHttpEndPoint"
contract="ProductTransferLib.IProductTransferService" />
<endpoint address="Mex" binding="mexHttpBinding"
bindingConfiguration=""
name="ProductTransferServiceMexEndPoint"
contract="IMetadataExchange" />
<host>
<baseAddresses>
<add
baseAddress="http://buraksenyurt:1000/ProductTransferService" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration> |
Sunucu uygulama basit olarak HTTP
tabanlı bir sunum yapmakta ve BasicHttpBinding bağlayıcı tipini
ele almaktadır. Bununla birlikte istemci tarafının, servise ait
Metadata bilgisini çekebilmesi için IMetadataExchange
arayüzünü kullanan bir MexHttpBinding EndPoint' ide
kullanılmaktadır.
İstemci uygulamamızı kullanırken yine
Add Service Reference seçeneği ile aynı solution içerisinde yer alan
örnek servise ait referans üretimini gerçekleştirebiliriz. İstemci
tarafına ait konfigurasyon içeriği aşağıdaki gibidir(Bu içerik Add
Service Reference seçeneğinin kullanılması sonucunda otomatik olarak
üretilmektedir.)
<?xml
version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="ProductTransferServiceHttpEndPoint"
closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false"
hostNameComparisonMode="StrongWildcard" maxBufferSize="65536"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8"
transferMode="Buffered" useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192"
maxArrayLength="163840" maxBytesPerRead="4096"
maxNameTableCharCount="16384"/>
<security mode="None">
<transport clientCredentialType="None"
proxyCredentialType="None" realm="" />
<message clientCredentialType="UserName"
algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint
address="http://buraksenyurt:1000/ProductTransferService"
binding="basicHttpBinding"
bindingConfiguration="ProductTransferServiceHttpEndPoint"
contract="ProductTransferServiceReference.ProductTransferService"
name="ProductTransferServiceHttpEndPoint" />
</client>
</system.serviceModel>
</configuration> |
İstemci
tarafındaki örnek kod içeriği ise aşağıdaki gibidir.
using System;
using System.IO;
using ClientApp.ProductTransferServiceReference;
namespace ClientApp
{
class Program
{
static void Main(string[] args)
{
ProductTransferServiceClient client = new
ProductTransferServiceClient("ProductTransferServiceHttpEndPoint");
Sender sndr =
new Sender
{
Name="Burak Selim",
SenderId=10001,
SenderNumber=new CustomerNumber{ Number=1, Region='A',
LastName="SENYURT"}
};
Receiver[]
receivers = {
new Receiver{ Name="Bil", Number=new CustomerNumber{
LastName="Geyts", Region='B', Number=1}, ReceiverId=10002,
RequestedProductCount=100},
new Receiver{ Name="Deyv", Number=new CustomerNumber{
LastName="Masteyn", Region='C', Number=2}, ReceiverId=10003,
RequestedProductCount=150},
new Receiver{ Name="Co", Number=new CustomerNumber{
LastName="Satriyani", Region='C', Number=3}, ReceiverId=10055,
RequestedProductCount=75}
};
RequestStatus
requestStatus;
DateTime
processDate;
byte[]
orderPicture;
Console.WriteLine("Sipariş için bir tuşa basınız.");
Console.ReadLine();
int
result=client.CompleteOrderProcess(
DateTime.Now,
Guid.NewGuid(),
new
Product{ ProductId=1, Name="Her Yönüyle WCF", ListPrice=10,
OrderDate=DateTime.Now},
sndr,
receivers,
out
orderPicture,
out
processDate,
out
requestStatus);
Console.WriteLine("result {0}",result.ToString());
File.WriteAllBytes(System.Environment.CurrentDirectory +
"\\ResponsePicture.gif", orderPicture);
Console.WriteLine("İşlemler tamamlandı. Çıkmak için bir tuşa
basınız.");
Console.ReadLine();
}
}
} |
İstemci uygulamada servise ait proxy
nesnesi örneklendikten sonra CompleteOrderProcess metodunun ihtiyacı
olan parametreler hazırlanmaktadır. CompleteOrderProcess metodu aslında
ProcessOrderResponse Mesaj Sözleşmesi tipinden bir parametre almaktadır.
Ne varki istemci tarafında metodun uygulanış şekli biraz farklıdır.
Herşeyden önce, servise gönderilecek SOAP paketi içerisinde yer alacak
Header ve Body elementlerinin her biri, istemci tarafında ayrı birer
metod parametresi şekline ele alınmaktadır. Metodun çağırılması sonucu istemciye
dönecek olan SOAP mesajındaki Header kısmında yer alan int değer aslında
istemci tarafında, CompleteOrderProcess' in dönüş değeridir. Yine istemciye döndürülen ve
Body kısmında yer alan orderPicture,processDate ve
requestStatus
değişkenleri ise, CompleteOrderProcess metodunun out tipinden
parametreleri olarak ele alınmaktadır. orderPicture değişkeni bir
byte[]
dizisi olarak mesaj içeriğinden toparlanmakta ve fiziki olarak istemci
tarafındaki bir dosyaya yazdırılmaktadır. Bu tahmin edileceği üzere servis
tarafından gönderilen resimdir. Projemizde hem sunucu hemde istemci
uygulamamızı çalıştırdığımızda aşağıdaki ekran görüntüsünde yer alan
sonuçları elde ederiz.

Aslında burada şaşırtıcı bir sonuç
yoktur. Nesne tabanlı olacak şekilde istemci ve servis arasındaki tipler
kolay bir şekilde kullanılmıştır. Ancak bizim için önemli olan arka
planda hareket eden SOAP mesajlarının içeriklerinin ne hale geldiğidir.
Bu amaçla Fiddler isimli HTTP Debugging aracından yararlanırsak, örneğin çalıştırılması
sonrasında ağ trafiğinde, aşağıdaki ekran görüntüsünde yer alan mesajlaşmanın
oluştuğunu görürüz.

Dikkat edileceği üzere, Mesaj
Sözleşmelerinde Header ve Body kısımlarında hangi bilgilerin yer
almasını istiyorsak buna göre bir ağaç yapısı oluşmuştur. Request
kısmına ait olan SOAP zarfının XML içeriği tam olarak aşağıdaki gibidir.
<s:Envelope
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<h:OrderDate
xmlns:h="http://Northwind/ProductTransferService">2009-02-08T01:53:03+02:00</h:OrderDate>
<h:OrderNumber
xmlns:h="http://Northwind/ProductTransferService">9476df3d-0f74-4643-9fcf-b7344d5da37d</h:OrderNumber>
<h:OrderedProduct
xmlns:h="http://Northwind/ProductTransferService"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<ProductId
xmlns="http://Northwind/ProductTransferService/Product">1</ProductId>
<Name
xmlns="http://Northwind/ProductTransferService/Product">Her
Yönüyle WCF</Name>
<ListPrice
xmlns="http://Northwind/ProductTransferService/Product">10</ListPrice>
<OrderDate
xmlns="http://Northwind/ProductTransferService/Product">2009-02-08T01:53:03+02:00</OrderDate>
</h:OrderedProduct>
</s:Header>
<s:Body>
<ProductOrderRequest
xmlns="http://Northwind/ProductTransferService">
<OrderSender
xmlns:a="http://Northwind/ProductTransferService/Sender"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:Name>Burak Selim</a:Name>
<a:SenderId>10001</a:SenderId>
<a:SenderNumber
xmlns:b="http://Northwind/ProductTransferService/CustomerNumber">
<b:LastName>SENYURT</b:LastName>
<b:Number>1</b:Number>
<b:Region>65</b:Region>
</a:SenderNumber>
</OrderSender>
<Receivers
xmlns:a="http://Northwind/ProductTransferService/Receiver"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:Receiver>
<a:Name>Bil</a:Name>
<a:Number
xmlns:b="http://Northwind/ProductTransferService/CustomerNumber">
<b:LastName>Geyts</b:LastName>
<b:Number>1</b:Number>
<b:Region>66</b:Region>
</a:Number>
<a:ReceiverId>10002</a:ReceiverId>
<a:RequestedProductCount>100</a:RequestedProductCount>
</a:Receiver>
<a:Receiver>
<a:Name>Deyv</a:Name>
<a:Number
xmlns:b="http://Northwind/ProductTransferService/CustomerNumber">
<b:LastName>Masteyn</b:LastName>
<b:Number>2</b:Number>
<b:Region>67</b:Region>
</a:Number>
<a:ReceiverId>10003</a:ReceiverId>
<a:RequestedProductCount>150</a:RequestedProductCount>
</a:Receiver>
<a:Receiver>
<a:Name>Co</a:Name>
<a:Number
xmlns:b="http://Northwind/ProductTransferService/CustomerNumber">
<b:LastName>Satriyani</b:LastName>
<b:Number>3</b:Number>
<b:Region>67</b:Region>
</a:Number>
<a:ReceiverId>10055</a:ReceiverId>
<a:RequestedProductCount>75</a:RequestedProductCount>
</a:Receiver>
</Receivers>
</ProductOrderRequest>
</s:Body>
</s:Envelope> |
Bu XML içeriği incelendiğinde tam
olarak Mesaj Sözleşmesinde belirttiğimiz kriterlere uyulduğu
görülmektedir. Söz gelimi Header kısmında OrderDate, OrderNumber ve
OrderProduct elementleri yer almaktayken, Body kısmında
ProductOrderRequest elementi tarafından sarmalanmış olan, OrderSender ve
Receivers elementleri bulunmaktadır. Receivers aslında Receiver[]
dizisinin kullanılması nedeni ile kendi içerisinde birden fazla
Receiver
alt elementi içermektedir. Burada geliştirici tanımlı tiplerin(Product,Receiver,Sender
gibi) Veri Sözleşmesi olarak tanımlanmaları nedeniyle XML elemetleri
içerisine aktarılmış olmalarıda gözden kaçırılmamalıdır. Yine istemciye
dönen mesajın(Response) tam içeriğine bakıldığında aşağıdakine
benzer bir SOAP çıktısı ile karşılaşılmaktadır.
<s:Envelope
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<h:OrderdProductCount
xmlns:h="http://Northwind/ProductTransferService">325</h:OrderdProductCount>
</s:Header>
<s:Body>
<ProductOrderResponse
xmlns="http://Northwind/ProductTransferService">
<OrderPicture>R0lGODlhIAAgAPcAAAAAAB8hNR4hODMpHCQgLiIkNyYqRysvUC4wQy
0wTi0xUzU3SzE1VjE1WzU5Xjs
+VDw/WDU6Yzk+ZTk/bDg5dTFILDl0FzpGRz5BXD5CYz1CbD5EdVYzLXI/SF9OMF1
WP05tK0JoN0pxNVF5JVR1M2lfPn
FHKn5GP3VpL31vN31wL3d0OEZIVkZUSUFFY0FGbURIZEZKaklLYExPaEJIek1SaEx
Td1VWalNXd1prWFp1Rl96bWRcS
2JNYGtrRnhuRGdlaGNkdmh5eTc3mDxDxT5P2Dxdxj5d3T9g1z9r40VMg0pRiVZbhFR
bl1hkilljl0Nxr0h2t1tlqFVzqGFmjW
ZpgWtthGlti2Npl25xiGd6l25xkG1ym3ByhnN0inl2h3N2kXF2m3R4lnR5mXp8lWNqq2p
zp2xzsXN5q3l+pXp/qnR7tEFW0E
Jt0EN85l+fGkmLJ0mSIVOKJFWJNVeXJFqpH1y2GFqiJGOWKWK3Hm2oIV6KVFnFGGn
NF3POGnrSGWHJIHbNInvQIHiDiH
qAonyCuH6Qr0qK3kOF7EeP8EqU8Vua6lWb8miW1XGYzWWk8XSq73et8H+x8YM2N
5oxJ7MsG4pfPIN4K5J7LpR0OKFV
KaB2HbB+FYNtQpp8eKRNR7JyRtAqE+UsEfg+D/Q2ENRNE9dyH8F0NuxHD/pJDfxWDO
1+HP1lDv12EbeDC7OaFKmUIqm
XNruDK72nEKyMfoHSHYTRJMeKCMiRCN+GH9WWBc2xDNqjA8GnIv2GE/uaF/CGIf2W
IOCrBOS1BfqlHPq0HfysIfu3I+vG
A/fXBPrcFvzeKfzeOP3gOIKDj4yDlZCCkYaKrYGHs4CGu4SJtYWLvYqPtIiNuo6TvJWXp
pGVu5+ivoiOwIySwpGWxJSZxJ
ecyJmdxJmeyJ2hw52iy4i28JK88aGlxqGmzKSpzqmsyaar0Kmt0a6xzK2x07a4zbG11bS4
17a52Lq907m92ZzC8b7B3K3J
7aXH8MTG3MjL4c7R5dHT5gAAAAAAAAAAACH5BAEAAP8ALAAAAAAgACAAAAj/AP8JH
EiwoMGDCNvNsCcvHjx37tixW
5cuHTpy5MSB++atW7UM2BDaszduGxkyXrp0AWMFTBYrYqpY+8bxmxpu0xDSs5eGTIIX
DjRIcCBBgoYAWxCE4ditG45
q1Q7mo0ePEJkFCAoM8GCCA4ECBWAsCAOuo1OPB/nJgzdP3z5auY4Ra6UK1ad3+aaKa1r
NBreoBvM5hLf21K9jv+iOCu
VlkLhv1TxWe1ponsF58DK707bKWDFfrFKRopRDzpwdY8II2QMHS2WD9B5C9BRMri9Vojvo
oGOnT58/f/pYoFLonMF0E
CeCClZsLm5RN0bw/g2cTwUsa74ZRAdP4jhTxIr9/2KlalSlG3Tq8KHeh88DJ2uobU+QThw0YLI
yWdJUitSk0nX4VosgfcT
BhAsNoGHQQ9es8wwLNzjhhA01nNADCXTk0YcftgQCSAhmwGeGQe6MQ006UjhyTzmPJGLEEl
yAcIeGgXTIBwtrPLHGG
QXls04Y6URziDn4lMMIIkcM8cUNLcjxhh54iHABE2tg8QSPBOmDDhbsmPFIPfVEwogbR1CQyS2Y
8LAACx9ckoIZa5SBA
5YDtUMOF+JAAQk+5jyiSBJEBMEJL8Mko0wzyiRTghlnYNFEGe0Q5A453VATRTlFPoLkEEDowgsyh
jKTDDIxnHHGE2WU
oQ5BF4lDyCL14P8jphtsUNCJLsIkY+gyybzChKlnpGoNQdpoNEUjhmjxBBRtEPHCJroUmswyia5wJTep
SiHfQN885gQLz4
BBhRMwUHDDLZ/q2oyuLEgjTqplSFEGQeBsRA013dCTDzloUBGEK8KAqgyvu1hRDbxSSPEEQZA11c01
3HTjDj/6qNPFD
7DM0kssKDzwaLwJN9HEQO9QY8A13fxVzTTTFFJIN95EcwUONdCwBKogS6GEAiMPhFxEE1Fk0UXhFB0
OTdqEow022i
zN9IgDMbCBEgUYoEADEUQwAQ00NKCAAgd8DfbUSpQ9NQ4EZUCDElZnPcEGG3DttQNifz01EzgoYfMVB
ME9sLYBAhj
QgAZxr+2113XH7cMKeivhBUHZxBADBA9U/gAGGLjgAgYZYO555pprHgMNCJVu+umop6766gcFBAA7
</OrderPicture>
<ProcessDate>2009-02-08T01:53:03.46875+02:00</ProcessDate>
<Status>Ok</Status>
</ProductOrderResponse>
</s:Body>
</s:Envelope> |
Gözden kaçmayacak olan nokta
OrderPicture elementinin içeriğidir :) Tahmin edileceği üzere bu
elementin içeriği, servis tarafındaki resmimizin byte[] dizisi haline
geldikten sonra, SOAP mesajı içeriğine Base64 kodlamasına göre
serileştirilmiş halidir. Bu içerik, istemci tarafında ters serileştirilme
işleminden sonra yine byte[] dizisi olacak şekilde ele
alınabilmektedir.
Bunların haricinde Header kısmında OrderProductCount elementinin,
Body
kısmında ise ProductOrderResponse elementi ile sarmalanmış olan
OrderPicture, ProcessDate ve Status alt elementlerinin olduğu
görülmektedir.
Mesaj Sözleşmelerinde ele alınan bir
diğer durumda türlendirilmemiş versiyonların kullanılmasıdır(Untyped
Message Contracts). Burada System.ServiceModel.Channels isim alanında
yer alan Message sınıfı ele alınmaktadır. SOAP 1.1 ve
SOAP 1.2 uyumlu
mesajları işaret edebilen bu sınıf yardımıyla, istemciden gelen talepler
ele alınabilir ve cevaplar oluşturularak Message tipinden
örnekler
üzerinden karşı tarafa gönderilebilir. Son olarak bu durumu değerlendirip makalemizi
tamamlayalım. Bu amaçla Servis Sözleşmemize aşağıdaki ekran
görüntüsünde yer alan yeni bir operasyon ilave
ettiğimizi düşünelim.

RunProcess isimli operasyon parametre ve dönüş
değeri olarak Message tipini kullanmaktadır. Söz konusu operasyon
metodunun ProductTransferService içerisindeki uyarlaması ise aşağıdaki
gibi yapılabilir.
// Untyped
Message alıp veren örnek servis operasyonu metodu.
public
Message
RunProcess(Message
request)
{
// Servise gelen Untyped Message ' ın Body kısmında yer alan
Product dizi içeriğini elde etmek için GetBody metodunun generic
versiyonundan yararlanılır.
Product[] products =
request.GetBody<Product[]>();
// İstemciye döndürelecek Untyped Message' ın Body kısmında
yer alacak örnek Product içeriği için dizi oluşturulur.
Product[] resultSet=new Product[products.Length];
// Gelen mesajın Body kısmından elde edilen dizi üzerinde
örnek işlemler yapılır.
// Örnekte ListPrice bilgisi 1 birim arttırılmıştır.
for (int i = 0; i < products.Length; i++)
{
products[i].ListPrice += 1;
resultSet[i] = products[i];
}
// Operasyondan döndürelecek olan Untyped Message
oluşturulur.
// İlk parametre SOAP versiyonunu belirtir. Örneğin "SOAP
1.1".
// İkinci parametre servis operasyonunda ReplyAction
özelliğine atanan değerdir.
// Üçüncü parametre ise Body kısmında yer alacak olan nesne
örneğidir.
Message response = Message.CreateMessage(request.Version,
"ReplyAction",resultSet);
// Untyped Message geriye döndürülür.
return
response;
} |
RunProcess isimli servis
operasyonu, istemciden gelen mesajın Body kısmında yer alan
Product nesne verilerini ele almakta ve örnek olarak ListPrice
değerlerini 1 birim arttırarak geriye döndürmektedir. Metoda gelen
türlendirilmemiş mesajın gövdesindeki veri içeriğini ele alabilmek için
GetBody<T> metodundan yararlanılır. Tahmin edileceği üzere
metodun kullandığı generic tip üzerinden bir XML ters
serileştirme işlemi söz konusudur. Nitekim istemciden gelen mesaj XML
tipindedir ve kod içerisinde nesnel olarak kullanılması gerekmektedir.
Bunlara ek olaraktan, servisin istemciye göndereceği türlendirilmemiş
mesajın üretimi için, Message sınıfının static
CreateMessage fonksiyonundan yararlanılır. Metodun aşırı
yüklenmiş(overload) 11 farklı versiyonu bulunmaktadır. Örneğimizde
kullandığımız halinde, ilk parametre ile SOAP versiyonu, ikinci
parametre ile SOAP Action adı ve son olarak üçüncü
parametre ilede Body kısmına gelecek olan nesne örneği
belirtilmiştir. Eklenen bu yeni fonksiyonellik nedeniyle istemci
tarafında yer alan servis referansınında güncellenmesi gerekmektedir. Bu
güncelleme işleminin ardından RunProcess isimli operasyon istemci
tarafında örnek olarak aşağıdaki kod parçasında görüldüğü gibi
kullanılabilir.
Console.WriteLine("\nUntyped Message\n");
// Güncel kanal implementasyonundan yararlanarak
OperationContextScope nesnesi örneklenir.
// OperationContextScope nesnesinden yararlanarak gelen ve giden
mesajların içerikleri yönetilebilir, Header, Body gibi
kısımlarına müdahale edilebilir.
using
(new OperationContextScope(client.InnerChannel))
{
// Untyped mesaj içerisinde gönderilecek olan Product nesneleri
için bir dizi hazırlanır.
Product[] products =
{
new Product{
Name="Programming WCF", ListPrice=12, OrderDate=DateTime.Now,
ProductId=19},
new Product{
Name="Programming C# 3.0", ListPrice=16, OrderDate=DateTime.Now,
ProductId=21}
};
// İstemciden servise gönderilecek olan Untyped Message
hazırlanır.
// İlk parametre mesaj versiyonudur. (SOAP 1.1 gibi).
// İkinci parametre servis sözleşmesinde RunProcess
operasyonunda belirtilen Action özelliğinin değeridir.
// Üçüncü parametre ise mesaj içeriğinde gönderilecek olan
serileştirilebilir nesne örneğidir. Bu örnekte Product tipinden
bir dizi kullanılmaktadır.
Message request = Message.CreateMessage(OperationContext.Current.OutgoingMessageHeaders.MessageVersion,
"RequestAction", products);
// Operasyon çağrısı yapılır ve parametre olarak hazırlanan
Untyped Message örneği gönderilir.
// Çağrı sonucu yine bir Untyped Message örneğidir.
Message reply = client.RunProcess(request);
// Servisten gelen Untyped Message içerisindeki Body kısmında
tutulan Product topluluğunu dizi olarak ele almak için GetBody
metodunun generic versiyonu kullanılır.
Bunun sonucu olarak elde edilen sonuç Product tipinden bir dizi
olacaktır.
Product[] response = reply.GetBody<Product[]>();
foreach (Product product in response)
{
Console.WriteLine(product.Name+"
"+product.ListPrice);
}
} |
İstemci tarafında RunProcess
metodu çağırılmadan önce gönderilecek mesajın oluşturulması için yine
CreateMessage static metodundan yararlanılmaktadır. Yine ilk
parametre olarak SOAP versiyonu, ikinci parametre olarak SOAP
Action değeri ve üçüncü parametre olarakta Body kısmına
serileştirilecek nesne örneği belirtilmiştir. Servis tarafından gelen
mesaja ait Body bilgisinin okunması içinde GetBody<T> metodundan
yararlanılmaktadır. İstemci tarafında dikkat edilmesi gereken
noktalardan biriside tüm bu işlemleri içerisine alan Using bloğunda
OperationContextScope nesnesinden yararlanılması ve o anki
kanal(Channel) bilgisinin kullanılmasıdır. Örnek uygulamamız bu haliyle test
edildiğinde çalışma zamanı görüntüsü aşağıdakine benzer olacaktır.

Ancak elbetteki arka planda yer alan
mesaj içeriğine Fiddler aracı yardımıyla bakıldığında Body kısmında
hareket eden Product verilerinin içeriği açık bir şekilde görülebilmektedir.

Dikkat edileceği üzere Request
mesajında gönderilen Product nesnelerine ait ListPrice
değerleri, Response mesajı içerisinde 1 birim
arttırılmıştır. Eğer mesajların RAW içeriklerine bakılırsa SOAP
Action
bilgisininde set edilmiş olduğu görülebilir. (Size tavsiyem
GetBody metodlarına olan çağrılarda BreakPoint kullanarak
request ve response değişkenlerinin çalışma zamanı
içeriklerini QuickWatch ile izlemenizdir.)

 |
Eğer istemciden talep
gönderildikten sonra varsayılan olarak 1 dakikalık zaman
dilimi içerisinde servis tarafından cevap gelmezse aşağıdaki
ekran görüntüsünde yer alan TimeoutException istisnası
ile karşılaşılır.

Bu sorun SendTimeout
değeri arttırılarak çözümlenebilir. Bu sorun, uzun süren
operasyonların söz konusu olduğu durumda dikkate alınması
gereken istisnaların başında gelmektedir. |
Buraya kadar yaptıklarımıza
baktığımızda, istemci ve sunucu arasındaki Mesaj içeriklerinin
yönetiminin Mesaj Sözleşmeleri yardımıyla ele alınabildiği sonucu
ortaya çıkmaktadır. Buna göre istenirse, istemci ve sunucu arasında
taşınacak bir veri tipinin belirli parçalarının SOAP zarfı içerisindek
Header veya Body bölümleri arasında ayrıştırılması mümkün
olabilmektedir. Hatta, istemci ve servis arasında özel mesaj
desenlerinin oluşturulması da söz konusu ve olasıdır. Elbette bu işi
tamamlayıcı en önemli nokta şifreleme(Encryption) işlemlerininde hesaba
katılmasıdır. Buda yapıldığı takdirde mesajın daha güvenilir bir şekilde
ele alınması ve korunması mümkün hale gelmektedir. Diğer taraftan
istemci ve servis arasındaki mesajların türlendirilmemiş olmaları
halindede ele alınabildikleri ve içeriklerinin yönetilebildikleride
ortadadır. Mesaj Sözleşmeleri
ile ilişkili olarak daha detayı bilgi almak için,
MSDN' de yayınlanan içeriği takip etmenizi öneririm. Böylece geldik bir
makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek
hepinize mutlu günler dilerim
Örneği
İndirmek İçin Tıklayın
Burak Selim ŞENYURT
MVP (Connected System Developer-2008,C# 2007,2006)