İlkay İlknur

just a developer...

Farklı Endpointler Üzerinden Sunulan Web Servislerin Referanslarının ve Ortak Kullanılan Tiplerinin İstemci Tarafında Tek Bir Noktadan Yönetimi

Merhaba Arkadaşlar,

Günümüzde yazılım geliştirme süreçlerine baktığımızda artık sıklıkla web servisler ile karşılaşmaktayız. Kimi zaman Silverlight gibi istemci taraflı uygulamalar geliştirirken data erişimi veya sunucu taraflı işlemler gibi nedenlerle web servislerini kullanırken kimi zamanda bir takım güvenlik veya uygulamadaki bağımlılıkları birbirinden ayırmak için iş kurallarını web servisler üzerinden dışarıya sunmaktayız.

Yukarıda bahsettiğimiz nedenlerden veya daha pek çok nedenden dolayı web servislerini uygulamalarımız içerisinde sıkça kullanmaktayız. Web servislerini kimi zaman kendimiz yazarken kimi zamanda hazır olarak yazılmış olan web servislerini kullanmaktayız.

Servis implementasyonlarında dikkat etmemiz gereken noktalardan biri de servislerden dışarıya sunulan operasyonların sayısı. Juval Löwy, WCF servislerinin tasarlanması konusunda bir servisten 20’den fazla metot sunulmaması gerektiğini tavsiye eder ve 12 operasyonun en uygun rakam olacağını belirtir. Bu rakam geliştirdiğiniz servisin durumuna göre artabilir de azalabilir de ancak önemli olan birbirleriyle ilişkili olan operasyonların aynı serviste durmasıdır.

Yukarıda okumuş olduklarımızı bir web servis geliştirirken göz önünde bulundurmamızda fayda var ;) Özellikle de yazacağımız web servislerini başka uygulamalar kullanacaksa !!! Şimdi gelelim yazımızın esas konusuna :) Web servislerini geliştirirken bahsettiğim şekilde servislermizi mantıksal olarak bölersek hem yönetimsel hem de ölçeklenebilirlik olarak daha iyi bir yapı sağlayabiliriz. Peki ya bu web servislerini kullanacak olan client(istemci) uygulamalar için durum nasıl ?

Uygulamada İçerisinde Artan Proxy Sayısı ve Aynı Tiplerin Defalarca Oluşması

Şimdi client(istemci) uygulamaların web servislerin sayısının artmasıyla beraber ne gibi sıkıntılarla karşılaşabileceğini incelemeye çalışalım. Örnek senaryonuz ise şu şekilde: İstemci uygulamamız fatura ve vergi ödemeleri için dışarıdaki bir firma tarafından sunulacak olan web servislerini kullanacak. Fatura ve vergi işlemleri için ayrı web servislerini kullanan firma fatura ve vergi ödeme fonksiyonu için aynı tipteki parametreleri beklemekte. BillPaymentService ve TaxPaymentService adını verdiğimiz servisleri ServiceContract’ları şu şekilde olabilir.

[ServiceContract] public interface IBillPayment 
{    
[OperationContract]    
bool BillPayment(Payment payment); 
}

[ServiceContract
public interface ITaxService 
{    
[OperationContract]
bool PayTaxes(Payment payment); 
} 

Servislerimizin implementasyonlarını ise basit bir şekilde aşağıdaki gibi yapabiliriz.

public class BillPaymentService : IBillPayment 
{     
#region IBillPayment Members
 public bool BillPayment(Payment payment)
 {
         return true;
 }     
#endregion 
}
public class TaxPaymentService :ITaxService  
{     
#region ITaxService Members 
public bool PayTaxes(Payment payment)     
 {       
 return true;
 }    
#endregion 
}
Servis tarafında kullanılan Payment sınıfını ise şu şekilde geliştirebiliriz.
[DataContract
public class Payment 
{
 [DataMember]    
public int BillNumber { get; set; }
 [DataMember] 
 public string NameSurname { get; set; } 
} 

Servis tarafında yapacağımız geliştirmeler şimdilik bu kadar. Şimdi gelelim bu servisi kullanacak olan istemci uygulamaya.

Hemen test etmek için bir console uygulaması(Söz konusu hızlı bir şekilde test yapmaksa console uygulaması candır :) ) yaratalım ve servislerimizin referanslarını uygulamamıza ekleyelim. Ben BillPaymentService isimli web servisinin referans adını BillService, TaxPaymentService isimli web servisin referans adını da TaxService olarak vereceğim. (Bundan sonraki kodlarda göreceğiniz namespace’lerde bu isimler bulunacaktır.)

Servis referanslarını ekledikten sonra şimdi sıra geldi yarattığımız servisleri kullanmaya. Hemen basit bir şekilde yazdığımız servisleri çağıracak kodu yazalım ve uygulamamızı test edelim.

class Program {    
static void Main(string[] args)
{
    BillService.Payment billPayment = new BillService.Payment()       
 {           
 BillNumber = 12300,           
 NameSurname = "İlkay İlknur"       
 };
BillService.BillPaymentClient billServiceClient = new BillService.BillPaymentClient();       
 bool billResult = billServiceClient.BillPayment(billPayment);     
 TaxService.Payment taxPayment = new TaxService.Payment()       
 {
           BillNumber = 78313,           
 NameSurname = "İlkay İlknur"       
 };
TaxService.TaxServiceClient taxServiceClient = new TaxService.TaxServiceClient();       
 bool taxResult = taxServiceClient.PayTaxes(taxPayment);       
 Console.WriteLine("Bill Payment Result {0}, Tax Payment Result {1}", 
 billResult, taxResult);     
} 

Uygulamamız oldukça basit olduğundan uygulamayı çalıştırınca hemen True yanıtını alıyor olacağız. Ancak bizim burada düşüneceğimiz nokta ise istemci tarafında yazdığımız kodlar ! Yukarıdaki kodu incelediğimizde her bir servisle ilgili tiplerin ayrı namespaceler içerisinde oluşturulduğunu görmekteyiz. Bu nedenle de yukarıdaki senaryoda Payment tipi her bir namespace içerisinde ayrı ayrı oluşturulduğundan dolayı her servis çağrısında kendi namespace’i içerisindeki tipi kullanmak zorundayız. Yukarıdaki kullanımın dezavantajlarından biri de yazdığımız kodun okunabilirliğinin çok az olması. Bir de servis tarafını düşündüğümüzde aslında Payment tipi ortak bir tip. Bu nedenle servis çağrımız ilgili servise gittiğinde bu ortak tipe çevrilmekte.

Bu noktada aklımıza gelen soru ise şu : Acaba biz de web servis tarafında yaptığımız gibi istemci tarafından tek bir Payment tipi yaratıp bu tipi bütün servislere parametre olarak gönderebilir miyiz ? Böylece hem kodumuz daha temiz ve anlaşılır olacaktır hem de developer olarak geliştirim sırasında farklı namespace’ler içerisindeki tiplerin yönetimiyle uğraşmıyor olacağız. Bu noktada aklımıza gelen bir yöntem olmakla beraber bir de bu yazının konusu olan başka bir yöntem de bulunmakta. Şimdi bu yöntemleri sırayla inceleyelim. İlk olarak aklımıza gelen ilk yöntem :)

Reuse Types in All Referenced Assemblies

Eğer daha önce bu seçeneği kullandıysanız aklınıza ilk gelen yöntem bu olacaktır. Kısaca bu yöntemden bahsetmemiz gerekirse bir web servisinden dönen tiplerimizi bir class library(dll) içerisinde tutuyoruz.(Anlamsal olarak farklı class libraryler içerisinde de tutabilirsiniz.Tek class library olma zorunluluğu bulunmamakta.) Daha sonra bu class libraryleri hem web servisimize hem de istemci uygulamamıza referans olarak ekliyoruz. Sonrasında ise “Add Service Reference” ekranında ilgili class libraryleri seçip bu libraryler içerisindeki tiplerin web servis çağrılarında kullanılmasını istediğimizi belirtiyoruz. Böylece Visual Studio da buna göre ilgili proxy kodlarını yaratıyor.

Yukarıdaki örneğimizde bu yöntemi uygulamamız gerekirse ilk olarak iki servisinde ortak olarak kullandığı Payment sınıfını Common ismini verdiğimiz bir class library içerisine alıyoruz.

Daha sonra ise kullandığımız 2 web servise ve istemci uygulamamıza Common ismini verdiğimiz class library referansını ekliyoruz. Sıra geldi eklediğimiz referansı güncellemeye. Hemen service referansına sağ tıkla Configure Service Reference  diyelim ve gelen ekrana bir göz atalım.

İşaretli kısma baktığımızda aslında herşey anlaşılmakta. :) Burada projeler arasında ortak olarak referans edilmiş olan libraryler içerisindeki tiplerin yeniden kullanımının sağlanması gerektiğini Visual Studio’ya bildiriyoruz. Böylece de Visual Studio ilgili bildirime göre Proxy sınıflarını oluşturuyor.

Bu kısım service referansı ekleme sırasında default olarak işaretli durumda gelmekte. Eğer siz sadece belirli libraryler içerisindeki referansların içerisindeki tiplerin yeniden kullanımını sağlamak istiyorsanız bir aşağıdaki checkbox’ı seçip istediğiniz libraryleri tek tek seçebilirsiniz.

Bu yöntemi kullandığımızda sağlayacağımız avantajlardan biri de eğer servisten dışarıya sunduğumuz tipin bir parametreli constructor’ı varsa bu constructor da doğrudan istemci tarafına gelmekte. Aslında yaptığımız doğrudan ilgili dll içerisindeki tipi yaratmak. ;)

Örneğin eklediğimiz BillService referansını güncelleyelim ve nasıl bir kullanım oluşuyor bir bakalım.

Yukarıdaki resimden de anlayacağınız gibi metodumuzun parametresi bu sefer Common namespace’i içerisindeki Payment tipini alacak şekilde değişti. Eğer TaxService için de aynısını uygularsanız aynı değişikliği görüyor olacaksınız. Böylece 2 servisimizi de Common namespace’i içerisindeki Payment tipini alacak şekilde kullanabiliriz.

Fakat bu yöntemi her zaman kullanamıyoruz. Özellikle kendi yazdığımız servis ve istemcilerde bu yöntemi kullanabilme imkanımız varken web üzerinde bulunan ve implementasyonunu bizim yapmadığımız servislerde bu yöntemi kullanmak mümkün olmuyor. Çünkü ortada yeniden kullanılabilirliği sağlayabileceğimiz bir class library bulunmamakta. Ayrıca yine bir çok farklı servis referansımız bulunmakta. Aslında hem tek bir referans içerisinden tüm servisleri yöntebilsek ve ortak tipleri de kullanabilsek ne güzel olur :) İşte şimdi inceleyeceğimiz yöntem işte tam da bunu sağlayacak :)

Reference.svcmap Dosyasını Manuel Editleme ve Namespace Mapping

Visual Studio içerisinden bir web servis referansı eklemeye çalıştığımızda karşımıza çıkan ekrandan seçimlerimizi yaptıktan sonra Visual Studio aslında arka planda svcutil.exe uygulamasını çalıştırmakta. Ancak Visual Studio sağladığı kullanıcı arayüzüyle biz developerlara hızlı bir şekilde bir web servis referansı ekleme sürecini geçirmeyi hedeflediği için aslında bizler çokta bu detaylarla ilgilenmiyoruz.

Ancak Visual Studio içerisinde kullandığımız Add Service Reference penceresi svcutil uygulamasının özelliklerini tam anlamında kullanmamızı sağlamamakta. Bunun temel nedeni de aslında Visual Studio’nun en önemli özelliği olan yazılım geliştirme sürecini kolaylaştırmak. Oldukça basit bir arayüzle hemen servis referansını yaratıp uygulamayı geliştirmeye devam etmek. Eğer svcutil’in tüm özelliklerini sunmak için bu arayüze eklentiler yapılsaydı çoğu developer için kafa karıştırıcı durumlar oluşabilirdi.

Svcutil uygulamasının işlevlerinden biri de web servisler tarafından sunulan tiplerin web servis tarafındaki namespace’lerine göre istemci taraftaki ilgili namespace’lerle eşleştirmesi. Nasıl mı ? hemen bakalım.

Not : Bu noktadan sonra yapacağımız işlemler Visual Studio’nun otomatik olarak yarattığı dosya üzerinden olacaktır. Bu dosyayı yanlış bir şekilde değiştirmeniz durumunda Visual Studio referansı güncellerken hata verebilir.

İstemci uygulamamıza bir web servis referansı ekledikten sonra arka planda Visual Studio bir takım dosyalar oluşturmakta. İstemci uygulamasını seçtikten sonra Visual Studio’dan Show All Files derseniz bu dosyaları rahatlıkla görebiliriz.

Gördüğünüz gibi aslında web servis referansımız tek bir dosyadan oluşmuyor. Wsdl,disco,xsd,svcinfo gibi uzantılara sahip pek çok dosya bulunmakta ve en altta da Reference.cs isimli proxy sınıfı bulunmakta. Burada bizim ilgileneceğimiz dosya ise Reference.cs’in hemen üzerindeki Reference.svcmap xml dosyası. Hemen bu dosyayı xml olarak açalım ve inceleyelim.

<ReferenceGroup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
ID="055d32c1-7b73-4547-91e3-815871cca568" 
xmlns="urn:schemas-microsoft-com:xml-wcfservicemap">   
<ClientOptions>     
<GenerateAsynchronousMethods>false</GenerateAsynchronousMethods>     
<EnableDataBinding>true</EnableDataBinding>     
<ExcludedTypes />     
<ImportXmlTypes>false</ImportXmlTypes>     
<GenerateInternalTypes>false</GenerateInternalTypes>     
<GenerateMessageContracts>false</GenerateMessageContracts>     
<NamespaceMappings />     
<CollectionMappings />     
<GenerateSerializableTypes>true</GenerateSerializableTypes>     
<Serializer>Auto</Serializer>     
<UseSerializerForFaults>true</UseSerializerForFaults>     
<ReferenceAllAssemblies>true</ReferenceAllAssemblies>     
<ReferencedAssemblies />     
<ReferencedDataContractTypes />     
<ServiceContractMappings />   
</ClientOptions>   
<MetadataSources>     
<MetadataSource Address="http://localhost:51919/BillPaymentService.svc" 
Protocol="http" SourceId="1" />   
</MetadataSources>   
<Metadata>     
<MetadataFile FileName="BillPaymentService.xsd" 
MetadataType="Schema" 
ID="362ccb94-7e45-4c9e-b180-d69a87e2f884" 
SourceId="1" 
SourceUrl=http://localhost:51919/BillPaymentService.svc?xsd=xsd2 />     
<MetadataFile FileName="BillPaymentService1.xsd" 
 MetadataType="Schema" 
ID="053220d3-d932-47ea-a256-009545da0853" 
SourceId="1" 
SourceUrl=http://localhost:51919/BillPaymentService.svc?xsd=xsd1 />     
<MetadataFile FileName="BillPaymentService.wsdl" 
 MetadataType="Wsdl" 
 ID="df9ef587-0317-43ed-aa36-8d5e042f3ced"
  SourceId="1"
  SourceUrl="http://localhost:51919/BillPaymentService.svc?wsdl" />     
<MetadataFile FileName="BillPaymentService2.xsd" 
 MetadataType="Schema"
  ID="7f21e958-3083-44e9-adef-0f50c89c5926"
  SourceId="1"
  SourceUrl="http://localhost:51919/BillPaymentService.svc?xsd=xsd0" />     
<MetadataFile FileName="BillPaymentService.disco" 
 MetadataType="Disco"
  ID="1d841d8c-58c9-4aa1-a73d-1a00399c5546"
  SourceId="1"
  SourceUrl="http://localhost:51919/BillPaymentService.svc?disco" />   
</Metadata>   <Extensions>     
<ExtensionFile FileName="configuration91.svcinfo" 
Name="configuration91.svcinfo" />     
<ExtensionFile FileName="configuration.svcinfo" 
Name="configuration.svcinfo" />   
</Extensions> 
</ReferenceGroup>

Dosyanın yapısına baktığımızda özetle bu xml dosyada servis referansı ile ilgili seçeneklerin tutulduğunu görmekteyiz. Örneğin MetadataSources içerisinde proxy’nin hangi endpointler için kullanıldığını görürken Metadata içerisinde ise proxy içerisinde bulunan dosyalarla ilgili bir takım tanımlamalar görüyor olacağız.

Yazımızın başında 2 amacımız olduğundan bahsetmiştik. Bunlardan biri tek bir referans içerisinde bir den fazla web servisinin referansını yönetmek. Diğeri ise web servisler arasındaki ortak tipleri bir kere yaratıp tüm servislerde bu ortak tipleri kullanmak. İlk olarak birinci maddeden başlayarak ilerleyelim. Sanırım bunu yapmak için nasıl hareket etmemiz gerektiğin bir önceki paragraftan az çok tahmin etmişsinizdir :) Svcmap dosyası içerisideki MetadataSources içerisine yeni bir MetadataSource ekleyeceğiz.

<MetadataSources>     
<MetadataSource Address="http://localhost:51919/BillPaymentService.svc" 
Protocol="http" SourceId="1" />     
<MetadataSource Address="http://localhost:51548/TaxPaymentService.svc" 
Protocol="http" SourceId="2" /> 
</MetadataSources>
 

Burada dikkat etmemiz gereken nokta SourceId değeri, bu değer her bir MetadataSource için unique(tekil) bir değer olmalıdır. Gelelim amacımızın ikinci maddesine. Svcmap dosyasının xml’ini incelediğinizde ClientOptions içerisinde NamespaceMappings isimli tagi göreceksiniz. İşte bu tag tam da bizim istediğimizi gerçekleştirmemize yarayacak olan bölüm. Bu tag içerisinde yapacağımız tanımlamalarla web servis üzerindeki belirli namespace’lerdeki tipleri uygulamamız içerisindeki tanımladığımız namespace’ler içerisinde yaratacağız. Yani kısaca “web servis üzerindeki www.tempuri.org/x namespace’inde olan tipleri git benim uygulamamdaki X namespace’inde yarat”’ın bildirimini yapacağız.

Ancak öncelikle yazımızın en başında yarattığımız web servislerinde ilgili namespace tanımlamalarını yapalım.

[ServiceContract(
 Namespace="http://www.ilkayilknur.com/PaymentService.BillPayment")] 
public interface IBillPayment 
{     
[OperationContract]     
bool BillPayment(Payment payment); 
} 
[ServiceContract(
 Namespace = "http://www.ilkayilknur.com/PaymentService.TaxPayment")] 
public interface ITaxService 
{     
[OperationContract]     
bool PayTaxes(Payment payment); 
} 
[DataContract(Namespace="http://www.ilkayilknur.com/PaymentService.Common")] 
public class Payment {     
[DataMember]     
public int BillNumber { get; set; }     
[DataMember]     
public string NameSurname { get; set; } 

NamespaceMappings içerisine tanımlayacağımız NamespaceMapping içerisinde 2 attribute bulunmakta. Bunlar ClrNamespace ve TargetNamespace. TargetNamespace içerisinde web servis üzerindeki namespace’i verirken ClrNamespace içerisinde ise TargetNamespace içerisinde bulunan tiplerin istemci uygulaması içerisinde hangi namespace içerisinde yaratılması gerektiğini belirtiyor olacağız. Bu tanımlamaları da aşağıdaki gibi gerçekleştirebiliriz.

<NamespaceMappings>       
<NamespaceMapping 
TargetNamespace="http://www.ilkayilknur.com/PaymentService.BillPayment" 
ClrNamespace="TestApp.BillPayment" />       
<NamespaceMapping 
TargetNamespace="http://www.ilkayilknur.com/PaymentService.TaxPayment" 
ClrNamespace="TestApp.TaxPayment" />       
<NamespaceMapping 
TargetNamespace="http://www.ilkayilknur.com/PaymentService.Common" 
ClrNamespace="TestApp.Payment.Common" /> 
</NamespaceMappings>

Yapacağımız tüm işlemler bu kadar. Şimdi svcmap dosyasını kaydedelim ve servis referansını güncelleyelim.

Yukarıdaki değişiklikleri BillPaymentService  web servisi referansını eklediğimiz BillService referansı içerisinde gerkçekleştirdik. Bu nedenle BillService referansını güncellediğimizde yaptığımız tanımlamalar sayesinde TaxService referansı da BillService referansı içerisine eklenmiş oldu. Artık TaxService referansını istemci uygulamamızda tutmamıza da gerek kalmadı. :) Tek servis referansı üzerinden 2 serviside çağırabilir durumdayız. Hemen istemci uygulamamıza geri dönelim ve güncellemeleri yapalım.

static void Main(string[] args) {
Payment.Common.Payment payment = new Payment.Common.Payment()
{
    BillNumber = 12300,
    NameSurname = "İlkay İlknur"
};

BillPayment.BillPaymentClient billServiceClient = 
 new BillPayment.BillPaymentClient();
bool billResult = billServiceClient.BillPayment(payment);
 payment.BillNumber = 78313;
TaxPayment.TaxServiceClient taxServiceClient = 
 new TaxPayment.TaxServiceClient();
 bool taxResult = taxServiceClient.PayTaxes(payment);
 Console.WriteLine("Bill Payment Result {0}, Tax Payment Result {1}",
  billResult, taxResult); 

Evet gördüğünüz üzere artık servis proxy sınıflarını svcmap içerisinde yaptığımız namespace tanımlamaları içerisinde bulunan sınıflar içerisinden kullandık ve Payment nesnesini Common namespace’i içerisinde  bulunan sınıftan yaratarak Tax servisi içerisinde parametre olarak göndermeden önce sadece BillNumber değerini değiştirerek aslında her ne kadar tam anlamda yeniden kullanılabilirlik olmasada ortak tipi web servislere parametre olark geçirebileceğimizi gördük.

Bu yönteme baktığımızda tüm servislerin referansını tek bir proxy üzerine toplayarak aslında proxylerin yönetimlerini biraz zorlaştırmış oluyoruz. Çok basit olarak bir serviste yapılan değişiklikten dolayı referansı güncellemek isterseniz içerisinde bulunan tüm web servislerin referansları güncelleniyor. Ancak baktığımızda tiplerin ortak kullanımını da tam anlamıyla burada sağlamış oluyoruz. Üstelik herhangi bir şekilde class library referansı olmadan. Hem de bu yöntemi kullanacağımız third-party web servislerde de kullanabilmekteyiz.  Özellikle web servislerinizin sayısı oldukça fazlaysa ve hemen hemen her servis çağrısında göndermeniz gereken ortak bir parametre varsa bu yöntemi mutlaka kullanmanızı öneriyorum. İstemci tarafı kodlarınızı yazarken işinizi oldukça kolaylaştıracaktır.

Bir başka yazımızda görüşmek üzere :)

Hoşçakalın,



Yorum Gönder


Yorumlar

  • profile

    engin

    • 13
    • 1
    • 2013

    WCF hakkında yazdıklarınız sayesinde tarafımdan minnetle anıldığınızı ifade etmek isterim. Bu makalenizdeki örneği Entity kullanımı ile nasıl yapılabileceğini merak ediyorum. Başarılarınızın devamını dilerim.