Windows Communication Foundation (WCF) ile Servis Yönelimli Programlama


27 Ağustos 2012     Etiketler: C# WCF .NET

Microsoft’un servis yazılım aracı olan WCF’i incelemeye başlamadan önce, ne zaman ve neden servis yazma gereksinimi duyarız, ve servis yazmak bize ne gibi avantajlar sağlar bunları tartışalım.


Bu makale ilk olarak F5 Dergi Haziran-Temmuz 2012 sayısında yayınlanmıştır. F5 Dergi'ye ücretsiz olarak abone olabilirsiniz.


Ne zaman servis yazmalı?
Yazılımcılar olarak üzerinde çalıştığımız işlev birden çok uygulama tarafından kullanılacaksa, bu işlevi yeninden kullanılabilir ve diğer sistemlere monte edilebilir bir şekilde üretmek pek tabii ki akıl karı bir iş olacaktır. Peki bunu başarmak nasıl mümkün olabilir?


Örnek olarak bir firmanın faturalama işlemini ele alalım. Üzerinde çalıştığımız otomasyon uygulamasının bir faturalama işlevi sağlaması gerektiğini varsayalım. Bu faturalama işlevini nesne yönelimli programlama prensiplerini kullanarak bir sınıf (veya sınıflar) içinde modüler bir biçimde toplayabiliriz. Ancak bu işlevi başka bir uygulamada kullanmak için yazdığımız sınıfları diğer uygulamaya kopyalamamız gerekecektir. Aynı sınıfların farklı projelerde ayrık bir biçimde var olması demek, ileride faturalama işlevinde gerekli olabilecek değişikliklerin birden çok yerde güncellenme zorunluluğu yaratacağından, bu durum pek istenen bir durum olmayacaktır.


Faturalama işlevi sağlayan sınıfları bileşen yönelimli programlama prensiplerini kullanarak bir bileşen kütüphanesi (mesela bir DLL paketi) içinde toplayabiliriz ve nerede faturalama işlemine ihtiyaç duyarsak, geliştirdiğimiz paketi kullanabiliriz. Bu yaklaşımla yukarıda bahsettiğimiz problemi elimine etmiş olmakla birlikte, şimdi de kurulum ve entegrasyon problemi ile baş etmek durumunda kalmış oluruz. Ne zaman DLL paketinde bir değişiklik yapılırsa, yeni versiyonun kurulumu ve başarılı entegrasyonu gerekecektir. Bu da DLL cehennemi adıyla da bilinen probleme meyilli bir alternatiftir.


Faturalama işlevi sağlayan tüm sınıf ve kütüphaneleri servis yönelimli programlama prensiplerini kullanarak bir servis olarak geliştirirsek, yukarıda bahsi geçen problemleri büyük ölçüde elimine etmiş ve söz konusu işlevi diğer uygulamalardan bağımsız bir şekilde ve merkezi bir noktada toplamış oluruz. Kurulum problemi tek bir ortama (servis sunucusuna) inmiş ve entegrasyon problemleri büyük ölçüde “servis” ve “kullanıcıları” tarafından icabet edilen kontratlara indirgenmiştir. Servis olarak geliştirilen işlevler birtakım standartlara uygun olmak koşulu ile hangi dil ve platformda geliştirilirse geliştirilsin, değişik platform ve teknolojiler tarafından kullanılabilirler.


WCF ile örnek servis projemize geçmeden önce servis ve kullanıcıları arasındaki iletişim nasıl olur, çok fazla detaya girmeden bunu inceleyelim. Kullanıcı tarafından bir servisin kullanılabilmesi için kullanıcı tarafında yerel olarak var olacak vekil (proxy) yapılara ihtiyaç vardır. Vekil yapılar genellikle servis tarafından ilan edilen meta-verilerle tanımlanırlar. Kullanıcılar bu meta-verilerle tanımlandığı şekliyle vekil yapıları oluştururlar. Bu vekil yapılar sayesinde “kullanıcı kod” servis tarafında var olan asil yapıları sanki yerel yapılar gibi yerel olarak nesnelleştirir. Vekil yapılar aracı görevi görürler ve bu vekil yapılara ait metotlar çağırıldığında iletişim kanalları aracılığıyla (mesela HTTP) servis tarafında vekil yapıların asil kopyaları yaratılırlar. Asil yapılar servis tarafında işlevlerini tamamladıklarında yine aynı iletişim kanalları aracılığıyla kullanıcı tarafına geri taşınıp oradaki orijinal vekil yapıları güncellerler. WCF’in gücü ve potansiyeli bu karmaşık iletişim ve entegrasyon aktivitelerini soyutlamasından ve programcıdan saklayabilmesinden gelmektedir. Vekil yapıların servis sunucusuna intikali, servis tarafında oluşan sonuçların kullanıcı tarafına geri iadesi gibi teknik olarak karmaşık konular programcının değil WCF teknolojisinin sorumluluğundadır. WCF programcıya araca değil amaca (yani gerçek işleve) odaklanma lüksü sağlar.


Servis yönelimli programlama konusuna özet bir giriş yaptıktan sonra, Visual Studio 2010 ve .NET Framework 4.0 ile bir faturalama modülü yazalım ve bu modülü bir WCF servisi olarak diğer uygulamaların kullanıma sunalım.


Visual Studio 2010 ile WCF Service Application şablonunu seçerek F5.WCF.FaturaServisi adında yeni bir proje yaratalım ve aşağıdaki üç yapıyı ekleyelim.


Fatura.cs
public class Fatura

{
public int FaturaID { get; set; }

public string MusteriReferans { get; set; }

public double ToplamMiktar { get; set; }

public string FaturaBilgi { get; set; }

}



IFaturaServis.cs

public interface IFaturaServis

{

Fatura FaturaKaydet(Fatura fatura);

Fatura FaturaBul(int faturaID);

}


FaturaServis.cs
public class FaturaServis : IFaturaServis

{

public Fatura FaturaKaydet(Fatura fatura)

{
//normalde burada faturayı veri tabanına kaydetmeliyiz
// örneğimizde bu kısma girmiyoruz

// test edebilmek için FaturaID alanını rastgele bir sayıyla dolduralım

fatura.FaturaID = (new Random()).Next(1000);

return fatura;

}

public Fatura FaturaBul(int faturaID)

{
//yine örneğimizi basit tutmak adına
// test edebilmek için rastgele bir fatura gönderelim

return new Fatura

{

FaturaID = (new Random()).Next(1000),
MusteriReferans = "Deneme " + faturaID + " Ltd.",
ToplamMiktar = 11.22 * faturaID,
FaturaBilgi = "Test faturası " + faturaID
};
}
}


İşlevsel olarak pek bir değeri olmamakla birlikte örnek olarak kullanabileceğimiz modüler bir yapıya ulaştık. Ancak bu yapının henüz WCF ve servisler ile bir yakınlığı yok. Ulaştığımız bu yapıyı
FaturaServis sınıfı aracılığıyla dış dünyanın kullanımına sunmak istiyoruz. Bu aşamada akla şu soru gelebilir:


Neden IFaturaServis ara-yüzünü tanımladık ve FaturaServis sınıfını bu ara-yüzden kalıt alarak yazdık? Aslında FaturaServis sınıfını normal ve bağımsız bir sınıf olarak tanımlayarak da bir WCF servisi yaratmak mümkündür ancak WCF servis sınıflarını bir ara-yüz yapısından kalıt alarak yaratmak yazılım mühendisliğinin önemli prensiplerinden biri olan yapılar arasındaki bağımlılıklar somut sınıflardan ziyade soyut yapılar (abstract sınıflar veya ara-yüzler) üzerinden olmalı prensibinden dolayıdır. Bu bize özellikle servisimizin test edilebilirliği açısından büyük yarar sağlamasının yanı sıra servis kullanıcısı tarafında gerçekleşecek vekil yapıların üretimini basitleştirme adına da faydalı olacaktır.


Projemize bir servis dosyası (.svc) eklemeden önce yapmamız gereken önemli bir iş daha var. O da projemizde mevcut olan ve dış dünya tarafından bilinmesi gereken yapıların bir takım özel sınıf ve metot nitelikleri ile dekore edilmesidir. Şimdi bu özel nitelikleri tanımlayalım.


ServiceContract:
DLL: System.ServiceModel.dll
Tam kalifiye adı: System.ServiceModel.ServiceContract
Servisi tanımlamak için kullanılır. Eğer servisimizi (bu örneğimizde olduğu gibi) bir ara-yüz üzerinden yazıyorsak bu ara-yüze, veya direkt olarak normal (bağımsız) bir sınıf aracılığıyla yazıyorsak bu sınıfa uygulanır.


OperationContract:
DLL: System.ServiceModel.dll
Tam kalifiye adı: System.ServiceModel.OperationContract
Kullanıcılar tarafından kullanılacak metotları tanımlamak için kullanılır. Ara-yüz veya sınıf metotlarına uygulanır.


Örneğimizde servisimiz bir ara-yüz üzerinden yazıldığından IFaturaServis ara-yüzüne aşağıdaki eklemeleri yapıyoruz..


[System.ServiceModel.ServiceContract]
public
interface IFaturaServis

{
[System.ServiceModel.OperationContract]

Fatura FaturaKaydet(Fatura fatura);

[System.ServiceModel.OperationContract]

Fatura FaturaBul(int faturaID);

}


DataContract:
DLL: System.Runtime.Serialization.dll

Tam kalifiye adı: System.Runtime.Serialization.DataContract
Servis ve kullanıcıları arasındaki iletişimde parametre olarak gidip gelecek yapıları tanımlamak için kullanılır.


DataMember:
DLL: System.Runtime.Serialization.dll

Tam kalifiye adı: System.Runtime.Serialization.DataMember
DataContract olarak nitelendirilmiş yapılar üzerinde serileştirilecek alanları tanımlamak için kullanılır.


Örneğimizde Fatura servis ve kullanıcıları arasında gidip gelecek olan tek karmaşık yapı… Bu yüzden Fatura sınıfını ve içerdiği alanları DataContract ve DataMember nitelikleri ile dekore etmeliyiz.



[System.Runtime.Serialization.DataContract]

public class Fatura

{
[System.Runtime.Serialization.DataMember]
public int FaturaID { get; set; }

[System.Runtime.Serialization.DataMember]

public string MusteriReferans { get; set; }


[System.Runtime.Serialization.DataMember]

public double ToplamMiktar { get; set; }


[System.Runtime.Serialization.DataMember]

public string FaturaBilgi { get; set; }

}



Şimdi projemize otomatik olarak eklenen Service1.svc dosyasında birtakım değişiklikler yaparak bu dosyayı FaturaServis yapısına ev sahipliği yapacak duruma getirelim.

Önce Service1.svc.cs kod dosyasını silelim. Sonra Service1.svc dosyasını açalım ve aşağıdaki duruma getirelim.



<%
@ ServiceHost Language="C#" Service="F5.WCF.FaturaServisi.FaturaServis" %>


Service.svc dosyası açık iken projemizi çalıştıracak olursak, bize servisimizi test etme imkanı verecek olan WCF Test Client programı çalışacaktır. Bu program aracılığıyla servisimize ait FaturaKaydet ve FaturaBul metotlarını kolaylıkla test edebiliriz.


Her ne kadar çalışır durumda bir servis yaratmış olsak da, WCF’in nasıl çalıştığını, servis-kullanıcı arasındaki etkileşim ve iletişimin nasıl olduğunu gerçek anlamda kavrayabilmek için Son Nokta (Endpoint) kavramını iyi anlamamız gerekir.


WCF son noktaları WCF servislerinin dış dünyayla olan iletişim kapılarıdır. Üç öğeden oluşurlar:


Adres (address)
Son noktanın URL adresini belirler. Eğer servis bir .svc dosyası ile servis ediliyorsa son nokta adresleri .svc dosyasının adresine göreceli olarak belirtilmelidir.



Kontrat (contract)
Servis ara-yüzünün (ya da sınıfının) tam kalifiye adıyla son noktanın arkasındaki WCF servisini belirler. Örneğimizde bu değer IFaturaServis ara-yüzünün tam kalifiye adı olan F5.WCF.FaturaServisi.IFaturaServis değeridir.


Bağlantı Bilgileri (binding)
Servis ve kullanıcıları arasında oluşturulacak bağlantı detayları kümesini belirler.
Örnek: basicHttpBinding, wsHttpBinding, netTcpBinding, netNamedPipeBinding, netMsmqBinding.



Son noktalar WCF servisleri tarafından tanımlanırlar ve “servis kullanıcıları” bu tanımlar aracılığıyla kendi konfigürasyonlarını ve vekil yapıları oluştururlar. Peki örneğimizdeki WCF servisinin son noktası nerede tanımlanıyor? Şimdi bunu inceleyelim.


Son nokta tanımlamaları SVC dosyaları ile sunulan servisler için web.config dosyasında yapılırlar. Ancak örneğimizdeki web.config dosyasını açıp bakarsak göreceğiz ki herhangi bir son nokta tanımı mevcut değil. O zaman nasıl oluyor da servisimizi çalıştırdığımızda WCF Test Client programı başarılı bir şekilde servisimize bağlanabiliyor ve bize servisimizi test etme imkanı veriyor?


Microsoft WCF ekibi .NET 4 ile önceki versiyonlarında oldukça karmaşık olan ve sıklıkla problemlere sebep olan WCF Servis konfigürasyonunu basitleştirme amacıyla Varsayılan Son Noktalar (Default Endpoints) kavramını geliştirmiştir. Bu kavram sayesinde web.config dosyasında <system.serviceModel> içinde <services>...services> ve <bindings>...bindings> blokları olmasa da servisimiz için ASP.NET tarafından basicHttpBinding ayarlı bir son nokta otomatik olarak varsayılacak ve servisimiz çalışacaktır. Ne zaman basicHttpBinding değil de başka bağlantı bilgileri (binding) kullanmak istersek <services>...services> ve <bindings>...bindings> bloklarını bizim yazmamız gerekecektir.


Şimdi web.config dosyamızdaki <system.serviceModel> bloğunu aşağıdaki hale getirerek, servisimizi basicHttpBinding ile değil wsHttpBinding ile servis edelim.


<system.serviceModel>

<services>

<service name="F5.WCF.FaturaServisi.FaturaServis">

<endpoint address="" binding="wsHttpBinding" bindingConfiguration="FaturaServis" contract="F5.WCF.FaturaServisi.IFaturaServis">endpoint>
service>

services>

<bindings>

<wsHttpBinding>

<binding name="FaturaServis">

<security mode="None">security>

binding>

wsHttpBinding>

bindings>

<behaviors>

<serviceBehaviors>

<behavior>

<serviceMetadata httpGetEnabled="true"/>
<
serviceDebug includeExceptionDetailInFaults="false"/>

behavior>

serviceBehaviors>

behaviors>

<serviceHostingEnvironment multipleSiteBindingsEnabled="true"/>

system.serviceModel>



<system.serviceModel> bloğunun içine baktığımızda ilk alt blok olarak <services> alt bloğunu görüyoruz. Burada projemizde servis edilmesini istediğimiz servis ya da servisleri listeliyoruz. <service name="F5.WCF.FaturaServisi.FaturaServis"> bloğunda ise dikkat edilmesi gereken bir husus name alanında var olan değerin ilgili .svc dosyasındaki Service alanıyla aynı değerde olmasıdır.


Şimdi örneğimizdeki endpoint bloğunu incelemeye devam edelim.


<endpoint address=""

contract="F5.WCF.FaturaServisi.IFaturaServis"

binding="wsHttpBinding">

endpoint>


Yukarıdaki konfigürasyonda address değeri boş. SVC dosyaları ile servis edilen servisler için tanımlanan address değeri SVC dosyasına göreceli bir değer olmalıdır ve bu değerin boş olması tanımlanan endpoint’e SVC dosyasının fiziksel adresi üzerinden ulaşılacağı anlamına geliyor. (Bir WCF servisi SVC dosyası olmadan da, System.ServiceModel.ServiceHost aracılığıyla da servis edilebilir. Bu konuya ileriki sayılarımızda inceleyeceğiz.)

Konfigürasyonumuzda binding değerini de wsHttpBinding olarak belirttik. Bu da .NET Framework tarafından wsHttpBinding adıyla bilinen bağlantı bilgileri kümesine işaret ediyor. Opsiyonel olan bindingConfiguration ifadesi ile de bu ayarlarda yapmak istediğimiz değişiklikleri tanımladığımız <bindings> bloğunda yer alan <binding name="FaturaServis"> bloğuna işaret ediyoruz. Bu blokta
<security mode="None">security> ifadesi ile wsHttpBinding ayarlarında küçük bir değişiklik yapıyoruz.


Son olarak, <behaviors> (yani servis davranışları) konfigürasyonuna bakacak olursak burada da isimsiz bir davranış tanımı görüyoruz (<behavior name="">). İsimsiz olması tanımlanan servislere otomatik olarak uygulanacak olması anlamına geliyor. Bu tanımın içerisinde de <serviceMetadata httpGetEnabled="true" /> ifadesi var. Bu da servisimiz tarafından tanımlanan meta-verilerin internet tarayıcıları aracığıyla görüntülenmesine ve daha önemlisi kullanıcı sistemlerin vekil yapıları yaratabilmesine olanak sağlayacaktır.


Eğer konfigürasyonda <serviceMetadata httpGetEnabled="true" /> ifadesi mevcut değilse servis kullanıcıları spesifik olarak Son Nokta adresini, bağlantı bilgilerini (binding) ve, ServiceContract, OperationContract, DataContract, DataMember olarak tanımlanan yapıların detaylarını bilmek zorunda olacaklardır.


Basit bir örnek de olsa faturalama işlevi sağlayan yapılarımızı bir WCF servisi aracılığıyla dış dünyanın kullanımına açtık. Servis kullanıcıları artık servisin URL adresini bilmek kaydıyla servis tarafından sunulan işlevden faydalanabilirler.


Gelecek sayımızda WCF konusunu kullanıcı kod perspektifinden inceleyeceğiz. Bu makale boyunca geliştirdiğimiz örnek WCF servisini test etmek isteyen okulcularımız http://lab.f5dergi.com/FaturaServisi/Service1.svc adresinden servise ulaşabilir ve WCF Test Client (C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\WcfTestClient.exe) ya da örnek projelerinizden Add Service Reference şeklinde referans eklemek kaydıyla test edebilirisiniz.


ÖNEMLİ NOT: Şirket içi ağlardan bağlanarak test etmek için aşağıdaki bloğu web.config (ya da app.config) dosyasına eklemek zorunda olabilirsiniz.

<system.net>

<defaultProxy useDefaultCredentials="true">

<proxy usesystemdefault="True" />

defaultProxy>

system.net>


Kaynak kodlar:
http://f5dergi.com/indir/Kod/F5.WCF.FaturaServisi.zip
(üyelik gerektirmektedir)






Yorumlar


Merhabalar, Yazmış olduğunuz servisi indirerek kendi sunucuma attım ama timeout hatası alıyorum sizin verdiğiniz url dekini ekliyorum sorunsuz çalışıyor. Acaba sunucuda bir ayar mı yapmak gerekiyor. Failed to invoke the service. Possible causes: The service is offline or inaccessible; the client-side configuration does not match the proxy; the existing proxy is invalid. Refer to the stack trace for more detail. You can try to recover by starting a new proxy, restoring to default configuration, or refreshing the service. İstek kanalı, yanıt beklerken 00:04:59.9908130 sonra zaman aşımına uğradı. Request çağrısına geçirilen zaman aşımı değerini artırın ya da Binding üstündeki SendTimeout değerini artırın. Bu işlem için ayrılan süre daha uzun bir zaman aşımı değerinin bir bölümü olabilir.
korkmaz @ 12.11.2012 08:04:30

çok güzel makale olmuş sizi tebrik ederim ancak bide bu projenin ııs 7.5 ekleme bölümünü anlatabilir misiniz? teşekkürler
xxx @ 21.1.2013 09:37:24

Yorumlar için teşekkürler. korkmaz'ın bahsettiği timeout hatası framework versiyonundan olsa gerek. Örnek projemiz .NET Framework 4 versiyonundadır. Sunucunuzda çalışması için .NET Framework 4'un kurulu olması gerekir.
ozgur @ 21.1.2013 20:07:39

xxx tarafından gönderilen yoruma da teşekkürler. Örnek projenin IIS 7.5'de çalışması için özel bir şey yapmaya gerek olmaması lazım. Projeden 'publish' yapmak yeterli olmalı. Spesifik olarak verebileceğiniz bir hata mesajı var mıydı acaba?
ozgur @ 21.1.2013 20:10:28

Sizin yorumunuz

Email


Adınız







F5 Dergi © 2017