Değerli Okurlarım Merhabalar,
Bir önceki makalemizde WCF(Windows
Communication Foundation) mimarisinin kullandığı serileştirici
tiplerden bahsetmiş ve son olarak versiyonlama(Versioning)
vakalarına değinmiştik. Bu makalemizde ise versiyonlama vakalarının
örnek uygulama üzerinden test ederek analiz etmeye çalışacağız.
Versiyonlama vakalarının merkezinde veri sözleşmesi(Data Contract)
farklılıkları yer almaktadır. Daha öncedende değinildiği üzere üç farklı
versiyonlama vakası bulunmaktadır. New Members, Missing Members,
Round-Trip. Bu bölümde ilk olarak yeni üyelerin(New Members)
oluştuğu vaka irdelenmeye çalışılacaktır. Söz konusu senaryoda, istemci
veya servis tarafının sahip olduğu veri sözleşmesinin yeni bir
versiyonunu, karşı taraf ile paylaştığı bir ortam söz konusudur. WCF
çalışma zamanı varsayılan olarak böyle bir durum ile karşılaştığında,
fazladan gelen üyenin kendisini ve içeriğini görmezden gelmektedir.
Ancak yinede fazla üyeleri içeren nesne verisi, karşı tarafa
iletilmektedir. Öncelikli olarak tüm versiyonlama örneklerinde
kullanılacak olan ve aşağıdaki sınıf şemasında(Class Diagram) görülen
tipleri içeren bir WCF servis kütüphanesi(WCF Service Libary)
geliştirildiğini düşünelim.

Servis kütüphanesinde yer alan Urun
isimli sınıf bir veri sözleşmesi(Data Contract) olacak şekilde aşağıdaki
gibi tanımlanmıştır. Bu nedenle DataContract ve DataMember nitelikleri
kullanılmaktadır.
using System;
using
System.Runtime.Serialization;
namespace AdventureLib
{
[DataContract]
public class Urun
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Ad { get; set; }
[DataMember]
public double Fiyat { get; set; }
}
} |
Servis sözleşmesi(Service Contract)
ve uygulayıcı tipe ait kod içerikleri ise aşağıdaki gibidir.
IUrunYonetim;
using System;
using
System.ServiceModel;
namespace AdventureLib
{
[ServiceContract(Name="Adventure Product Service",
Namespace="http://www.bsenyurt.com/AdventureProductService")]
public interface IUrunYonetim
{
[OperationContract]
void UrunEkle(Urun urn);
}
} |
UrunYonetim;
using System;
namespace AdventureLib
{
public class UrunYonetim
:IUrunYonetim
{
#region IUrunYonetim Members
public void
UrunEkle(Urun
urn)
{
string bilgi
= String.Format("{0} {1} {2}", urn.Id.ToString(), urn.Ad,
urn.Fiyat.ToString("C2"));
Console.WriteLine(bilgi);
}
#endregion
}
} |
UrunYonetim sınıfı içerisinde
yer alan UrunEkle metodu, parametre olarak Urun tipinden
bir nesne örneği almaktadır. Senaryoda ilgili operasyonun herhangibir
değer dönürüp döndürmemesinin bir önemi yoktur. Servis tarafındaki
uygulama ise basit bir Console projesi olarak tasarlanabilir. Bu
uygulamanın konfigurasyon içeriği ve Main metoduna ait kod bloğu
aşağıdaki gibidir.
Servis tarafı konfigurasyon içeriği;
<?xml
version="1.0" encoding="utf-8" ?>
<configuration>
<system.diagnostics>
<sources>
<source
name="System.ServiceModel.MessageLogging"
switchValue="Verbose,ActivityTracing">
<listeners>
<add type="System.Diagnostics.DefaultTraceListener"
name="Default">
<filter type="" />
</add>
<add name="ServiceModelMessageLoggingListener">
<filter type="" />
</add>
</listeners>
</source>
</sources>
<sharedListeners>
<add
initializeData="c:\vs2005projects\wcf
samples\wcfserializationandencoding\sunucu\app_messages.svclog"
type="System.Diagnostics.XmlWriterTraceListener, System,
Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089"
name="ServiceModelMessageLoggingListener"
traceOutputOptions="None">
<filter type="" />
</add>
</sharedListeners>
</system.diagnostics>
<system.serviceModel>
<diagnostics>
<messageLogging
logEntireMessage="true" logMalformedMessages="true"
logMessagesAtServiceLevel="true"
logMessagesAtTransportLevel="true" />
</diagnostics>
<behaviors>
<serviceBehaviors>
<behavior name="ProductServiceBehavior">
<serviceMetadata />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service
behaviorConfiguration="ProductServiceBehavior"
name="AdventureLib.UrunYonetim">
<endpoint address="" binding="netTcpBinding"
bindingConfiguration="" name="ProductServiceTcpEndPoint"
contract="AdventureLib.IUrunYonetim" />
<endpoint address="Mex" binding="mexTcpBinding"
bindingConfiguration="" name="ProductServiceMexTcpEndPoint"
contract="IMetadataExchange" />
<host>
<baseAddresses>
<add
baseAddress="net.tcp://localhost:4500/ProductService" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration> |
Servis tarafında, istemciden
gelen mesaj içerikleri izlenmek istendiğinden Diagnostics
özelliği açılmış ve Mesaj seviyesinde izleme yapılması için
gerekli konfigurasyon ayarları tesis edilmiştir. Böylece istemciden
servis tarafına gelen veri sözleşemesi içeriklerine detaylı bir şekilde
bakılabilir. Bunların dışında, TCP bazlı MetadaEXchange
yayınlaması için ek bir EndPoint noktasıda yer almaktadır.
Servis uygulama Main metodu kodları;
using System;
using
System.ServiceModel;
using
AdventureLib;
namespace Sunucu
{
class Program
{
static void Main(string[] args)
{
ServiceHost host =
new ServiceHost(typeof(UrunYonetim));
host.Open();
Console.WriteLine("Servis Açık. Kapatman için bir tuşa
basınız");
Console.ReadLine();
host.Close();
}
}
} |
İstemci tarafında proxy üretimi
amacıyla svcutil aracından faydalanılmaktadır. Nitekim servis
tarafında TCP üzerinden MEX(MetadataExchange) yayınlaması
yapıldığından bu mümkündür.(Üretim işlemi sırasında servis
uygulamasının çalışıyor olması gerektiğini hatırlamakta yarar vardır.
Aksi takdirde ilgili URL üzerinden Metadata bilgisi
çekilemez.) Svcutil aracının kullanımı sonrası üretilen
istemci taraflı Proxy dosyasında, Urun sınıfı aşağıdaki
şekilde yer almaktadır.

[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization",
"3.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="Urun",
Namespace="http://schemas.datacontract.org/2004/07/AdventureLib")]
public partial class Urun
:
object, System.Runtime.Serialization.IExtensibleDataObject
{
private System.Runtime.Serialization.ExtensionDataObject
extensionDataField;
private string AdField;
private double FiyatField;
private int IdField;
public
System.Runtime.Serialization.ExtensionDataObject ExtensionData
{
get{return this.extensionDataField;}
set{this.extensionDataField = value;}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public string Ad
{
get{return this.AdField;}
set{this.AdField = value;}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public double Fiyat
{
get{return this.FiyatField;}
set{this.FiyatField = value;}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public int Id
{
get{return this.IdField;}
set{this.IdField = value;}
}
} |
Dikkat edileceği üzere servis tarafında
tanımlı veri sözleşmesi içeriğinde yer alan ve DataMember
niteliği ile işaretlenmiş olan tüm özellikler burada da yer almaktadır.
Bunların yanında Urun sınıfının istemci versiyonunun
IExtensibleDataObject isimli arayüzü(Interface) uyguladığı ve
ExtensionData isimli bir özelliğe(Property) sahip olduğuda
gözden kaçırılmamalıdır. Bu arayüz Round-Trip vakalarında önem
kazanmaktadır ve ekstra bilgilerin taşınması amacıyla kullanılmaktadır.
(İlerleyen örneklerde bu durum servis tarafı için araştırılmaktadır.)
Şimdilik istemci tarafındaki Urun sınıfının aşağıdaki gibi
StokMiktari isimli yeni bir özelliğe sahip olduğu varsayılsın.
Böylece istemcinin ilgili veri sözleşmesinin yeni bir sürümüne sahip
olduğu vakası canlandırılabilir. Bu amaçla manuel olarak proxy dosyasına
müdahelede bulunulabilir.

[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization",
"3.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="Urun",
Namespace="http://schemas.datacontract.org/2004/07/AdventureLib")]
public partial class Urun
: object,
System.Runtime.Serialization.IExtensibleDataObject
{
private System.Runtime.Serialization.ExtensionDataObject
extensionDataField;
private string AdField;
private double FiyatField;
private int IdField;
private int StokMiktariField;
public System.Runtime.Serialization.ExtensionDataObject
ExtensionData
{
get{return this.extensionDataField;}
set{this.extensionDataField = value;}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public string Ad
{
get{return this.AdField;}
set{this.AdField = value;}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public double Fiyat
{
get{return this.FiyatField;}
set{this.FiyatField = value;}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public int Id
{
get{return this.IdField;}
set{this.IdField = value;}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public int StokMiktari
{
get{return this.StokMiktariField;}
set{this.StokMiktariField = value;}
}
} |
Şimdide istemci tarafındaki Console
uygulmasında aşağıdaki kodların yazıldığı göz önüne alınsın.
using System;
using
AdventureLib;
namespace Istemci
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Başlamak için bir tuşa basın");
Console.ReadLine();
AdventureProductServiceClient client = new
AdventureProductServiceClient("ProductServiceTcpEndPoint");
Urun
mouse = new Urun()
{
Id=10,
Ad="Microsoft Optical Mouse",
Fiyat=8.45,
StokMiktari=190
};
client.UrunEkle(mouse);
}
}
} |
Buna göre istemci uygulama Urun
sınıfının StokMiktari özelliğinide kullanaraktan servis tarafına
bir operasyon çağrısında bulunmaktadır. Bir başka deyişle istemci
tarafından Urun sınıfının yeni versiyonuna ait bir nesne içeriği
serileştirilerek servis tarafına gönderilmektedir. Eğer örnek, WCF
servis kütüphanesinde yer alan UrunEkle metoduna çalışma
zamanında breakpoint konularak incelenirse aşağıdaki ekran
görüntüsü ile karşılaşılır.

Dikkat edileceği üzere servis
tarafındaki Urun tipinde StokMiktari isimli bir özellik
bulunmadığından WCF çalışma zamanı, istemciden gelen paketteki
Urun verisini ters serileştirdikten(DeSerialization) sonra görmezden
gelmiş ve StokMiktari özelliğini atlamıştır. Peki gerçektende istemci
uygulama, StokMiktari özelliğini içeren bir veri paketini servis
tarafına göndermiş midir? İşte servis tarafında yapılan Diagnostics
ayarlarının faydası bu noktada kendini göstermektedir. app_messages
isimli svclog dosyası açılırsa, aşağıdaki ekran görüntüsünde yer
alan içerik ile karşılaşılır.

Dikkat edileceği üzere XML
içeriğinde StokMiktari isimli bir elementin ve 190
değerinin servis tarafına gönderildiği açık bir şekilde görülebilir.
Ancak, WCF çalışma zamanı tarafından bu element içeriği görmezden
gelinmektedir. Bu WCF çalışma zamanının, New Members
vakasındaki tipik davranışıdır.
İkinci vakaya(Missing Members)
gelindiğindeyse; bu kez taraflardan birisinde(özellikle istemci
açısından bakıldığında) ilgili veri sözleşmesinin eski versiyonunun
karşı tarafa gönderilmesi durumu söz konusudur. Bu durumu analiz etmek
için istemci tarafında yer alan Urun sınıfından Fiyat ve
son eklenen StokMiktari özelliklerinin kaldırıldığı
düşünülebilir. Bu durumda istemci tarafında Id ve Ad özellikleri olan,
servis tarafında ise Id,Ad ve Fiyat özellikleri olan birer veri sözleşme
tipi söz konusudur.

Şimdi aynı uygulama tekrardan test
edilirse çalışma zamanındaki Debug görüntüsünde, servis tarafına
ulaşmayan üye özellik(Fiyat özelliği) için varsayılan bir değerin
otomatik olarak atandığı görülür.

 |
Missing Members
vakasına göre, eksik üyelerin tiplerine göre varsayılan değer
atamaları otomatik olarak yapılmaktadır. Buna göre referans
tipli değişkenler için null(örneğin String tipi),
ilkel değer türleri içinse 0 veya 0.0
değerleri, bool alanı için false değeri atanır.
Diğer taraftan istenilirse, veri sözleşmesi içerisinde aşağıdaki
kod parçası uygulanaraktan, varsayılan değerin farklı bir
şekilde set edilmeside sağlanabilir. Tahmin edileceği üzere bu
metod ters serileştirme(DeSerializing) sırasında devreye
girmektedir.
[OnDeserializing]
void OnDeserializing(StreamingContext context)
{
Fiyat=1;
} |
İstemcinin gönderdiği mesaj içeriğine
svclog dosyası üzerinden bakıldığında ise, sadece Id ve
Ad özelliklerinin değerlerinin gönderildiği açık bir şekilde
görülmektedir.

Görüldüğü gibi WCF çalışma
zamanı servis tarafında yine olayı sessiz bir şekilde örtpas etmiştir.
Bu bir anlamda iyi olabilir. Ancak servis tarafındaki veri
sözleşmesinde(Data Contract) yer alan bazı üyeler için IsRequired
değeri true olarak belirlenirse, durum biraz daha farklılaşır.
Söz gelimi son örneğe göre, Fiyat özelliğinin servis tarafının
kullandığı WCF Service Library içerisinde aşağıdaki
gibi değiştirildiği varsayılsın.
[DataContract]
public class Urun
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Ad { get; set; }
[DataMember(IsRequired=true)]
public double Fiyat { get; set; }
} |
Bu duruma göre geliştirilen uygulama
test edildiğinde istemci tarafındaki UrunEkle metodunun icrası
sırasında istemci tarafına aşağıdaki istisnanın(Exception)
fırlatıldığı görülür.(Detaylı Exception bilgisi için servis
tarafında ServiceDebug davranışı eklenmiş ve
IncludeExceptionDetailOnFaults özelliğinin değeri true olarak
belirlenmiştir.)

Dikkat edileceği üzere Fiyat
isimli bir element değerinin beklendiği ifade edilmektedir. Dolayısıyla
IsRequired özelliğine true verilmesi halinde Missing
Members vakasında ortama bir istisna(Exception) fırlaması
söz konusudur.
 |
Bilindiği gibi Serializable
niteliğine(Attribute) sahip tiplerde
serileştirilebildikleri için WCF uygulamalarında taraflar
arasında gönderilebilmektedirler. Lakin, Serializable
niteliği uygulanmış tipler içerisindeki tüm özellikler aslında
IsRequired=true davranışını sergilerler. Ancak, özellikle
.Net Remoting ile yazılmış uygulamlardan kalan
Serializable tiplerin kullanıldığı WCF
senaryolarında, OptionalField niteliği(Attribute)
kullanılarak ilgili üyeler için IsRequired=false
davranışı tanımlanabilir.
Tabi bazı hallerde servisin veya
istemcinin kullandığı veri sözleşmesi ayrı bir library
içerisinde olabilir ve müdahele edilemeyebilir. Bu durumda
ilgili kütüphanedeki serializable tiplerin tamamına ait
özellikler/alanlar, WCF çalışma zamanı IsRequired=true
şeklinde yorumlanacaktır. Bu durumda Missing Members
vakasına göre, olası istisnalara karşı gerekli tedbirlerin
alınması gerekebilir. |
Round-Trip vakasında ise,
istemcinin servis tarafına veri sözleşmesinin yeni bir versiyonunu
gönderdiği ancak servis tarafındaki operasyon sonrasında da istemciye
veri sözleşmesinin eski halinin döndürüldüğü düşünülmektedir. Olayı daha
iyi anlamak IUrunYonetim servis sözleşmesine aşağıdaki
fonksiyonelliğin eklendiği göz önüne alınsın.
using System;
using System.ServiceModel;
namespace AdventureLib
{
[ServiceContract(Name="Adventure Product
Service",Namespace="http://www.bsenyurt.com/AdventureProductService")]
public interface IUrunYonetim
{
[OperationContract]
void UrunEkle(Urun urn);
[OperationContract]
Urun
UrunGuncelle(Urun urn);
}
} |
Operasyon dikkat edileceği üzere,
Urun tipinden bir parametre almakta ve yine Urun tipinden bir
nesne örneğini geri döndürmektedir. Senaryo gereği operasyon sırasında
istemciden gelen Urun nesnesinde değişiklik yapılmakta ve güncel hali
istemci tarafına gönderilmektedir. Lakin istemci tarafından servis
tarafına gelen Urun nesne örneğinde, servis tarafının kullandığı Urun
tasarımında olmayan StokMiktari isimli bir özellik bulunduğu
varsayılmaktadır. Buna göre doğal olarak servis tarafı StokMiktari
isimli özelliği sessizce göz ardı etmektedir. Sonrasında ise Urun
değişkeninin taşıdığı nesne üzerinde aşağıdaki örnek güncellemeyi yapıp
istemci tarafına göndermektedir.
using System;
namespace AdventureLib
{
public class UrunYonetim
:IUrunYonetim
{
#region IUrunYonetim Members
public void UrunEkle(Urun urn)
{
string bilgi
= String.Format("{0} {1} {2}", urn.Id.ToString(), urn.Ad,
urn.Fiyat.ToString("C2"));
Console.WriteLine(bilgi);
}
public Urun
UrunGuncelle(Urun urn)
{
urn.Fiyat += 10;
return urn;
}
#endregion
}
} |
Bu durumda istemci tarafında set edilen
stok miktarı değeri doğal olarak sıfırlanmış olur. Durumu canlandırmak
için istemci tarafındaki kodların aşağıdaki şekilde değiştirildiği göz
önüne alınsın.(Elbette servis sözleşmesinde bir değişiklik yapıldığı
için istemci tarafındaki proxy dosyasının svcutil aracı
veya Visual Studio ortamından Add Service
Reference seçeneği ile yeniden oluşturulması gerekebilir. Bununla
birlikte senaryonun gerçeklenmesi için istemci tarafında sıfırlanan
Urun sınıfı içerisine StokMiktari isimli özelliğin yeniden
eklenmesi de gerekmektedir.)
using System;
using AdventureLib;
namespace Istemci
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Başlamak için bir tuşa basın");
Console.ReadLine();
AdventureProductServiceClient client = new
AdventureProductServiceClient("ProductServiceTcpEndPoint");
Urun mouse = new
Urun()
{
Id=10,
Ad="Microsoft Optical Mouse",
Fiyat=8.45,
StokMiktari=190
};
Urun
donenUrun = client.UrunGuncelle(mouse);
string
urunBilgisi = String.Format("{0} {1} {2} {3}",
donenUrun.Id.ToString(), donenUrun.Ad,
donenUrun.Fiyat.ToString("C2"),
donenUrun.StokMiktari.ToString());
Console.WriteLine(urunBilgisi);
}
}
} |
Servis ve istemci tarafı
çalıştırıldığında aşağıdaki sonuçlar gözlemlenir.

Dikkat edileceği üzere istemci
tarafında 190 olarak set edilen StokMiktari değeri,
UrunGuncelle operasyon çağrısından sonra 0 olarak
bırakılmıştır. Oysaki servis tarafına ulaşan mesajlara svclog
dosyasından bakıldığında aşağıdaki ekran görüntüsünde olduğu gibi 190
değerinin aktarıldığı görülebilir.

Peki çözümsel bir yaklaşım var mıdır ve
nedir? Çözüm, IExtensibleDataObject arayüzünün(Interface) servis
tarafında kullanılan veri sözleşmesine uygulanmasıdır. Bu arayüz
sayesinde, istemciden servis tarafına gönderilen ancak servis tarafında
var olmayan özellik değerlerinin taşınması ve elde edilmesi mümkün
olabilmektedir. Bu amaçla servis tarafının kullandığı Urun
sınıfına aşağıdaki gibi IExtensibleDataObject arayüzünün
uygulanması yeterlidir.
using System;
using System.Runtime.Serialization;
namespace AdventureLib
{
[DataContract]
public class Urun
:IExtensibleDataObject
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Ad { get; set; }
[DataMember(IsRequired=true)]
public double Fiyat { get; set; }
private
ExtensionDataObject extensionData;
#region
IExtensibleDataObject Members
public
ExtensionDataObject ExtensionData
{
get{return extensionData;}
set{extensionData = value;}
}
#endregion
}
} |
IExtensibleDataObject arayüzü
sadece ExtensionData isimli bir özellik içermektedir. Bu özellik
ExtensionDataObject veri türündendir. Servis tarafındaki
ilgili operasyon çağrısı debug modda incelendiğinde aşağıdaki
verilere ulaşıldığı görülür.

Dikkat edileceği üzere,
extensionData alanının içeriğinde, istemci tarafından gönderilen
StokMiktari özelliğine ait bilgiler ve set edilen 190 değeri
yer almaktadır. Buna göre UrunGuncelle metodunun istemciye
döndürdüğü Urun nesnesinin içeriğinde StokMiktari özelliği
190 değeri ile korunmaktadır. Çalışma zamanında uygulamaların
ekran çıktısı aşağıdaki gibidir.

Round-Trip vakasında örnektende
görüldüğü üzere IExtensibleDataObject arayüzü ile bir çözüm
üretilebilmektedir ki bu Microsoft tarafından Best-Practice
olarakta belirtilmektedir.
Versiyonlama farklılıkları dışında
serileştirme ile ilişkili olaraktan dikkat edilmesi gereken farklı
konularda vardır. Söz gelimi servis tarafından yayınlanan bir veri
sözleşmesi içerisinde serileştirilemeyen(NonSerializable) ve
geliştirici tarafından doğrudan müdahalede bulunulamayan sonradan
tanımlı tipler var olabilir. Bu durumda vekil veri sözleşmeleri(Surrogate
DataContract) kullanılarak veri sözleşmesinin serileştirilmesi
yoluna gidilebilir. Bu konu bir sonraki makalede
çözümleyiciler(Encoding) ile birlikte ele alınmaya çalışılacaktır.
Böylece geldik bir makalemizin daha sonuna. Bu makalemizde kısaca
serileştirme işlemlerinde ortaya çıkabilecek olan versiyonalama vakaları
analiz edilmeye çalışmıştır. Bir
sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.
Örnek Uygulama için Tıklayın
Burak Selim ŞENYURT
selim@bsenyurt.com