Burak Selim Senyurt(MVP)
Matematik Mühendisi bir .NET Severin Yazıları...

WCF 4.0 Yenilikleri - DataContractResolver ile Dinamik Tip Çözümleme(Dynamic Type Resolution) [Beta 1]

Pazar, 27 Eylül 2009 01:30 by bsenyurt

Merhaba Arkadaşlar,

Hatırlayacağınız üzere bir önceki yazımızda, WCF serileştirme işlemlerinde Known Types sorunsalını değerlendirmeye çalışmıştık. Bu sorunsalın giderilmesinde ele alınan tekniklerden biriside KnownType niteliğinin(Attribute) kullanılmasıyıdı. Ama istersek servise uygulanacak ServiceKnownType niteliği ve başka diğer teknikleri de değerlendirebileceğimizden bahsetmiştik. Ne varki tüm bu teknikler static bir model sunmaktadır. WCF 4.0 ile birlikte, tip çözümlemelerinin(Type Resolution) dinamik olarak ele alınmasını sağlayan DataContractResovler isimli abstract bir sınıfın geldiği görülmektedir. Bu sınıf System.Runtime.Serialization.dll assembly' ının .Net Framework 4.0 versiyonunda yer almaktadır. Abstract bir sınıf olması, türetmede(Inheritance) kullanıldığı takdirde anlam kazanacak bir tip olduğunu ifade etmektedir.

Aslında teori basittir. DataContractResolver sınıfı iki abstract metod tanımlaması içerir. ResolveType ve ResolveName. Bu metodlar tahmin edileceği üzere, tip çözümlemesinde serileştirme(Serialization) ve ters-serileştirme(DeSerialization) işlemlerinde bir veya daha fazla Known Type' ın ele alınması gerektiği durumlarda devreye girmektedir. Çok doğal olarak metodların uygulanması için bir sınıfın DataContractResolver tipinden türetilmesi gerekir. O halde "türetilen ve tip çözümlemesi işlerini üstlenen sınıf nerede kullanılır?" sorusu da ortaya çıkmaktadır Wink Bunun için farklı teknikler olmasına rağmen belkide en basiti, DataContractSerializer nesne örneği oluşturulurken yapılan bildirimdir. Böylece, DataContractSerializer nesne örneğinin uygulayacağı serileştirme ve ters-serileştirme işlemleri sırasında karşılaşılabilecek olası Known Type sorunlarında başvurulabilecek bir yardımcı belirlenmiş olmaktadır ki bu yardımcı, DataContractResolver türevi olan bir sınıftır. İşe bu açılardan bakıldığında, DataCotractResolver sayesinde dinamik tip çözümleme yeteneğine(Dynamic Type Resolution) sahip olduğumuzu görebiliriz. Aslında kafalarımızı dahada karıştırmadan önce dilerseniz bir önceki yazımızda ele aldığımız ve Known Type sendromuna neden olan örneğimizi ele alıp ilerlemeye çalışalım. (Örneklerimizi Visual Studio 2010 Beta 1 ve .Net Framework Beta 1 üzerinde geliştirdiğimizi hatırlatmak isterim. Yani bir sonraki sürümde Beta 1 pek çok farklılık olabilir Undecided)

using System;
using System.Runtime.Serialization;
using System.Xml;

namespace UsingDataContractResolver
{
    [DataContract]
    class Product
    {
        [DataMember]
        public object Information { get; set; }
    }

    [DataContract]
    class ProductInformation
    {
        [DataMember]
        public string Summary { get; set; }
        [DataMember]
        public int Id { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            XmlObjectSerializer serializer = new DataContractSerializer(typeof(Product));

            serializer.WriteObject(new XmlTextWriter(Console.Out) { Formatting = Formatting.Indented }
                , new Product { Information = new ProductInformation { Id = 1000, Summary = "Özet bilgi" } });
       }
    }
}

Hatırlayacağınız üzere object tipinden tanımlanmış olan Information özelliğine, ProductInformation tipinden bir nesne örneği atandığında ve Known Type ile ilişkili bir bildirimde bulunmadığımızda, çalışma zamanı hatası almaktaydık. Bu sefer örneğimizi .Net Framework 4.0 tabanlı olarak derleyip çalıştıracağız. Çok doğal alarak SerializationException tipinden bir istisna mesajı almayı bekliyoruz. Ancak bu sefer, SerializationException mesajı içerisinde DataContractResolver kullanılmasının da önerildiği görülmektedir.

Peki ya çözüm?

Zaten .Net 3.5 sürümünde KnownType niteliği gibi materyalleri kullanarak bu sorunu aşabilmekteyiz. Ancak WCF 4.0 ile birlikte sunulan DataContractResolver abstract sınıfı sayesinde, söz konusu sorunu çalışma zamanında dinamik olarak değerlendirme şansına sahibiz. Şimdi örneğimizi buna göre revize edeceğiz. İlk yapmamız gereken DataContractResolver türevli bir sınıfın tasarlanması olacaktır. Aynen aşağıda görüldüğü gibi.

class ProductInformationResolver
        :DataContractResolver
    {
        public override Type ResolveName(string typeName, string typeNamespace, DataContractResolver knownTypeResolver)
        {
            if (typeName == "ProductInfo"
                && typeNamespace == "http://www.adventure.com/resolver/productInformationType")
                return typeof(ProductInformation);
            else
                return knownTypeResolver.ResolveName(typeName, typeNamespace, null);
        }

        public override void ResolveType(Type dataContractType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace)
        {
            if (dataContractType == typeof(ProductInformation))
            {
                XmlDictionary dictionary = new XmlDictionary();
                typeName = dictionary.Add("ProductInfo");
                typeNamespace = dictionary.Add("http://www.adventure.com/resolver/productInformationType");
            }
            else
            {
                knownTypeResolver.ResolveType(dataContractType, null, out typeName, out typeNamespace);
            }
        }
    }

Güzel...Laughing Şimdi bir kaç noktayı açıklığa kavuşturmaya çalışalım. Öncelikli olarak DataContractResolver tipine ait iki metodun ezildiğini(override) görmekteyiz. ResolveType metodu serileştirme işlemi sırasında devreye girmektedir ve içeride kontrol edilen tipin XML' de nasıl ifade edileceğini belirtmektedir(xsi:type tanımlaması). Metodda ilk olarak dataContractType parametresinin çalışma zamanında ProductInformation olup olmadığı kontrol edilir. Eğer ProductInformaion tipindense yeni bir XmlDictionary nesnesi örneklenir ve typeName ile typeNamespace değerleri bu nesne üzerine Add metodu ile set edilir. Zaten typeName ve typeNamespace parametrelerinin out tipinden oldukları gözden kaçmamalıdır. Bir başka deyişle bu parametre değerleri, ResolveType metodunun çağırıldığı ortama aktarılmaktadır.(Out ve Ref parametrelerini hatırlıyorsunuz değil mi ?  Wink ) Kısacası, serileştirme işlemi sırasında eğer ProductInformation tipi ile karşılaşılırsa, tip çözümlemesinin nasıl yapılacağı geliştiricinin isteği doğrultusunda tanımlanabilmektedir. Burada yer alan typeName veya typeNamespace değerlerinin herhangibir dış ortamdan alınabileceğini(örneğin parametrik bir XML tablosu birden fazla tipin çözümlenmesi sırasında değerlendirilebilir) belirtmekte yarar olduğu kanısındayım.

ResolveName metodu ise tahmin edileceği üzere ters serileştirme(DeSerialization) işlemi sırasında devreye girmektedir. Metod içerisinde ilk olarak typeName ve typeNamespace değişkenleri kontrol edilir ve buna göre geriye döndürülecek tip(Type) belirlenir. Bu metod içerisinde de nesne tipi(Object Type) ve xsi:type eşleştirmeleri için bir referans veri kaynağı(örneğin bir XML içeriği) kullanılabilir.

Peki ya bundan sonrası? Çalışma zamanı ProductInformationResolver tipini kullanacağını nereden bilecek? İşte cevap...

XmlObjectSerializer serializer = new DataContractSerializer(typeof(Product),null,Int32.MaxValue,false,false,null,new ProductInformationResolver());
            serializer.WriteObject(new XmlTextWriter(Console.Out) { Formatting = Formatting.Indented }
                , new Product { Information = new ProductInformation { Id = 1000, Summary = "Özet bilgi" } });

Dikkat edileceği üzere DataContractSerialiazer nesne örneği oluşturulurken son parametre olarak ProductInformationResolver örneği verilmektedir. Buna göre serializer nesnesinin yapacağı serileştirme ve ters-serileştirme işlemleri sırasında, ProductInformationResolver nesne örneği devreye girecektir. Örneğimizi bu haliyle deneyecek olursak, çalışma zamanında aşağıdaki sonuçların üretildiğini görebiliriz.

Görüldüğü üzere Information elementi içerisinde, DataContractResolver türevli olan ProductInformationResolver nesne örneğine ait ResolveType metodu içerisinde belirlenen, typeName ve typeNamespace değerleri yer almaktadır. Elbetteki serileştirilen nesnenin ters-Serileştirme işlemi sırasında da işlemlerin başarılı bir şekilde yürütüldüğü gözlemlenebilir. Ama yinede kodumuzu aşağıdaki gibi revize edip ters serileştirme işleminin çalıştığından emin olmalıyız.

static void Main(string[] args)
        {
            FileStream fs=new FileStream("Product.xml",FileMode.Create,FileAccess.Write);
            serializer.WriteObject(fs
                , new Product { Information = new ProductInformation { Id = 1000, Summary = "Özet bilgi" } });
            fs.Close();

            Product product=(Product)serializer.ReadObject(new FileStream("Product.xml",FileMode.Open,FileAccess.Read));
            ProductInformation information=(ProductInformation)product.Information;
            Console.WriteLine("{0} {1}",information.Id,information.Summary);

       }

Bu kez Product.xml dosyası içerisinde serileştirme işlemini yaptıktan sonra ReadObject metodu yardımıyla XML kaynağından okuma işlemini gerçekleştirmekteyiz. ReadObject metodu geriye object türünden bir referans döndürdüğü için, bilinçli bir tür dönüşümü(Explicitly Type Cast) yapılmaktadır. Sonrasında ise product nesne örneği üzerinden Information özelliğine gidilmekte ve ProductInformation tipine dönüştürülen referansın, Id ve Summary değerlerine bakılmaktadır. İşte çalışma zamanı sonucu.

WCF 4.0 son sürümü ile gelmesi muhtemel olan bu yenilik sayesinde, Known Type durumlarının çalışma zamanında dinamik olarak değerlendirilmesi sağlanmaktadır. Bu özelliğin geliştiricilere daha büyük bir esneklik sunduğuda ortadadır. Böylece geldik WCF 4.0 ile ilgili bir yeniliğin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Tags:   ,
Categories:   WCF 4.0 Beta 1
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WCF 4.0 Yenilikleri - HTTP Cache Desteği [Beta 1]

Salı, 22 Eylül 2009 22:07 by bsenyurt

Merhaba arkadaşlar,

Performans pek çok uygulama geliştirme ortamında önem arz eden konuların başında gelmektedir. Özellikle Web tabanlı uygulamalarda performans arttırmak adına göz önüne alınan kriterlerden biriside farklı tipteki önbellekleme(Caching) işlemleridir. En basit ve popülerlerinden birisi olan Output Caching,REST tabanlı WCF servisleri içinde kullanılabilmektedir. WCF' in önceki sürümünde WebOperationContext tipinden yararlanılarak ekstra kod eforu ile ele alınabilen Output Cache özelliği, 4.0 sürümünde tamamen dekleratif olarak değerlendirilebilmektedir. Aslında bu yenilik bilindiği üzere WCF Rest Starter Kit Preview 2 ile birlikte .Net Framework 3.5 üzerinde de uygulanabilmektedir. Output Cache özelliği performans için önemli bir kriter olduğundan, WCF 4.0 versiyonunda doğrudan ele alınmaktadır.

Bu yazımızda ön bellekleme işleminin WCF 4.0 içerisinde, REST tabanlı servisleri için nasıl geliştirilebileceğini ele almaya çalışacağız. İşe ilk olarak bir WCF Service Application projesi oluşturarak ve içerisine aşağıdaki servis sözleşmesi ve uygulayıcı tipi ekleyerek başlayabiliriz.

Servis Sözleşmesi(Service Contract)

using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Web.Caching;

namespace Calculus
{
    [ServiceContract(Namespace="http://calculus/BasicMathService")]
    public interface IBasicMathService
    {
        [AspNetCacheProfile("ShortCache")] // Config dosyasındaki outputCacheProfile girdilerinden parametre olarak verilen isimdekini işaret eder
        [OperationContract]
        [WebGet]       
        string Sum(double x, double y);
    }
}

Uygulayıcı tip;

using System;
using System.ServiceModel.Activation;

namespace Calculus
{
    [AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed)]
    public class BasicMathService
        : IBasicMathService
    {
        public string Sum(double x, double y)
        {
            return string.Format("{0} zamanlı hesaplama {1}+{2}={3}", DateTime.Now.ToLongTimeString(), x, y, x + y);
        }
    }
}

Servisimize ait svc içeriği;

<%@ ServiceHost Language="C#" Debug="true" Service="Calculus.BasicMathService" CodeBehind="BasicMathService.svc.cs" Factory="System.ServiceModel.Activation.WebServiceHostFactory" %>

IBasicMathService isimli servis sözleşmesi içerisinde yer alan Sum metoduna WebGet ve OperationContract dışında AspNetCacheProfile isimli bir niteliğin(attribute) daha uygulandığı görülmektedir. Bu nitelik parametre olarak string bir bilgi alır. Bu bilgi ise biraz sonra yazacağımız Web.config dosyası içerisindeki bir Cache profilini işaret etmektedir. Dolayısıyla bir operasyonun çıktısının ön belleklenmesi için gerekli özellikler, konfigurasyon dosyasında tanımlanır. Servis kodunda dikkat çekici noktalardan biriside, BasicMathService tipinin, AspNetCompatibilityRequirements isimli niteliği uygulamış olmasıdır. Bu durumu biraz sonra değerlendiriyor olacağız nitekim uygulanmadığı hallerde başımıza iş açacaktır Undecided

Tabikide üzerinde durmamız gereken en önemli kısım config dosyası içeriğidir.

<?xml version="1.0"?>
<configuration>
 <system.web>
  <compilation targetFrameworkMoniker=".NETFramework,Version=v4.0" debug="false"/>
 </system.web>
 <system.web>
  <caching>
   <outputCacheSettings>
    <outputCacheProfiles>
     <!-- Süreleri farklı olan iki ayrı Cache profili tanımlanmıştır -->
     <add name="ShortCache" duration="20" varyByParam="none"/>
     <add name="LongCache" duration="600" varyByParam="none"/>
    </outputCacheProfiles>
   </outputCacheSettings>
  </caching>
 </system.web>
 <system.serviceModel>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
  <!--WebServiceHostFactory tipini kullandığımız için aşağıdaki davranışı eklememize gerek kalmamıştır-->
  <!--<behaviors>
      <endpointBehaviors>
        <behavior>
          <webHttp enableHelp="true" />
        </behavior>
      </endpointBehaviors>
    </behaviors>-->
  <services>
   <service name="Calculus.BasicMathService">
    <endpoint binding="webHttpBinding" contract="Calculus.IBasicMathService"/>
   </service>
  </services>
 </system.serviceModel>
</configuration>

Görüldüğü üzere outputCacheSettings elementi içerisinde iki farklı önbellekleme profili tanımlanmıştır. Buna göre, Sum isimli operasyonumuz 20 saniyelik ön bellekleme yapan ve Querystring parametrelerini hesaba katmayan bir yapı sunmaktadır. (Tabiki önbellekleme profilini oluştururken farklı özellikleride değerlendirebiliriz. Örneğin ön bellekleme lokasyonunu değiştirebilir sunucu tarafı, istemci tarafı veya her iki taraf gibi değerler verilebilir.) Config dosyasında bold olarak işaretlediğimiz diğer kısımlarda önemlidir. WCF 4.0 tarafına getirilen Output Cache özelliği aslında Asp.Net ortamının hazır olarak sahip olduğu Output Cache kabiliyetlerini kullanmaktadır. Bu nedenle Asp.Net uyumluluğu önemlidir.

Uygulama basit bir kaç ayarlamaya sahip olmasına rağmen geliştirme ve testler sırasında beklenmedik pek çok hata ile karşılabiliriz. Sealed İşte karşılaşabileceğimiz bir kaç hata ve önerilen çözümler(ki bu çözümlerin bir kısmı must olarak görülmelidir)

  • Web.config dosyasında targetFrameworkMonikor değerinin set edildiğinden emin olmalıyız. Edilmediği takdirde çalışma zamanında alınacak hata mesajı :  "The application domain or application pool is currently running version 4.0 or later of the .NET Framework. This can occur if IIS settings have been set to 4.0 or later for this Web application, or if you are using version 4.0 or later of the ASP.NET Web Development Server. The <compilation> element in the Web.config file for this Web application does not contain the required 'targetFrameworkMoniker' attribute for this version of the .NET Framework (for example, '<compilation targetFrameworkMoniker=".NETFramework,Version=v4.0">'). Update the Web.config file with this attribute, or configure the Web application to use a different version of the .NET Framework."
  • aspNetCompatibilityEnabled niteliğinin değeri true olmadığı sürece, AspNetCacheProfile özelliğini kullanamayız. Alınacak çalışma zamanı hata mesajı : "AspNetCacheProfileAttribute is supported only in AspNetCompatibility mode. "
  • aspNetCompatibilityEnabled niteliğinin true olarak atamış olması yeterli olmayacaktır. Nitekim servis sınıfı için AspNetCompatibilityRequirements niteliğinin de set edilmesi gerekir. Edilmediği takdirde alınacak çalışma zamanı hata mesajı : "The service cannot be activated because it does not support ASP.NET compatibility. ASP.NET compatibility is enabled for this application. Turn off ASP.NET compatibility mode in the web.config or add the AspNetCompatibilityRequirements attribute to the service type with RequirementsMode setting as 'Allowed' or 'Required'"
  • Asp.Net Development Server üzerinden geliştirme yapıldığında Output Cache özelliği test edilememektedir. Output Cache özelliğinin çalışabilmesi için, servis uygulamasının IIS üzerine dağıtılması gerekmektedir.
  • IIS üzerine atılan WCF servis uygulamasının özelliklerinden Target Framework' ün 4.0 versiyonunu işaret edecek şekilde değiştirilmesi gerekir. (Örneği geliştirdiğim zamanda 4.0.20506 versiyonu idi)
  • Güvenlik ile ilişkili bir sıkıntı yaşanabilir. Özellikle varsayılan olarak Anonymous Access ve Integrated Windows Authentication modlarından her ikiside seçili gelebilir. Bu noktada WCF çalışma zamanı sadece bir tanesinin seçili olmasını isteyebilir. Bu durumda sadece Anonymous Access seçeneğini işaretleyerek testleri yapabiliriz. Yapılan değişiklik sonrası yinede hata mesajı alınıyorsa, IIS' in bir kere reset edilmesi gerekebilir.
  • varyByParam değerini kullanmıyorsak bile none olarak atamalıyız(Asp.Net tarafından bildiğimiz bir kural). Yapmadığımız takdirde alacağımız çalışma zamanı hata mesajı : "The cache profile, 'ShortCache', must include value for 'VaryByParam'. "

Gelelim çalışma zamanı sonuçlarına. IIS üzerine atılan WCF servisimizi çalıştırdıktan sonra Sum operasyonu için yapılan ilk HTTP GET talebi sonrası aşağıdaki örnek ekran görüntüsü elde edilmiştir.

Görüldüğü gibi ilk talep karşılanmıştır. Hemen zaman bilgisine dikkat edelim ve 20 saniyelik Output Cache süresi dolmadan önce ikinci bir talepte bulunduğumuzu hatta x ve y değerlerini farklı olarak verdiğimizi düşünelim. Bu durumda aşağıdaki sonuçlar alınacaktır.

Görüldüğü gibi istemciye gönderilen içerik değişmemiştir. Nitekim şu andaki cevap, Asp.Net Output Cache mekanizması tarafından karışlanmış bir önceki talep için üretilen hazır çıktıdır. Ancak Output Cache süresini aştıktan sonra tekrar talepte bulunursak ikinci talep için güncel sonuçları aşağıdaki örnek çıktıda görüldüğü gibi alabiliriz.

Böylece sistemin çalıştığını ispat etmiş olduk Wink Output Cache özelliği REST tabanlı WCF servislerinde, perfomansı arttırıcı bir unsur olarak görülebilir. Nitekim sunucu ve istemci arasındaki gidiş gelişlerde, sürekli hesaplanması gerekmeyen ve çoğunlukla sabit olan içeriklerin tekrardan üretilmesi yerine belirli zaman dilimleri boyunca ön bellekten karşılanması hem servisin işlem yükünü azaltacak hem de hızlı cevap verme sürelerini doğuracaktır. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

RESTSupport.rar (37,47 kb)

Tags:   ,
Categories:   WCF 4.0 Beta 1
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WCF 4.0 Yenilikleri - Automatic Help Page [Beta 1]

Perşembe, 17 Eylül 2009 01:02 by bsenyurt

Merhaba Arkadaşlar,

WCF 4.0 tarafında beklenen gelmesi yüksek olan yenilikleri sizlere aktarmaya çalıştığım yazılarımızın yavaş yavaş sonlarına gelmekteyiz. Elbette incelemeyemediğimiz bir çok detay var. Bunları ilerleyen dönemlerde ürün son halini alırken tartışma ve araştırma fırsatımız olacak. Bu yazımızda WCF 4.0 tarafına entegre olarak gelen REST geliştirme modeline yönelik yeteneklerden bahsedeceğiz. Aslında bu yeniliklerin çoğunu WCF Rest Starter Kit ile birlikte, .Net Framework 3.5 platformu üzerinde kullanabiliyoruz. Ne varki, ek bir pakete ihtiyaç duyulmadan kullanılabilen iki özellik, WCF 4.0 içerisine entegre edilmiş durumda. Bunlardan birisi otomatik yardım sayfaları(Automatic Help Page). WCF 4.0 içerisindeki WebServiceHost fabrikasını kullandığımızda otomatik olarak her RESTful servis için gelen ve varsayılan olarak açık olan yardım sayfaları, istenirse konfigurasyon dosyasındaki bir nitelik yardımıyla kapatılabilirde.

Yardım sayfaları özellikle HTTP protokolünün GET,POST,PUT veya DELETE gibi metodları yardımıyla erişilen RESTful servis operasyonlarının tüketiciler tarafından kolayca anlaşılmasını hedeflemektedir. Nitekim, tüketici tarafını yazan geliştiricilerin bu modelindeki bir servisin operasyonlarını çağırırken, HTTP metoduna göre nasıl bir paket içeriği veya URL hazırlayıp göndermeleri gerektiğini bilmeleri gelmektedir. Otomatik olarak üretilen yardım sayfalarının tek ve yegane amacı bu ihtiyacı karşılamaktır.

Şimdi bu konuyu basit bir örnek üzerinden değerlendirmeye çalışarak sonuçları görmeyi hedefleyeceğiz. Bu amaçla Visual Studio 2010 Beta 1 ortamında ve .Net Framework 4.0 Beta 1 odaklı olarak hazırlayacağımız bir WCF Service Application üzerinden ilerliyor olacağız. Bu örnekte System.ServiceModel.Web.Activation isim alanında yer alan WebServiceHostFactory fabrikasından yararlanmayacağız(Bir sonraki konumuz olan HTTP Cache desteğine ait örnekte ise kullanacağız). Servisimize ait sözleşmemizi(Service Contract) aşağıda görüldüğü gibi geliştirdiğimizi düşünelim.

using System.ServiceModel;
using System.ServiceModel.Web;

namespace GeoService
{
    [ServiceContract(Namespace="http://GeoServices/LocationService")]
    public interface ILocationService
    {
        [OperationContract]
        [WebGet]
        string FindLocation(string gsmNumber);

        [OperationContract]
        [WebGet(ResponseFormat = WebMessageFormat.Json)]
        string FindLocationInJson(string gsmNumber);

        [OperationContract]
        [WebInvoke(Method = "DELETE")]
        string Delete(string location);

        [OperationContract]
        [WebInvoke(Method="POST")]
        bool Insert(Customer customer);

        [OperationContract]
        [WebInvoke(Method = "PUT")]
        bool Move(Customer customer);
    }
}

Servis sözleşmesinde örnek olarak HTTP GET, POST, PUT ve DELETE metodlarına göre kullanılabilen bazı operasyonlar tanımlanmıştır. FindLocationInJson operasyonu, JSON formatında cevaplar(Response) üretmektedir. Bilindiği üzere WebGet metodu için yapılan çağrılarda URL satırından gelen talepler söz konusudur. WebInvoke niteliği ile imzalanmış operasyonlarda ise HTTP içeriğinin paket olarak gönderilmesi söz konusudur. Bu operasyonlardan Insert ve Move metodları, parametre olarak serileştirilebilir bir tip kullanmaktadır. Dolayısıyla, servisi REST modele göre kullanmak isteyen geliştiricilerin bu kritik noktalara göre paket içeriği veya URL bilgilerini nasıl hazırlayacaklarını bilmeleri son derece yararlı olacaktır. Gelelim servis sözleşmesini uygulayan tipimize;

using System;

namespace GeoService
{
    public class LocationService
        : ILocationService
    {
        public string FindLocation(string gsmNumber)
        {
            return String.Format("({0}:{1})-({2}:{3})", 36, 42, 26, 45);
        }

        public string FindLocationInJson(string gsmNumber)
        {
            return String.Format("({0}:{1})-({2}:{3})", 36, 42, 26, 45);
        }

        public string Delete(string location)
        {
            return string.Format("{0} ----> {1}",location);
        }

        public bool Insert(Customer customer)
        {
            return false;
        }

        public bool Move(Customer customer)
        {
            return true;
        }
    }

    public class Customer
    {
        public string GsmNo { get; set; }
        public string Name { get; set; }
        public string Location { get; set; }
    }
}

Operasyonalrın uygulanışında herhangibir özel durum söz konusu değildir aslında. Asıl üzerinde duracağımız nokta Web.config dosyasının içeriğidir. Web.config dosyasında yer alan system.ServiceModel element içeriğini aşağıdaki gibi düzenleyebiliriz.

<system.serviceModel>
    <services>
      <service name="GeoService.LocationService">
        <endpoint address="" binding="webHttpBinding" contract="GeoService.ILocationService"/>
      </service>
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior>
          <webHttp enableHelp="true"/>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>

Bağlayıcı tip(Binding Type) olarak webHttpBinding kullanmamızın sebebi elbetteki servisimizin RESTful özelliklerine göre hizmet vermesinin sağlanmasıdır. Diğer yandan endPoint noktasına eklenen webHttp isimli davranışın enableHelp özelliğine true değeri atanmıştır. Buna göre çalışma zamanında help takısı ile servis talep edildiğinde aşağıdaki çıktı ile karşılaşılır.

URL : http://localhost:2166/LocationService.svc/help

Görüldüğü üzere tüm operasyonlar için nasıl çağırılacaklarına, ne tür cevaplar döndüreceklerine dair bilgiler ve hatta kullanılan serileştirilebilir tipler varsa bunların şemalarına ait detaylar bu yardım sayfasında yer almaktadır. Tabi şu anda help sayfası için, Internet Explorer' ın Feed özelliğine göre bir çıktı elde edilmektedir. Normal şartlarda kaynağa baktığımızda aslında varsayılan olarak ATOM formatında bir feed içeriğinin üretildiği görülebilir.

Hemen hatırlalatım örneğimizde WebServiceHostFactory kullanmadığımızdan, yardım sayfaları enableHelp niteliğine true değerini atamadığımız sürece çalışmayacaktır. GET metodlarının çağrıları dikkat edileceği üzere doğal olarak bir URL formatındadır. Diğer taraftan örneğin Insert operasyonunu çağırmak istediğimizi göz önüne alalım. Bu durumda geliştirici olarak tek yapmamız gereken Request ve örnek talep formatı için Example isimli bağlantılardan elde edilen sonuçlara bakmak olacaktır. Insert operasyonu için üretilen şema aşağıdaki gibidir.

URL : http://localhost:2166/LocationService.svc/help/request/schema

Dikkat edileceği üzere şema bilgisinden yararlanılarak operasyona parametre olarak nasıl bir tip gönderilmesi gerektiği, üyelerinin ne olacağı açık bir şekilde görülmektedir. Insert operasyonu için Example linkine tıkladığımızda ise örnek bir POST çağrısının içeriğinin nasıl olması gerektiğinin örneklendiği görülecektir.

URL : http://localhost:2166/LocationService.svc/help/Insert/request/example?format=Xml

İşte bu kadar. Son derece basit bir özellik olmakla birlikte otomatik yardım sayfaları aslında tüketiciyi yazan geliştiriciler için bulunmaz bir nimettir. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

HelpSupport.rar (18,55 kb)

Tags:   ,
Categories:   WCF 4.0 Beta 1
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WCF 4.0 Yenilikleri - Routing Service - MatchAll Filtresi [Beta 1]

Pazartesi, 14 Eylül 2009 09:00 by bsenyurt

Merhaba Arkadaşlar,

Bundan önceki yazılarımızda WCF 4.0 için yönlendirme servislerinin(Router Service) nasıl yazılabileceğini incelemeye çalışmıştık. Fark edeceğiniz üzere yönlendirme servislerinin en önemli noktaları arasında filtreleme tablosu ve filtrelerin olduğunu gördük. Bununla birlikte sadece Action tipinde bir filtreleme kullanıp, istemciden gelen SOAP paketinin Action kısmından yararlanılarak bir yönlendirme yapılmasını inceledik. Oysaki filtreleme tipi olarak Action dışında, Address, AddressPrefix, StrictAnd, EndpointName, MatchAll, XPath gibi seçeneklerimiz de bulunmaktadır. İşte bu yazımızda MatchAll seçeneğini incelemeye çalışıyor olacağız. MatchAll seçeneğine göre, istemciden gelen mesajın içeriği ne olursa olsun, söz konusu talebin tanımlanan birden fazla DownStream servise yönlendirilmesi mümkündür. Ancak önemli bir kısıtlama vardır. Bu kısıtlamaya göre sadece One-Way veya Duplex modeldeki iletişim(Communication) desteklenir. Dolayısıyla Request/Reply modelde olan iletişimi ele alan tipleri yönlendirme servisi üzerinde kullanamayız. Bu kısıtlamaya rağmen bazı senaryolarda(örneğin asenkron modellerde), istemciden gelen talebin birden fazla DownStream' e aktarılmasının WCF 4.0 ile gelen özellikler sayesinde kolaylaştırılmış olması, geliştiriciler açısından oldukça heyecan vericidir.Wink 

Öyleyse vakit kaybetmeden basit bir örnek üzerinden ilerlemeye ne dersiniz. Ben yazıyı gecenin geç bir vaktinde yazdığım için yanımda bir adet sıcak kahveyi bulundurmayı ihmal etmedim. Cool Örnek senaryomuzda aynı servis sözleşmesini(Service Contract) implemente eden 3 farklı alt servisimiz bulunmaktadır. Router Service, istemciden gelen talebi alıp ne olduğu ile ilgilenmeden doğrudan bu 3 servise aktarma işlemini üstlenmektedir. Dolayısıyla ispat etmemiz gereken noktalardan birisi, istemciden gelen talebin sonrasında operasyonun 3 servis üzerinde de çalışıyor olmasıdır. OneWay veya Duplex kısıtlamasından dolayı biz örneğimizde OneWay olarak imzalanmış basit bir servis operasyonu kullanıyor olacağız. Öncelikle örneğimize ait mimari modelimize bir göz atalım.

Görüldüğü üzere sadece Endpoint tanımlamaları açısından farklı olan(ve gerçek hayat senaryolarında istenirse farklı makinelerde bulunabilecek olan) ama aynı sözleşmeyi uygulayan 3 Downstream servisimiz bulunmaktadır. Servislerimizin uyguladığı sözleşme aşağıdaki kod parçasında olduğu gibidir.

using System.ServiceModel;
using System.Runtime.Serialization;

namespace AdventureContracts
{
    [ServiceContract(Namespace="http://adventure/productService")]
    public interface IAdventureContract
    {
        [OperationContract(IsOneWay=true)]
        void ProcessProduct(Product product);
    }

    [DataContract]
    public class Product
    {
        [DataMember]
        public int ProductId { get; set; }
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public double ListPrice { get; set; }
        [DataMember]
        public int Amount { get; set; }
    }
}

Servislerimizin üçünün kodlarını burada ayrı ayrı yazmamıza gerek olmadığını düşünüyorum. Nitekim hem odaklanmamız gereken nokta Router servis tarafıdır hemde yazımızın okunurluğunun zorlaşmaması gerekmektedir. Tabiki örneği indirip incelemenizi şiddetle öneririm. Gelelim yönlendirme servisimize. Yönlendirme servisimizin App.config dosyası içeriği aşağıdaki gibidir.

Router Service konfigurasyon içeriği(App.config);

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <routing routingTableName="RTable"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <client>
      <endpoint address="net.tcp://localhost:60001/Adventure/BackendMirror/ProductService1" binding="netTcpBinding" contract="*" name="ProductServiceEndpoint1" />
      <endpoint address="http://localhost:60002/Adventure/Backend/ProductService2" binding="wsHttpBinding" contract="*" name="ProductServiceEndpoint2"/>
      <endpoint address="http://localhost:60003/Adventure/Internal/ProductService3" binding="wsHttpBinding" contract="*" name="ProductServiceEndpoint3" />
    </client>
    <services>
      <service name="System.ServiceModel.Routing.RoutingService">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:60005/Adventure/ProductService"/>
          </baseAddresses>
        </host>
        <endpoint binding="wsHttpBinding" contract="System.ServiceModel.Routing.ISimplexSessionRouter"/>
      </service>
    </services>
    <routing>
      <filters>
        <filter filterType="MatchAll" name="ProductFilter"/>
      </filters>
      <routingTables>
        <table name="RTable">
          <entries>
            <add endpointName="ProductServiceEndpoint1" filterName="ProductFilter"/>
            <add endpointName="ProductServiceEndpoint2" filterName="ProductFilter"/>
            <add endpointName="ProductServiceEndpoint3" filterName="ProductFilter"/>
          </entries>
        </table>
      </routingTables>
    </routing>
  </system.serviceModel>
</configuration>

Dikkat edileceği üzere fiterType niteliğine MatchAll değeri verilmiştir. Bu değere göre, istemciden gelecek olan talep(Request) name niteliğine atanan değere eş düşen endPoint noktalarına iletilmelidir ki bu bildirimlerde yine entries elementi içerisinde yapılmaktadır. entries elementi içerisindeki filterName niteliğinin değeri ile, filter elementi içerisindeki name niteliğinin değerlerinin aynı olduğuna lütfen dikkat edelim. Konfigurasyon dosyasında önem arz eden noktalardan bir diğeride, yönlendirme servisi için kullanılan sözleşme tipidir(ISimplexSessionRouter). Hatırlayacağınız gibi, Request/Reply modelin desteklenmediğinden bahsetmiştik. Bu nedenle daha önceki örneklerimizde kullandığımız IRequestRepyleRouter built-in sözleşme tipini bu senaryoda kullanamayız.

Örneğimizi test ettiğimizde çalışma zamanında aşağıdaki ekran görüntüsüne benzer sonuçları alırız.

Görüldüğü gibi tüm DownStream servisleri, istemciden gelen Product tipini ele almış ve basit bir şekilde kullanmıştır. Biz örneğimizde sadece gelen bilgiyi ekrana yazdırıyoruz. Aynı istemci paketinin, n sayıda DownStream servisi tarafından değerlendirilip üzerlerinde farklı şekillerde işlemler uygulanması söz konusu olduğunda, MatchAll filtereleme modelini göz önüne alabiliriz. Tabiki Request/Reply kısıtlamasını unutmamak gerekir. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Router Project 3.rar (249,84 kb)

Tags:   ,
Categories:   WCF 4.0 Beta 1
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (1) | Comment RSSRSS comment feed
Bookmark and Share

WCF 4.0 Yenilikleri - Routing Service - Hata Yönetimi [Beta 1]

Perşembe, 10 Eylül 2009 01:02 by bsenyurt

Merhaba Arkadaşlar,

Bir önceki blog yazımızda WCF 4.0 ile basit bir yönlendirme servisinin(Router Service) nasıl yazılabileceğini incelemeye çalışmıtık. Tabi bu tip bir sistemde dikkat edilmesi gereken vakalardan biriside, Downstream servislerde istisnaların(Exceptions) oluşması halinde nasıl davranılacağıdır. Peki ne gibi durumlardan bahsediyoruz? Örneğin, Router servisine gelen paketin yönlendirildiği bir alt servis çalışmıyor olabilir. Bu durumda bir TimeoutException oluşması muhtemeldir. Benzer şekilde CommunicationException ve türevi olan istisna tiplerinin fırlatılmasıda söz konusudur. Bu gibi istisnaların ortaya çıkması halinde en azından işleyişin devamlılığını sağlamak ve sistemin çökmesini engellemek için, WCF 4.0 tarafı konfigurasyon dosyasında Alternatif Endpoint tanımlamaları yapılmasına izin vermektedir. Buna göre Downstream servislerinden bahsedilen tipteki istisnalardan birisi alınırsa, istemciden gelen talebin karşılanmak üzere alternatif olarak tanımlanmış olan bir servise yönlendirilmesi sağlanmış olunur. Bu alternatif servise olan yönlendirme tamamen çalışma zamanında ve router servisin yönetimi altında gerçekleşmektedir. Konuyu daha net kavrayabilmek adına bir önceki blogumuzda yazdığımız örneği aşağıdaki vakaya göre test ettiğimizi düşünelim.

İlk etapta Router Servisimiz ile tüm DownStream servislerimiz çalıştırılır. Sonrasında istemci uygulama açıkken ve henüz taleplerini iletmeden önce Downstream servislerinden herhangibiri kapatılır. Örneğin UserService servisinin kapatıldığını düşünebiliriz. Bu durumda istemci tarafının bir CommunicationException istisnası ile sonlanması gerekmektedir.

Örneği kolay bir şekilde canlandırabilmek için Router servisimizde includeExceptionDetailInFaults özelliğine true değeri atayıp, istemci tarafındaki kod içeriğini ise aşağıdaki gibi güncelleştirmemiz yerinde olacaktır.

using System;
using System.ServiceModel;
using ClientApp.MemberManagementSpace;

namespace ClientApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("İstemci hazır...Başlamak için tuşa basınız");
            Console.ReadLine();

            try
            {
                MemberManagementServiceClient client = new MemberManagementServiceClient();

                User burak = new User { Name = "Burak Selim Şenyurt" };

                string registerResult = client.RegisterUser(burak);
                Console.WriteLine(registerResult);

                string updateResult = client.UpdateUserName(burak, "Burki");
                Console.WriteLine(updateResult);

                Console.ReadLine();
                client.Close();
            }
            catch (CommunicationException excp)
            {
                Console.WriteLine(excp.Message);
            }
        }
    }
}

Test sonrasında aşağıdaki gibi bir sonuçla karşılaşmamız muhtemeldir. 

Görüldüğü gibi RegisterService üzerinden yapılan kullanıcı kayıt operasyonu başarılı bir şekilde çalışmış ancak, UserService üzerinden yapılan çağrı için ortama bir CommunicationException döndürülmüştür. Bu son derece doğaldır, nitekim söz konusu servis kapalıdır. Sealed Peki alternatif bir yolumuz var mıdır? Yazımızın başındada belirttiğimiz gibi, Downstream servislerinden birisinin çökmesi halinde en azından istemci talebinin bir başka yedek servis üzerine pas edilmesi sağlanabilir. İlk önce bu yedek servisi(Backup Service) geliştireceğiz. Sonrasında ise, Router servisimize ait konfigurasyon dosyasında bazı değişiklikler yapmamız gerekmektedir. Örneğimizin yeni modeli grafiksel olarak aşağıdaki gibi düşünülebilir.

UserBackupService isimli yedek servisimizin konfigurasyon dosyası ve kod yapısı aşağıdaki gibidir.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="UserBackupService.UserService">
        <endpoint address="" binding="wsHttpBinding" contract="ContractLibrary.IManagementContract" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:3446/UserBackupService" />
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>

ve kod içeriği;

using System;
using ContractLibrary;
using System.ServiceModel;

namespace UserBackupService
{
    class UserService

        : IManagementContract
    {
        public string RegisterUser(User newUser)
        {
            throw new NotImplementedException();
        }

        public string UpdateUserName(User oldUser, string newName)
        {
            Console.WriteLine("UpdateUserName metodu başlatıldı...");
            return String.Format("{0} isimli kullanıcı adı {1} olarak değiştirildi", oldUser.Name, newName);
        }

        public UserService()
        {
            Console.WriteLine("UserBACKUPService nesnesi örneklendi");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ServiceHost host = new ServiceHost(typeof(UserService));
            host.Open();

            Console.WriteLine("UserBACKUPService hazır...");
            Console.ReadLine();

            host.Close();
        }
    }
}

Aslında UserService' in bire bir kopyası olan sadece farklı bir port üzerinden sunulan bir servis geliştirmiş bulunuyoruz. Elbetteki bu servisi farklı bir makine üzerinde farklı Endpoint kuralları ilede sunabilir ve alternatif Endpoint olarak kullanabiliriz. Gelelim bu yazının en can alıcı noktasına. Router servisine ait konfigurasyon dosyasının içeriği...Wink

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <routing routingTableName="RTable"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <client>
      <endpoint address="http://localhost:3445/UserService" binding="wsHttpBinding" contract="*" name="UserServiceEndpoint" />
      <endpoint address="http://localhost:3446/UserBackupService" binding="wsHttpBinding" contract="*" name="UserBackupServiceEndpoint"/>
      <endpoint address="net.tcp://localhost:4001/RegisterService" binding="netTcpBinding" contract="*" name="RegisterServiceEndpoint" />
    </client>
    <services>
      <service name="System.ServiceModel.Routing.RoutingService">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:6501/User/Management/RouterService"/>
          </baseAddresses>
        </host>
        <endpoint binding="basicHttpBinding" contract="System.ServiceModel.Routing.IRequestReplyRouter"/>
      </service>
    </services>
    <routing>
      <filters>
        <filter name="RegisterFilter" filterType="Action" filterData="http://www.azon.com/Registration"/>
        <filter name="UpdateUserFilter" filterType="Action" filterData="http://www.azon.com/UpdateUser"/>
      </filters>
      <routingTables>
        <table name="RTable">
          <entries>
            <add filterName="RegisterFilter" endpointName="RegisterServiceEndpoint"/>
            <add filterName="UpdateUserFilter" endpointName="UserServiceEndpoint" alternateEndpoints="alternateEndpointList"/>
          </entries>
        </table>
      </routingTables>
      <alternateEndpoints>
        <list name="alternateEndpointList">
          <endpoints>
            <add endpointName="UserBackupServiceEndpoint"/>
          </endpoints>
        </list>
      </alternateEndpoints>
    </routing>
  </system.serviceModel>
</configuration>

İlk etapta, Backup servisi içinde bir Endpoint bildirimi yapıldığı ve yedek servisin işaret edildiği farkedilmektedir. Diğer yandan routingTables içerisinde yapılan entries bildirimlerinden UpdateUserFilter isimli olanında alternateEndpoints isimli bir nitelik(attribute) dikkati çekmektedir. Bu nitelik, dosyanın ilerleyen kısımlarında yer alan alternateEndpoints elementi altındaki listeyi işaret etmektedir. Bu liste içerisinde n sayıda alternatif endPoint ismi belirtilebilir. Bir başka deyişle bir endPoint' in karşılayamadığı istekleri, birden fazla endPoint noktasına denenmek üzere aktarabiliriz. Tabi bu durumu henüz test etme şansım olmadı. Ki beklenen sırasıyla servislerin denenmesi ve başarılı olandan sonrakilere geçilmemesi yönünde olmalıdır. Ancak entries/add elementi içerisinde priority isimli bir nitelikte bulunmakta ve öncelik seviyesini belirlemektedir. İşte size bir garajda araştırma ödevi.Cool 

Artık vakamızı tekrardan test edebiliriz. Yine tüm servisleri(Backup servisimiz dahil) çalıştıracak, ancak istemci talepte bulunmadan önce UserService' ini kapatacağız. İşte sonuçlar;

Görüldüğü gibi, UserService' in kapalı olması ve Exception üretmesi durumunda, Router servisimiz talebi bu kez alternatif endPoint listesinde belirtilen UserBackupService isimli yedek servise doğru yönlendirmiş ve istemcinin talebinin buradan karşılanmasını sağlamıştır. Tabiki burada ele alınan alternatif Endpoint' lerin işaret ettiği servisler farklı makinelereden, farklı bağlayıcı tiplerle(Binding Types), farklı iletişim protokolleri ile dağıtılabilir. Bu tamamen yedek servis stratejimize bağlıdır. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Router Project 2.rar (128,25 kb)

Tags:   ,
Categories:   WCF 4.0 Beta 1
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WCF 4.0 Yenilikleri - Routing Service Geliştirmek - Hello World [Beta 1]

Perşembe, 27 Ağustos 2009 03:03 by bsenyurt

Merhaba Arkadaşlar,

Routing Service konusu ile ilişkili bir önceki yazımızda modelin sunduğu alt yapıya kısaca değinmeye çalışmış ancak bir örnek geliştirme girişiminde bulunmamıştık. Bu yazımızda ise bir Hello World örneğini geliştirmeye çalışacağız. (Örneğimizi .Net Framework Beta 1 ve Visual Studio 2010 Beta 1 ile geliştirdiğimizi bir kere daha hatırlatmak isterim.) İlk olarak sizlere, örnek senaryomuzdan bahsetmek isterim. Router servisimizin arkasında genellikle Downstream olarak adlandırılan servislerimiz yer almaktadır. Bu servislerden birisi, kullanıcı kayıt işlemlerini(Register) üstlenirken, diğeride kullanıcı adını güncelleştirme işlemini ele almaktadır.(Tabiki bu örnekteki amaç yönlendirme servisini devreye almak olduğundan sadece iki basit operasyonun, farklı servislere dağılması üzerine yoğunlaşılmıştır) İstemci uygulama, Router servisi üzerinden yeni bir kullanıcıyı kayıt etmek veya güncellemek ile ilişkili işlemler için talepte bulunabilir. Gelen talep aslında bir SOAP paketidir. Geliştirdiğimiz örnekte, Router servis tarafında yer alan filtreleme içerisinde, SOAP Action içeriğine göre bir ayrıştırma yapılacak ve arka planda uygun olan servis metodlarına yönlendirme işlemi gerçekleştirilecektir. Buna göre Router servisimizin, istemciden gelen paketin Action değerine bakarak bir karar vereceğini söyleyebiliriz. Elbette bunu birde programatik ortamda söyleyebilmemiz gerekmektedir Wink Örneği tamamladıktan sonra oluşturduğumuz mimari tasarıma bakarsak(ki burada yazımızın başında veriyorum), ne yapmak istediğimizi daha net görebiliriz.

Downstream servislerimizden olan RegisterService hizmetine TCP bazlı bir iletişim ile erişilebilmektedir. UserService isimli diğer servisimiz ise Ws HTTP protokolüne göre hizmet sunmaktadır. RouterService isimli yönlendirme servisimiz ise Basic HTTP tabanlı bir iletişim kanalı sağlamaktadır. Dikkat edileceği üzere RouterService üzerinden ayrılan iki dalın içerisindeki URL adresleri birbirlerinden farklıdır. Bu adresler aslında, RegisterService ve UserService isimli hizmetlerin ortaklaşa uyguladıkları servis sözleşmesi(Service Contract) içerisinden tanımlanan Action değerleridir. Dolayısıyla işe ilk olarak her iki servisinde uyguladığı ortak sözleşmeyi tasarlayarak başlamamız gerekmektedir. Söz konusu sözleşme ContractLibrary isimli bir sınıf kütüphanesi içerisinde tanımlanmış olup sadece Downstream servisler tarafından kullanılmaktadır. İşte servis sözleşmemiz(Service Contract).

using System.ServiceModel;
using System.Runtime.Serialization;

namespace ContractLibrary
{
    // Downstream servislerin tamamının uygulayacağı ortak servis sözleşmesi
    // Namespace elementinin içeriği filtrelemelerde Action kısmına yazılacak bilgiler için önem arz etmektedir.
    [ServiceContract(Name="MemberManagementService",Namespace="http://www.azon.com/Membership/Management")]
    public interface IManagementContract
    {
        // Action değerlerini biz belirliyoruz
        [OperationContract(Action = "http://www.azon.com/Registration",ReplyAction="http://www.azon.com/RegistrationResponse")]
        string RegisterUser(User newUser);

        [OperationContract(Action = "http://www.azon.com/UpdateUser", ReplyAction = "http://www.azon.com/UpdateUserResponse")]
        string UpdateUserName(User oldUser, string newName);
    }
   
    [DataContract]
    public class User
    {
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public string Id { get; set; }
    }
}

IManagementContract isimli arayüz(Interface) içerisinde tanımlanan RegisterUser ve UpdateUserName metodlarının OperationContract niteliklerine dikkat edilmelidir. Bu niteliklerde yer alan Action ve ReplyAction değerleri ile, SOAP paketleri ve WSDL içerisindeki bazı tanımlamalar doğrudan etkilenmektedir. Çok doğal olarak, Router servisi içerisindeki filtreleme tablosunda yer alan Action kriterlerinde, bu operasyonlar için tanımlanan Action değerleri ele alınmalıdır. Şimdi sırasıyla RegisterService ve UserService servislerinin olduğu örnek Console projelerimizi geliştirelim.

UserManagementService projesine ait App.Config dosyası;

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="UserManagementService.UserService">
        <endpoint address="" binding="wsHttpBinding" contract="ContractLibrary.IManagementContract" />
        <endpoint address="Mex" kind="mexEndpoint" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:3445/UserService" />
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>

wsHttpBinding bağlayıcı tipini kullanılan bu servisin üzerinden standart mexEndpoint yardımıyla WSDL çıktısıda(Metadata Publishing) sağlanmaktadır. Aslında bu bir zorunluluk değildir. Örnekte bu özelliği açmamın nedeni, istemci için gerekli olan proxy tipinin üretimini kolaylaştırmaktır; ki proxy tipini ürettikten sonra istemcinin config dosyasını da çok farklı bir şekilde değerlendirdiğimizi söyleyebilirim. Nitekim, istemci uygulama Downstream servislerine değil, Router servisine talepte bulunmalıdır.

UserManagementService projesine ait UserService sınıfı;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ContractLibrary;
using System.ServiceModel;

namespace UserManagementService
{
    // Senaryoya göre RegisterService sadece RegisterUser operasyonunu üstlenmek üzere tasarlanmıştır.
    class UserService
        : IManagementContract
    {
        public string RegisterUser(User newUser)
        {
            throw new NotImplementedException();
        }

        public string UpdateUserName(User oldUser, string newName)
        {
            Console.WriteLine("UpdateUserName metodu başlatıldı...");
            return String.Format("{0} isimli kullanıcı adı {1} olarak değiştirildi", oldUser.Name, newName);
        }

        public UserService()
        {
            Console.WriteLine("UserService nesnesi örneklendi");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ServiceHost host = new ServiceHost(typeof(UserService));
            host.Open();

            Console.WriteLine("UserService hazır...");
            Console.ReadLine();

            host.Close();
        }
    }
}

UserService sınıfı, ContractLibrary sınıf kütüphanesi içerisinde yer alan IManagementContract isimli servis sözleşmesini implemente etmektedir. Dikkat edileceği üzere RegisterUser metodu için bir implemantasyon gerçekleştirilmemiş ve hatta bilinçli olarak NotImplementedExcetion tipinden bir istisna(Exception) nesnesi fırlatılmıştır. Nitekim bu istisna mesajını hiç almayacağımızı garanti edebilirim. Wink Servisimizi tamamladıktan sonra çalıştırıp WSDL çıktısına baktığımızda aşağıdaki ekran görüntüsünde yer alan sonuçlar ile karşılaşırız.

Görüldüğü gibi kutucuk içine alınan bölgelerde, OperationContract niteliğinin Action özelliklerine atanan değerler yer almaktadır. Ben örnekte ilerlerken tam bu noktada bir istemci uygulama oluşturup Add Service Reference ile söz konusu WSDL çıktısının karşılığı olan Reference.cs dosyasının ürettirilmesini tercih ettim. Ancak sonrasında istemci uygulama tarafında sadece Reference.cs dosyasının içeriğini bıraktım. Yani config dosyasının içeriğini ve Service Reference klasörünün tamamını(Reference.cs hariç) sildim. Bu durumda istemci tarafının RegisterUser ve UpdateUserName metodlara çağrı yapabilmesi managed olarak mümkün hale geldi. Her neyse...Vakit kaybetmeden InternalService isimli Console uygulamamızı ve RegisterService isimli servisimizi tasarlayarak yolumuza devam edelim.

InternalService projesine ait App.config içeriği;

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="InternalService.RegisterService">
        <endpoint address="" binding="netTcpBinding" contract="ContractLibrary.IManagementContract" />
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:4001/RegisterService" />
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>

RegisterService isimli servis netTcpBinding bağlayıcı tipini kullanmakla birlikte aynen UserService' te olduğu gibi ContractLibrary sınıf kütüphanesi içerisindeki IManagementContract arayüzünü uygulamaktadır.

InternalService projesinde yer alan RegisterService sınıfı içeriği;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ContractLibrary;
using System.ServiceModel;

namespace InternalService
{
    // Senaryoya göre RegisterService sadece RegisterUser operasyonunu üstlenmek üzere tasarlanmıştır.
    class RegisterService
        :IManagementContract
    {
        public string RegisterUser(User newUser)
        {
            Console.WriteLine("RegisterUser metodu başlatıldı...");
            return String.Format("{0} isimli kullanıcı oluşturuldu...Id = {1}", newUser.Name, Guid.NewGuid().ToString());
        }

        public string UpdateUserName(User oldUser, string newName)
        {
            throw new NotImplementedException();
        }

        public RegisterService()
        {
            Console.WriteLine("RegisterService nesnesi örneklendi");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ServiceHost host = new ServiceHost(typeof(RegisterService));
            host.Open();

            Console.WriteLine("RegisterService hazır...");
            Console.ReadLine();

            host.Close();
        }
    }
}

Bu kez, RegisterUser metodu uygulanmış ancak UpdateUserName metodu içerisinden NotImplementedException istisna örneğinin fırlatılması sağlanmıştır.

Artık yönlendirme servisinin yazılmasına başlanabilir. Yönlendirme servisi için belkide en önemli nokta konfigurasyon içeriğidir. Bununla birlikte yönlendirme servisinin, Downstream servislerine ait referansları bilinçli olarak(Örneğin Add Service Reference yardımıyla) eklemesine de gerek yoktur. Sadece Client Endpoint tanımlamalarını yapması yeterlidir. Bir başka deyişle hangi servise, hangi mesajlaşma tipi ile erişeceğini bilmesi yeterlidir. İşte yazımızın kalbini oluşturan yere geldik...Laughing

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <routing routingTableName="RTable"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <client>
      <endpoint address="http://localhost:3445/UserService" binding="wsHttpBinding" contract="*" name="UserServiceEndpoint" />
      <endpoint address="net.tcp://localhost:4001/RegisterService" binding="netTcpBinding" contract="*" name="RegisterServiceEndpoint" />
    </client>
    <services>
      <service name="System.ServiceModel.Routing.RoutingService">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:6501/User/Management/RouterService"/>
          </baseAddresses>
        </host>
        <endpoint binding="basicHttpBinding" contract="System.ServiceModel.Routing.IRequestReplyRouter"/>
      </service>
    </services>
    <routing>
      <filters>
        <filter name="RegisterFilter" filterType="Action" filterData="http://www.azon.com/Registration"/>
        <filter name="UpdateUserFilter" filterType="Action" filterData="http://www.azon.com/UpdateUser"/>
      </filters>
      <routingTables>
        <table name="RTable">
          <entries>
            <add filterName="RegisterFilter" endpointName="RegisterServiceEndpoint"/>
            <add filterName="UpdateUserFilter" endpointName="UserServiceEndpoint"/>
          </entries>
        </table>
      </routingTables>
    </routing>
  </system.serviceModel>
</configuration>

Aslında bu konfigurasyon içeriğine bir kaç dakika gözle bakmakta ve kafamızda gerekli bağlantıları yaparak neyin ne olduğunu anlamaya çalışmakta yarar olduğu kanısındayım. İlk olarak bir routing davranışını belirlendiğini hemen görebiliriz. Bu davranışın routingTableName niteliğine atanan değer ile, hangi filtreleme tablosuna bakılacağı belirlenmektedir. Yönlendirme ile ilgili eşleştirmelerin tamamı, routing elementi içerisinde yapılır. Dikkat edileceği üzere iki adet filtre belirlenmiştir. Bunların her ikiside Action tipindedir. Yani filterData niteliğine atanan değer, gelen taleplerin SOAP Action kısımlarında aranır. Peki bulunduklarında ne olur? table elementi altında yer alan entries alt boğumunda, bir filtrenin belirttiği kritere uyulması halinde hangi istemci endPoint noktasının devreye sokulacağı belirtilmektedir. Çok doğal olarak çalışma zamanı, gelen Action bilgisinin eş düştüğü Endpoint' i bulduktan sonra, yönlendirmeyi hangi Downstream tipine doğru yapacağını kolayca bilecektir. Önemli olan noktalardan bir diğeride, servisin Endpoint bilgisidir. Burada görüleceği üzere daha önceki yazımızdan da hatırlayacağınız built-in routing sözleşmelerinden birisi seçilmiştir. Buna göre operasyonlarımızda request/reply modeli söz konusu olduğundan IRequestRepyleRouter servis sözleşmesinden yararlanılmaktadır. Artık yönlendirme servisinin kodlarını aşağıdaki gibi geliştirebiliriz.

using System;
using System.ServiceModel;
using System.ServiceModel.Routing;

namespace Router
{
    class Program
    {
        static void Main(string[] args)
        {
            ServiceHost host = new ServiceHost(typeof(RoutingService));
            host.Open();
            Console.WriteLine("Routing Service hazır...");
            Console.ReadLine();
            host.Close();

        }
    }
}

Tek dikkat edilmesi gereken, System.ServiceModel.Routing assembly' ından gelen RoutingService tipinin kullanılmış olmasıdır. Bu sayede çalışma zamanında yönlendirme işlemleri için gerekli alt yapının oluşturulması sağlanacaktır. Artık geriye istemci tarafını tamamlamaktan başka bir şey kalmamaktadır. Yupiiii!!! Cool İşte istemci tarafının App.config dosyası içeriği;

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
      <client>
        <endpoint address="http://localhost:6501/User/Management/RouterService"
                  contract="MemberManagementSpace.MemberManagementService"
                  binding="basicHttpBinding"/>
      </client>
    </system.serviceModel>
</configuration>

Görüldüğü üzere Endpoint tanımlamasında, RoutingService adresi belirlenmiş ve sözleşme tipi olarak daha önceden projeye eklediğimiz Reference.cs içerisine otomatik olarak üretilen MemberManagementSpace.MemberManagementService atanmıştır.

ve Main metodu kodları;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ClientApp.MemberManagementSpace;

namespace ClientApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("İstemci hazır...Başlamak için tuşa basınız");
            Console.ReadLine();

            MemberManagementServiceClient client = new MemberManagementServiceClient();

            User burak = new User { Name = "Burak Selim Şenyurt" };
           
            string registerResult=client.RegisterUser(burak);
            Console.WriteLine(registerResult);

            string updateResult=client.UpdateUserName(burak, "Burki");
            Console.WriteLine(updateResult);

            Console.ReadLine();
            client.Close();
        }
    }
}

Görüldüğü gibi istemci uygulama çok normal olarak servislerin uyguladığı ortak sözleşme şemasına sadık kalacak şekilde taleplerde bulunur. RegisterUser ve UpdateUserName metod çağırıları. Ancak hangisi olursa olsun, tüm bu operasyon çağrılarına ilişkin talepler Routing Servisine uğrayacaktır. Görüldüğü gibi istemcinin, Downstream servislerini bilmesine gerek yoktur. Zaten istemci uygulamada, söz konusu Downstream servislerine ait referansların ve config içeriğinin olmayışı bunu kanıtlamaktadır. Action talepleri, yönlendirme servisi tarafından değerlendirilip alt servislere iletildikten sonra, üretilen cevaplar yine Routing servisi üzerinden istemci tarafına gönderilir.

Artık örneği test etmeye ne dersiniz? İşte benim aldığım sonuçlar;

Umarım sizlerde benzer sonuçları elde edebilirsiniz. Herşey yolunda görünüyor. Smile

Örnekte özetle neler yaptık?

A   

DownStream Services

 

 

UserManagementService

 

 

Adres

http://localhost:3445/UserService

 

Binding

wsHttpBinding

 

Metadata Publishing

true (Sadece istemci için gerekli Reference içeriğinin kolay elde edilmesi için açıldı. Kapatılabilir)

 

Sözleşme

IManagementContract (ContractLibrary içerisindeki servis sözleşmesidir)

 

Action

http://www.azon.com/UpdateUser

 

InternalService

 

 

Adres

net.tcp://localhost:4001/RegisterService

 

Binding

netTcpBinding

 

Sözleşme

IManagementContract (ContractLibrary içerisindeki servis sözleşmesidir)

 

Action

http://www.azon.com/RegisterUser

B

Router Service

(DownStream servislerinin Endpoint bilgilerini barındırır ama bu servislere ait referanslar tiplerini içermez)

 

Service Endpoint Adresi

http://localhost:6501/User/Management/RouterService

 

Binding

basicHttpBinding

 

Client Endpoint Adresleri

http://localhost:3445/UserService
net.tcp://localhost:4001/RegisterService

C

Client Uygulama

 

 

 

Sadece Action bilgilerine sahip olan proxy tipini içerir.

 

 

Proxy tipinin üretimi için UserManagementService üzerinden açılan WSDL içeriğinden yararlanılmıştır. Ama bilindiği üzere ellede üretimi yapılıp oluşturulan sınıfın istemci tarafına verilmesi yolu da tercih edilebilir.

 

 

Downstream servislerin ait Endpoint bilgilerini içermez, bunun yerine Router servise ait Endpoint bilgisini içerir.

 

 

Proxy nesnesi üzerinde Register ve UpdateUser çağrılarını, Router servise doğru gerçekleştirir.

Elbetteki bu örnekte en kritik noktalardan birisi filtrelemelerdir. Biz örneğimizde Action içeriğine bakarak bir filtreleme işlemi gerçekleştirdik. Ancak XPath kullanımı gibi senaryolarında mümkün olduğundan bahsetmiştik. Yani talebe ait içerik üzerinden XPath sorguları ile koşula uyan durumlarıda yönlendirme işlemlerinde kullanabiliriz. Bu gibi ince noktalarıda ilerleyen yazılarımızda sizlere aktarmaya çalışıyor olacağım. Şimdilik bu kadar. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Router Project.rar (104,09 kb)

Tags:   ,
Categories:   WCF 4.0 Beta 1
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (1) | Comment RSSRSS comment feed
Bookmark and Share

WCF 4.0 Yenilikleri - Routing Service Geliştirmek - Giriş [Beta 1]

Pazartesi, 24 Ağustos 2009 18:15 by bsenyurt

Merhaba Arkadaşlar,

Servis Yönelimli Mimari(Service Oriented Architecture) çözümlerinde zaman zaman yönlendirme amaçlı servislerin yazılması gerekmektedir(Router Service). Bu servislerin genel kullanım amacı çoğunlukla, istemcilerden gelecek olan talepleri değerlendirip asıl işi yapacak olan servislere devretmek ile ilişkilidir. Ancak, gelen taleplere ait içeriğinin(Message Content, Header vb...) filtrelenerek ele alınması gibi ileri seviye teknikleride içerebilir. Yönlendirme işlemleri için kullanılan pek çok donanımsal cihaz ve hatta yazılım zaten mevcuttur. Bu nedenle öncelikli olarak yönlendirme servislerine neden ihtiyaç duyulabileceğini kavramakta yarar vardır.

WCF tarafında Routing Service geliştirilmesi hangi durumlarda tercih edilir?

  • Özel bir Load Balancing yapısı için(genellikle donanımsal veya yazılımsal yük dengeleyici sistemlerin yetersiz kaldığı yada özelleştirilmek istendiğin durumlarda).
  • İstemciden gelen mesajın içeriğine göre servis yönlendirilmesi yapılmak istendiğinde(Content Based).
  • Önceliğe göre servis yönlendirmesi yapılmak istendiğinde(Priority Based)
  • Versiyonlama senaryolarında.
  • İstemciler ve yönlendirilen servisler arasında bir güvenlik geçidi kurulmak istendiğinde(ki bu geçit genellikle DMZ-demilitarized zone arkasında asıl servislere olan akışı güvenlik kontrolüne alır).

WCF 3.X tarafında yönlendirme servisi geliştirebilmek için belirli kodlama eforu sarfetmek gerekirken(ki bu konuda daha önceden yayınladığım bir yazımı incleyebilirsiniz), WCF 4.0 tarafında bu işlemler belirli tipler ve konfigurasyon özellikleri ile one-way,two-way ve duplex iletişim seviyesinde olduçka kolaylaştırılmıştır. WCF 4.0 tarafında System.ServiceModel.Routing assembly' ı içerisinde yine aynı adlı isim alanında yer alan RoutingService isimli sınıf, söz konusu yönlendirme servisi için gerekli çalışma zamanı ortamının hazırlanmasını sağlamakta ve ayrıca istemci taleplerinin filtrelenerek uygun alt servislere aktarılmasında önemli bir rol oynamaktadır.

Not : .Net Framework Beta 1 sürümünde RoutingService olarak geçen yönlendirme sınıfı çeşitli internet kaynaklarında(örneğin Michele Leroux Bustamante' nin bloğunda) RouterService olarak geçmektedir. Dolayısıyla final sürümde servisin adında farklılıklar olabilir. Örneklerimizi .Net Framework Beta 1 üzerinde geliştirdiğimizi hatırlatmak isterim.

Object Browser yardımıyla elde edilen yukarıdaki görüntünden farkedeceğiniz gibi, RoutingService sınıfı 4 farklı servis sözleşmesini(Service Contract) uygulamaktadır. Bu anlamda IDuplexSessionRouter, IRequestReplyRouter, ISimplexDatagramRouter ve ISimplexSessionRouter gibi önemli arayüzleri(Interfaces) implemente etmektedir. Dolayısıyla gerekli MEP(Message Exchange Patterns) modellerinin tümü desteklenmektedir. Buna göre servisin one-way, two-way veya duplex temelli isteklere göre çalışabilmesi sağlanmaktadır. RoutingService, ServiceHost nesne örneklemesi sırasında parametre olarak kullanıldığından, normal WCF host kurallarına tabidir. Yani IIS veya Self modellerde host edilebilir. Genellikle bir Windows Service, IIS yada duruma göre WAS üzerinden host edilmesi tercih edilmektedir. Genel olarak yönlendirme modelini aşağıdaki şekilde görüldüğü gibi özetleyebiliriz.

İstemciden(Client) gelen talepler yönlendirme servisine(Router Service) ulaştığında belirli filtrelerden geçmekte ve bu filtrelere göre belirlenmiş alt servis noktalarına aktarılmaktadırlar. Görüldüğü üzere önemli olan noktalardan biriside filtrelemedir. Filtreleme tablosu(Filter Table) ve içerdiği filtreler(Filters) config dosyası içerisinde depolanır. Bu filtrelerde;

  • XPath gibi sorgular kullanılabilir. Sonuç itibariyle mesaj içeriğinin XML tabanlı olduğu düşünüldüğünde bu son derece doğaldır. 
  • Sadece mesajın Header veya Soap Action kısımlarına bakılabilir.
  • Birden fazla filtrenin mantıksal ve(And) işlemine tabi tutulması sağlanabilir. Nitekim, daha önceden tanımlanmış iki farklı filtreye olan uygunluğun bir arada sağlanması istenebilir.
  • İstenirse programlanmış bileşeneler ile özel filtrelemeler yapılabilir.

Filtrelemeler konfigurasyon içerikli olarak tutulduğundan, geliştiricilerin(Developers) söz konusu filtreleme davranışlarını koda bulaşmadan değiştirebilmesi, güncellemesi veya yenilerini eklemesi mümkündür.

Yönlendirme servisi çok doğal olarak istemciden gelen talepleri belirli kurallara göre işletmektedir. Yönlendirme hizmetinin istemcilere sunacağı bir Endpoint olması kaçınılmazdır. Tabi istenirse birden fazla endpoint sunabilir(Örneğin built-in gelen ISimplexDatagramRouter, IRequestReplyRouter gibi servis sözleşmelerinden yararlanarak...) Diğer yandan istemcilerden gelen mesaj filtrelerde yer alan koşullardan birisine uyduğunda, ilişkili olan alt servise yönlendirilmelidir. Buna göre yönlendirme servisi, istemciden gelen ve filtreden geçen mesajları uygun alt servislere iletmesi gerektiğinden aynı zamanda bir istemci olarak düşünülmelidir. Dolayısıyla şekildende görüleceği gibi üzerinde her alt servis için en az bir Endpoint bulunmaktadır. Alt servisler istemcinin asıl işini yapmakla yükümlü olmakla birlikte, aynı sunucuda veya farklı sunucular üzerinde konuşlandırılmış olabilirler. Bu nedenle, yönlendirme servisi arkasında Web Farm gibi yapılara sıklıkla rastlandığını söyleyebiliriz.

Peki yönlendirme servisinin içerisinde yer aldığı basit bir sistemi nasıl tasarlayabiliriz? Burada belkide en kritik konu filtrelemelerdir. Özellikle filtrelerde gelen mesaj içeriği üzerinde XPath ile sorgular atılması önemli olan ve zor noktalardandır. Bu gibi konuları bir sonraki yazımızda ele almaya çalışıyor olacağız. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Tags:   ,
Categories:   WCF 4.0 Beta 1
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WCF 4.0 Yenilikleri - Managed WS-Discovery [Beta 1]

Cumartesi, 22 Ağustos 2009 08:13 by bsenyurt

Merhaba Arkadaşlar,

Yandaki resimdeki gibi çok çok uzun bir yolun başında ve ulaşmanız gereken yere yüzlerce kilometre mesafede olduğunuzu hayal edin. Sabırlı bir şekilde bu yolu gidebilmek için çok iyi bir disipline sahip olmanız gerekir. Yazılım geliştirme denilen büyük okyanusun içerisinde de bu tip yollar ile karşılaşmaz mıyız? Hemde sıklıkla karşılaşırız. Yılmadan yola devam edenler, nihayetinde mutlu sona ulaşırlar.

Ama belkide ulaşmazlar. Bu tamamen zamanın o andaki çevresel koşullarına bağlı olarak değişir. İşte bu yazımızda hakikaten sadece geliştirme aşaması dahi insanı çileden çıkarabilen zahmetli bir yola baş koyuyor olacağız. Hedefimiz, WS-Discovery tabanlı WCF sistemlerinde Managed Discovery modelini uygulayabilmek.

Konuyu MSDN ve diğer internet kaynaklarından araştırırken Ad Hoc modeli ile ilişkili tonlarca yazı olduğunu ama Managed tarafa pek kimsenin bulaşmak istemdiğini farkettim. Nedenini anlamam yaklaşık olarak 2,5 Litre kahve içmeme ve uykusuz bir Cumartesi gecesi geçirmeme neden oldu. Ama sonunda deydi. Aslında teorik olarak Managed modelin açıklaması son derece basit. İstemcilerin kullanmak isteyipte, farklı zamanlarda farklı lokasyonlardan ağa/ağlara dahil olan veya ayrılan servislerin keşfedilmesi görevi, istemci uygulamalardan alınıp istemci ile söz konusu servisler arasındaki başka bir Proxy servisine verilmektedir. Proxy servisi aslında hem announcement mesajları hemde istemcilerden gelecek olan Probe taleplerini dinlemektedir. Announcement mesajların dinlenmesi, online veya offline olan servislerin, Proxy servisi üzerinde bir saklama alanında tutulmasınıda gerektirir. Nitekim proxy servisi, ağa bağlı olan veya ayrılan tüm servislere ait ortak bir listeyi barındırıp istemci taleplerini bu listedeki durumlara göre karşılamalıdır. Diğer taraftan kendiside, istemciler tarafından keşfedilebilir olmalıdır. Bu nedenle tüm istemciler için ortak bir Discovery Endpoint noktasına sahip olmalıdır. Proxy servisini bu nedenlerden dolayı sürekli online halde kalan bir hizmet olarak düşünebiliriz. Online kalması önemlidir; çünkü online olduğu sürece, ağı dinleyerek katılan servisleri listesine alabilir ve istemcilerden gelen Probe veya Resolve gibi çağrılara cevap verebilir. Peki işi zorlayan nokta nedir?

Herşeyden önce Proxy servisinin, çalışma zamanındaki hareketliliği normal bir servis gibi değildir. Yani standart ServiceHost tipi tek başına yeterli değildir. Bu nedenle, DiscoveryProxyBase isimli abstract sınıftan bir türetme işlemi yapılarak üretilen bir servis tipi kullanılmalıdır. Çok doğal olarak bu base içerisinden override edilmesi gereken bir takım üyelerde gelmektedir. Ayrıca, Proxy servisi tek bir örnek olarak(Single Instance) oluşturulmalı fakat eş zamanlı olarak gelecek istemci ve announcement taleplerine de cevap verebilmelidir. Bu noktada IAsyncResult arayüzünüde içeren asenkron modeli uygulayıp Thread yönetimini üstlenen yardımcı bir takım tipler kullanması gerekmektedir. Zaten işin zorlaştığı nokta burasıdır. Neyseki MSDN' de bu konu ile ilişkili olan örnekte, Asenkron desenin uygulanması için standart olarak sunulan sınıflar hazırdır. Dolayısıyla bu yapının aynısı kullanılarak gerekli geliştirmeler biraz daha kolayca yapılabilir. Biz örneğimizde sadece asenkron iletişimi ele alan tipleri alırken, DiscoveryProxyBase tabanlı türetmeyi kendimize göre düzenleyeceğiz.

Öyleyse başlamaya ne dersiniz. Herkes sıcak kahvesini veya çayını yada yazın şu sıcak günlerinde gidecek serin bir içeceğini alsın ve benimle birlikte adım adım ilerlemeye gayret etsin. Başlamadan önce hedef modelimizin ne olduğunu kabaca aktarmak isterim. Aşağıdaki şekilde görülen senaryoyu ele almaya çalışacağız.

Şeklimizden anlaşılacağı üzere Discovery Proxy Servisimiz, Service X ve Service Y' nin online/offline olma durumlarını izlemektedir. Ayrıca istemci uygulama/uygulamalar aramak istediği servise ait talebi doğrudan Discovery Proxy servisine göndermektedir. İşte tam olarak gerçekleştirmek istediğimiz test senaryosu budur. İşe ilk olarak Discovery Proxy Servisinin yazımı ile başlayabiliriz. Dana öncedende belirttiğimiz gibi, bu servis içerisinde asenkron bir yapı kullanılması söz konusu olduğundan işimiz pek kolay değil. Ben sadece Proxy isimli DiscoveryProxyBase abstract sınıfından türeyen tipin uygulanışını burada göstermek istiyorum. Örnek uygulama kodlarını indirdiğinizde OnResolveAsyncResult ve OnFindAsyncResult gibi tiplerin detaylarınıda bulabilirsiniz ki bunlarda standart olarak kullanılan tiplerdir ve MSDN tarafından yayınlanmıştır. Discovery Proxy servisinin içerisindeki sınıf modeli en basit haliyle aşağıdaki şekilde olduğu gibidir.

Burada bizi daha çok ilgilendiren kısım DiscoveryProxyBase türevli olan Proxy sınıfının kodlamasıdır. İşte kodlarımız;

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Discovery;

namespace DiscoveryProxyService
{
    // T anından sadece tek bir Proxy servis nesne örneğinin olabileceğini ve aynı andan birden fazla çağrıyı karşılayabilecek şekilde kullanılabileceğini belirtiyoruz
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    class Proxy
        : DiscoveryProxyBase
    {
        // Endpoint ve metadata bilgilerini tutacağımız bir koleksiyon tanımlanır
        Dictionary<EndpointAddress, EndpointDiscoveryMetadata> _serviceList = null;
        // _serviceList' in tutarlığını(Consistency) sağlamak için tanımlanan yardımcı değişkendir
        object _syncLock = null;

        public Proxy()
        {
            _serviceList = new Dictionary<EndpointAddress, EndpointDiscoveryMetadata>();
            _syncLock = new object();
        }

        #region Yardımcı Metodlar

        // Online olan bir servis bilgisini listeye eklemek için kullanılır
        // Proxy her bir announce mesajı aldığını çalıştırılacaktır.
        void AddService(EndpointDiscoveryMetadata metadata)
        {
            // eş zamanlı thread senkronizasyonunu sağlamak için lock kullanılmıştır
            lock (_syncLock)
            {
                // Address key değerine sahip bir value var ise güncelleme yoksa ekleme yapar.
                _serviceList[metadata.Address] = metadata;
                Console.WriteLine("{0} adresli endpoint eklendi",metadata.Address.ToString());
            }
        }
        // Offline olan bir servisi listeden çıkartmak için kullanılır
        void RemoveService(EndpointDiscoveryMetadata metadata)
        {
            // eş zamanlı thread senkronizasyonunu sağlamak için lock kullanılmıştır
            lock (_syncLock)
            {
                _serviceList.Remove(metadata.Address);
                Console.WriteLine("{0} adresli endpoint çıktı", metadata.Address.ToString());
            }
        }

        // Bu metod ve aşırı yüklenmiş(overload) versiyonu Resolve ve Probe mesajlarında kullanılır
        // FindCriteria tipinden olan parametre ile gelen kriterler uyan servisleri, listeden çekmektedir
        List<EndpointDiscoveryMetadata> MatchFromServiceList(FindCriteria findCriteria)
        {
            List<EndpointDiscoveryMetadata> result = null;

            lock (_syncLock)
            {
                result = (from epMetadata in _serviceList.Values
                          where findCriteria.IsMatch(epMetadata)
                          select epMetadata).ToList<EndpointDiscoveryMetadata>();
            }

            return result;
        }

        // ResolveCriteria tipinden gelen parametrenin Address bilgisine eş düşen servisi listeden bulup Discovery Metadata bilgisini döndürür
        EndpointDiscoveryMetadata MatchFromServiceList(ResolveCriteria rCriteria)
        {
            EndpointDiscoveryMetadata result = null;

            lock (_syncLock)
            {
                result = (from epMetadata in _serviceList.Values
                          where epMetadata.Address == rCriteria.Address
                          select epMetadata).Single();
            }

            return result;
        }

        #endregion

        #region Override edilen metodlar

        // Online Announcement mesajı alındığından devreye giren metoddur
        protected override IAsyncResult OnBeginOnlineAnnouncement(AnnouncementMessage announcementMessage, AsyncCallback callback, object state)
        {
            AddService(announcementMessage.EndpointDiscoveryMetadata);
            return base.OnBeginOnlineAnnouncement(announcementMessage, callback, state);
        }

        // Online announcement mesajının işlenmesi bittiğinde devreye girer
        protected override void OnEndOnlineAnnouncement(IAsyncResult result)
        {
            base.OnEndOnlineAnnouncement(result);
        }

        // Offline announcement mesajı geldiğinde devreye giren metoddur
        protected override IAsyncResult OnBeginOfflineAnnouncement(AnnouncementMessage announcementMessage, AsyncCallback callback, object state)
        {
            RemoveService(announcementMessage.EndpointDiscoveryMetadata);
            return base.OnBeginOfflineAnnouncement(announcementMessage, callback, state);
        }

        // Offline announcement mesajının işlenmesi bittiğinde devreye giren metoddur
        protected override void OnEndOfflineAnnouncement(IAsyncResult result)
        {
            base.OnEndOfflineAnnouncement(result);
        }

        // Bir Find talebi geldiğinde devreye giren metoddur
        protected override IAsyncResult OnBeginFind(FindRequest findRequest, AsyncCallback callback, object state)
        {
            return new OnFindAsyncResult(
                MatchFromServiceList(findRequest.Criteria)
                , callback
                , state);
        }

        // Find talebinin işlenmesi sona erdiğinde devreye giren metoddur
        protected override Collection<EndpointDiscoveryMetadata> OnEndFind(IAsyncResult result)
        {
            return new Collection<EndpointDiscoveryMetadata>(OnFindAsyncResult.End(result));
        }

        // Resolve mesajı geldiğinde devreye giren metoddur
        protected override IAsyncResult OnBeginResolve(ResolveRequest resolveRequest, AsyncCallback callback, object state)
        {
            return new OnResolveAsyncResult(MatchFromServiceList(resolveRequest.Criteria)
                , callback
                , state);
        }

        // Resolve mesajının işlenmesi bittiğinde devreye giren metoddur
        protected override EndpointDiscoveryMetadata OnEndResolve(IAsyncResult result)
        {
            return OnResolveAsyncResult.End(result);
        }

        #endregion
    }
}

Sizi bu kod parçası ile bir süre yanlız bırakmak isterim Sealed Aslında sınıfımızın görevi basittir. Çevre ağlar üzerinde announcement mesajı yayınlayarak online veya offline olduğunu bildiren servisleri tutmakta ve buna ek olarak, istemciden gelen arama kriterlerine uygun olanlarını yine istemci tarafına yönlendirmektedir. Sınıfımız, yardımcı metodların yanı sıra DiscoveryProxyBase tipinden gelen bazı sanal metodlarıda(Virtual Method) ezmektedir. Özellikle eş zamanlı isteklerde oluşabilecek senkronizasyon sorunlarını aşmak için basit lock tekniğinden yararlanılmaktadır. Proxy servisini geliştirmek tek başına yeterli değildir. Bu servisin bir uygulama tarafından host edilmesi gerekmektedir. Bu anlamda basit bir Console uygulaması aşağıdaki kodlar ile tasarlanabilir.

using System;
using System.ServiceModel;
using System.ServiceModel.Discovery;

namespace DiscoveryProxyService
{
    class Program
    {
        static void Main(string[] args)
        {
            // ServiceHost nesnesi DiscoveryProxyBase türevli Proxy tipi ile oluşturulur.
            ServiceHost host = new ServiceHost(new Proxy());

            // İstemcilerin Probe mesajları için bir DiscoveryEndpoint noktası tanımlanır
            DiscoveryEndpoint discoEndpoint = new DiscoveryEndpoint(
                new NetTcpBinding()
                , new EndpointAddress("net.tcp://localhost:4034/Probe"));
            discoEndpoint.IsSystemEndpoint = false;
            // DiscoveryEndpoint host' a eklenir
            host.AddServiceEndpoint(discoEndpoint);

            // Online veya Offline olan servislerin kendilerini Proxy servisine bildirebilmeleri amacıyla bir AnnouncementEndpoint noktası oluşturulur ve servise ilave edilir
            AnnouncementEndpoint announceEndpoint = new AnnouncementEndpoint(
                new NetTcpBinding()
                , new EndpointAddress("net.tcp://localhost:4044/Announcement"));
            host.AddServiceEndpoint(announceEndpoint);

            host.Open();
            Console.WriteLine("Managed Discovery Servis Durumu : ");
            Console.ReadLine();
            host.Close();
        }
    }
}

Proxy servisini bu şekilde host ettikten sonra, kendisine bildirimde bulunabilecek bir servisin nasıl tasarlanabileceğine de bakmamız yerinde olacaktır. Bu anlamda örneğimizde ServiceX ve ServiceY isimli iki farklı servis uygulaması bulunmaktadır. Bu servislerin en önemli görevlerinden biriside, ağa dahil olmaları veya ayrılmaları halinde bu durumlarını Proxy servisine bildirmeleridir. Her iki servis arasındaki fark ise tabiki sundukları hizmettir.

ServiceX içeriği;

using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Discovery;

namespace ServiceX
{
    [ServiceContract]
    interface ICalculus
    {
        [OperationContract]
        double Sum(double x, double y);
    }

    class CalculusService
        : ICalculus
    {
        public double Sum(double x, double y)
        {
            return x + y;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {           
            ServiceHost host = new ServiceHost(
                typeof(CalculusService)
                , new Uri("net.tcp://localhost:9002/CalculusService/" + Guid.NewGuid().ToString()));
            host.AddServiceEndpoint(
                typeof(ICalculus), new NetTcpBinding(), string.Empty);
            // Bir announcement endpoint noktası oluşturulur ve proxy servisine bu sayede bildirim yapılması sağlanır
            AnnouncementEndpoint announcementEndpoint = new AnnouncementEndpoint(
                new NetTcpBinding()
                , new EndpointAddress("net.tcp://localhost:4044/Announcement"));
            // Servisin keşfedilebilir olması sağlanır
            ServiceDiscoveryBehavior serviceDiscoveryBehavior = new ServiceDiscoveryBehavior();
            serviceDiscoveryBehavior.AnnouncementEndpoints.Add(announcementEndpoint);
            host.Description.Behaviors.Add(serviceDiscoveryBehavior);
           
            host.Open();
            Console.WriteLine("Service X açıldı");
            Console.ReadLine();
            host.Close();
        }
    }
}

ServiceY içeriği;

using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Discovery;

namespace ServiceY
{
    [ServiceContract]
    interface IAdventure
    {
        [OperationContract]
        double FindExpensiveProduct(int categoryId);
    }

    class AdventureService
        : IAdventure
    {
        public double FindExpensiveProduct(int categoryId)
        {
            return 1000;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ServiceHost host = new ServiceHost(
                typeof(AdventureService)
                , new Uri("http://localhost:10005/Adventure/ProductService/" + Guid.NewGuid().ToString()));
            host.AddServiceEndpoint(
                typeof(IAdventure), new WSHttpBinding(), string.Empty);
            // Bir announcement endpoint noktası oluşturulur ve proxy servisine bu sayede bildirim yapılması sağlanır
            AnnouncementEndpoint announcementEndpoint = new AnnouncementEndpoint(
                new NetTcpBinding()
                , new EndpointAddress("net.tcp://localhost:4044/Announcement"));
            ServiceDiscoveryBehavior serviceDiscoveryBehavior = new ServiceDiscoveryBehavior();
            // Servisin keşfedilebilir olması sağlanır
            serviceDiscoveryBehavior.AnnouncementEndpoints.Add(announcementEndpoint);
            host.Description.Behaviors.Add(serviceDiscoveryBehavior);
            host.Open();
            Console.WriteLine("Service Y açıldı");
            Console.ReadLine();
            host.Close();
        }
    }
}

Piuuuuuvvvvv!!! Laughing İşimiz bitti diye düşünebilirsiniz. Ama hayır... Birde istemcilerin nasıl yazılabileceğine bakmamız gerekiyor. İstemci tarafında tabiki olmassa olmazlardan biriside, kullanmak istediği servislere ait proxy referanslarına sahip olmaları gerekliliğidir. Bunu göz önüne alarak ilerlediğimizi düşünürsek istemci tarafında da aşağıdaki gibi bir kodlama yapmamız yeterlidir.

using System;
using System.ServiceModel;
using System.ServiceModel.Discovery;

namespace ClientApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Teste başlamak için bir tuşa basınız");
            Console.ReadLine();

            // Proxy servisini keşfedebilmek için bir DiscoveryEndpoint oluşturulur
            DiscoveryEndpoint disEndpoint = new DiscoveryEndpoint(
                new NetTcpBinding()
                , new EndpointAddress("net.tcp://localhost:4034/Probe")
                );
            // İstemci tarafının kullanılabilir servisleri keşfetmesini kolaylaştıran DiscovertClient tipine ait nesne örneği DiscoveryEndpoint ile oluşturulur
            DiscoveryClient disClient = new DiscoveryClient(disEndpoint);
            disClient.Open();
            // Bir arama kriteri uygulanır ve dönen cevaptan kullanılabilir servis adresi tedarik edilir
            FindResponse response = disClient.Find(new FindCriteria(typeof(ICalculus)));
            EndpointAddress epAddress=response.Endpoints[0].Address;

            // Eğer arama kriterine uygun servisler bulunmuşsa
            if (response.Endpoints.Count > 0)
            {
                // İlkinin adres bilgisini al
                EndpointAddress epAddress = response.Endpoints[0].Address;

                Console.WriteLine("{0} adresi bulundu", epAddress.ToString());

                // İstemci için gerekli proxy referansı örneklenir ve Probe mesajı ile bulunan Endpoint adresi kullanılır.
                CalculusClient client = new CalculusClient(new NetTcpBinding(), epAddress);

                // Servis operasyonu çağrılır
                Console.WriteLine("{0}+{1}={2}", 3, 4, client.Sum(3, 4).ToString());
                Console.ReadLine();
            }

            disClient.Close();
        }
    }
}

Nihayet test yapabilmek için gerekli ortamı hazırladığımızı ifade edebilirim.Cool İlk olarak Discovery Proxy servisinin, sonrasında istemcinin kullanmak istediği servislerin ayağa kaldırılması gerekir. Son olarak istemci uygulamanın çalıştırılması ve test edilmesi yeterlidir. Yapılan ilk testler sonucunda aşağıdaki sonuçlar elde edilmiştir.

Görüldüğü gibi, ServiceX ve ServiceY isimli servislerin açılmaları ve kapatılmaları, Managed Discovery Proxy servisi tarafından tespit edilebilmiştir. ServiceX' in online olduğu zaman dilimi içerisinde, istemciden gelen talep başarılı bir şekilde karşılanabilmiştir. Farklı bir testide şu şekilde yapmak gerekir. İstemci uygulama, ServiceX için talepte bulunmadan önce, ServiceX kapatılır. Wink Bu durumda istemcinin aradığı kritere uyan bir servis ayakta olmadığı için, istemcinin bir işlem yapamıyor olması gerekir. Olayı istisna ile sonlandırmayı engellemenin yolu ise if ile yapılan Count kontrolüdür. Bu tip bir testin sonucunda çalışma zamanı görüntüsü aşağıdaki gibi olacaktır.

Her ne kadar sadece iki çalışma zamanı testi yapılmış olsada, örneğin iyi bir şekilde değerlendirilmesi ve olası tüm hataların önüne geçilmesi gerekmektedir. Söz gelimi, Proxy servisinin kapatılmasından sonra, kendisine bağlı olan başka servislerin kapatılmaya çalışılması esnasında, söz konusu servislere ait ortamlarda çalışma zamanı istisnaları(Runtime Exception) oluşması kaçınılmazdır. Bu gibi noktaları dikkat almanızı ve geliştirmenizi buna göre yapmanızı öneririm.

Nihayet, uzun saatlerin, gidilen kilometrelerce yolun sonunda gece bastırmış ve şehrin ışıkları görünmüştür. Hepimiz zaman zaman yazılım alanında bir konuyu öğrenirken bu tip zorlu yollardan geçmek zorunda kalabiliriz. Ancak sabırlı olanlarımız, yolun sonuna kadar gitmekten çekinmeyecek ve ödül olarak şehrin parlak ışıkları ile karşılanacaktır. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

ManagedWSDiscovery.rar (96,14 kb)

Tags:   ,
Categories:   WCF 4.0 Beta 1
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WCF 4.0 Yenilikleri - Announcement Kullanımı [Beta 1]

Cuma, 21 Ağustos 2009 13:00 by bsenyurt

Merhaba Arkadaşlar,

WCF 4.0 tarafında WS-Discovery tabanlı olarak gerçekleştirilen uygulamalarda önem arz eden noktalardan biriside, servislerin online veya offline olma durumlarını, bulundukları ağ üzerindeki dinleyicilere(Listeners) bildirmeleridir(Announce). Bildiri şeklinde yapılan yayınlamalar aslında istemcinin ağ üzerine yaydığı multicast mesajların yoğunluğunu azaltmak gibi olumlu bir etkiye de sahiptir. Şimdi bu bildirim işlemlerinin nasıl yapılacağını incelemeye çalışalım. Ad Hoc modelinin uygulanması ile ileişkili yazımızdaki örneğimizi bu amaçla devam ettirebiliriz. Servis tarafında konfigurasyon dosyasında sadece aşağıdaki eklemeleri yapmamız yeterli olacaktır.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceDiscovery>
            <announcementEndpoints>
              <endpoint kind="udpAnnouncementEndpoint"/>
            </announcementEndpoints>
          </serviceDiscovery>
          <serviceMetadata/>
        </behavior>
      </serviceBehaviors>
    </behaviors>   
    <services>
      <service name="ServerApp.CalculusService">
        <endpoint address="" binding="basicHttpBinding" contract="ServerApp.ICalculus"/>
        <endpoint address="Mex" kind="mexEndpoint" />
        <endpoint name="udpDiscovery" kind="udpDiscoveryEndpoint" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

Konfigurasyon dosyasında, Servis tarafına yeni bir davranış(Service Behavior) eklenmiş ve bu davranış için udpAnnouncementEndpoint tipinden bir Endpoint kullanılacağı belirtmiştir. Bu endpoint tipi çalışma zamanında, servisin ağ üzerindeki dinleyicilere mesaj gönderebilmesi için gerekli alt yapının oluşturulmasını sağlamaktadır. Bir bakşa deyişle işimizi oldukça kolaylaştırmaktadır Wink Ancak istemci tarafında biraz kod eforu sarfedilmelidir. İstemci tarafı bir dinleyici olarak, servisin ortama gönderdiği "ben geldim" veya "ben gittim" tadındaki mesajları yakaladığında devreye girecek olan iki olay metodunu ele alabilmelidir. Tabi bunlardan daha önemlisi çalışma zamanı için gerekli alt yapı hazırlıklarınıda gerçekleştirmelidir. Şimdi istemci tarafındaki kodlarımızı aşağıdaki gibi geliştirdiğimizi düşünelim.

using System;
using System.ServiceModel;
using System.ServiceModel.Discovery;

namespace ClientV2
{
    class Program
    {
        static void Main(string[] args)
        {
            // Servisin online/offline olma durumlarında yaptığı bildirimleri yakalayan nesne örneği
            AnnouncementService aService = new AnnouncementService();

            // Servis online olduğunda devreye giren olay metodu
            aService.OnlineAnnouncementReceived += delegate(object sender, AnnouncementEventArgs e)
            {   
                // Online olan servisin etkinleştirilmiş Endpoint noktalarının Address bilgileri ve o anki mesajın numarası yazdırılır.
                Console.WriteLine("\nMessage No : {0}\n\t{1} adresli EndPoint ONLINE oldu",
                    e.AnnouncementMessage.MessageSequence.MessageNumber,
                    e.AnnouncementMessage.EndpointDiscoveryMetadata.Address.ToString());

                // Etkinleşen Endpoint' ler üzerinden sunulan servis sözleşmeleri yazdırılır
                Console.WriteLine("Contracts ");
                foreach (var contractType in e.AnnouncementMessage.EndpointDiscoveryMetadata.ContractTypeNames)
                {
                    Console.WriteLine("\t{0}",contractType.Name);
                }
            };

            // Servis offline olduğunda devreye giren olay metodu
            aService.OfflineAnnouncementReceived += delegate(object sender, AnnouncementEventArgs e)
            {
                // Kapatılan servis üzerindeki Endpoint bilgileri yazdırılır.
                Console.WriteLine("\nMessage No : {0}\n\t{1} adresli EndPoint OFFLINE oldu",
                                    e.AnnouncementMessage.MessageSequence.MessageNumber,
                                    e.AnnouncementMessage.EndpointDiscoveryMetadata.Address.ToString());
            };

            // AnnouncementService örneği kullanılarak bir ServiceHost nesnesi örneklenir
            ServiceHost host = new ServiceHost(aService);
            // Service yeni bir UdpAnnouncementEndpoint eklenir
            host.AddServiceEndpoint(new UdpAnnouncementEndpoint());
            // Dinleme işlemleri için servis açılır
            host.Open();

            Console.WriteLine("Dinlemedeyiz...Çıkmak için bir tuşa basınız");
            Console.ReadLine();

            // Servis kapatılır
            host.Close();
        }
    }
}

Her ne kadar istemci tarafını geliştiriyor olsakta pek istemci tarzında olmadığını eminimki farketmişsinizdir.Wink Nitekim istemci tarafında ServiceHost nesnesi örneklenmekte ve kullanılmaktadır. Aslında bu son derece doğaldır. Nitekim online veya offline olan servislerin, istemciler üzerinde tetikleyebildiği iki olay söz konusudur. Buda istemcinin bir anlamda servis gibide davranış gösterebilmesini gerektirmektedir. (Normal şartlar altında servisin, istemciler üzerinde olay tetikletmesi gerektiği durumlarda özellikle .Net Remoting gibi modellerde çok kafa karıştırıcı kodlamalar yapılması gerektiğini hatırlatmak isterim.Undecided) WCF 4.0 tarafında ise tek yapmamız gereken bu iş yükünü AnnouncementService tipine atmaktır. Dikkat edileceği üzere ServiceHost nesnesi örneklenirken parametre olarak AnnouncementService referansı verilmektedir. Sonrasında ise ServiceHost nesnesine, UpdAnnouncementEndpoint tipinden bir Endpoint ilave edilmiştir. Örnekle ilişkili ilginç noktalardan biriside istemci tarafında App.config dosyasının bulunmayışıdır.(Örnekten bu dosyası bilinçli bir şekilde çıkarttığımı belirtmek isterim) İstemci uygulama dinlemede kaldığı süre boyunca, online veya offline olan tüm endPoint noktalarına ait announce mesajlarını yakalayabilmektedir. Bunlara ek olarak, istemcinin belirli bir servise odaklanmadığı da görülmektedir. Yerel ağ üzerindeki herhangibir servisten gelen announce mesajlarını dinleyebilmektedir. Modeli test etmek için istemci uygulama açık iken, bir veya daha fazla servisin(tabiki bunların WS-Discovery tabanlı olarak geliştirilmiş olma şartları vardır) kapatılıp açılması yeterlidir. Ben test sırasında aşağıdaki ekran görüntüsünde yer alan sonuçları aldım.

Görüldüğü üzere servisin bir kaç kere açılması ve kapatılmasının ardından istemci tarafındaki OnlineAnnouncementReceived  ve OfflineAnnouncementReceived olayları tetiklenmiş ve gerekli bildirimler yakalanmıştır. Artık bu noktadan sonra istemcinin sadece online olan Endpoint noktalarına göre proxy nesnelerini oluşturması ve kullanması yeterli olacaktır.

Bir sonraki yazımızda Ad Hoc modelini terkedip, Managed Discovery modelini incelemeye çalışacağız. Bildiğiniz üzere Ad Hoc modelde yerel/alt ağlar söz konusudur ve ağın ötesine geçilmesi halinde proxy tabanlı bir sistemin kullanılması gerekmektedir. Bakalım bizi ne gibi sürprizler bekliyor olacak...Wink Tekrardan görüşünceye dek hepinize mutlu günler dilerim. 

AdHocDiscoveryForAnnouncement.rar (82,45 kb)

Tags:   ,
Categories:   WCF 4.0 Beta 1
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WCF 4.0 Yenilikleri - Discovery için Scope Kullanmak [Beta 1]

Cuma, 21 Ağustos 2009 01:57 by bsenyurt

Merhaba Arkadaşlar,

Bir önceki yazımızda WCF 4.0 tabanlı servislerde WS-Discovery protokolünün, Ad Hoc modeline göre nasıl uygulanabileceğini görmüştük. Ad Hoc modelinde istemcinin, yerel ağ üzerine dahil olan bir servisi aramak için kullanabileceği kriterleri önceden belirlemesi ve bunları kullanması gerektiğinden bahsetmiştik. Bu amaçla kod tarafında FindCriteria tipinden yararlanılmaktadır. Bir önceki örneğimizde, arama kriterinde sadece servis sözleşmesini(Service Contract) kullanmıştık. Ancak, arama alanını biraz daha dar tutmak amacıyla Scope bildirimlerinde de bulunabiliriz. Bir başka deyişle, ağ üzerinde birden fazla servisin arandığı durumlarda kapsama alanımızı, ekleyeceğimiz Scope kriterlerine göre azaltma şansımız bulunmaktadır. Bir şekilde istemcinin ilgi alanınıda daha kesin çizgilerle belirlemiş olmaktayız. Peki bunu nasıl uygulayabiliriz?

Konunun servis tarafında yine konfigurasyon seviyesinde değerlendirilmesi gerekmektedir. Bu amaçla endpointDiscovery isimli bir endpoint davranışının kullanılması ve içerisinde gerekli scope tanımlamasının yapılması gerekmektedir. Bu amaçla daha önceden geliştirmiş olduğumuz servis örneğinde yer alan app.config dosyasına aşağıdaki davranış eklemelerini yaptığımızı düşünelim.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceDiscovery />
          <serviceMetadata/>
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="epBehavior">
          <endpointDiscovery>
            <scopes>
              <add scope="http://www.adventure.com/Math/Calculus"/>
            </scopes>
          </endpointDiscovery>
        </behavior>
      </endpointBehaviors>
    </behaviors>   
    <services>
      <service name="ServerApp.CalculusService">
        <endpoint address="" binding="basicHttpBinding" contract="ServerApp.ICalculus" behaviorConfiguration="epBehavior" />
        <endpoint address="Mex" kind="mexEndpoint" />
        <endpoint name="udpDiscovery" kind="udpDiscoveryEndpoint" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

Dikkat edileceği üzere endpoint davranışlarının tanımlandığı alanda, endpointDiscovery elementi içerisinde basit bir scope tanımlaması yapılmaktadır. scopes elementi içerisinde n sayıda scope tanımlaması olabilir. Tanımlamaların artması elbetteki kapsama alanının aranması için daha dar bir kriterin oluşturulmasına neden olacaktır. Bu daralma istemcinin arama operasyonu için aslında bir avantaj olarak düşünülebilir. Tanımlanan bu discovery davranışının hangi endpoint için ele alınacağı ise yine endpoint elementi içerisindeki behaviorConfiguration niteliği(attribute) yardımıyla sağlanmaktadır. Peki buna göre istemci tarafını nasıl kodlamalıyız? İşte istemci tarafındaki kod yapısının yeni hali...

using System;
using System.ServiceModel;
using System.ServiceModel.Discovery;
using ClientApp.CalculusSpace;

namespace ClientApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Başlamak için tuşa basın");
            Console.ReadLine();

            DiscoveryClient disClient = new DiscoveryClient("udpDiscovery");

            // Arama kriteri oluşturuluyor. Parametre olarak servis sözleşmesini içeren Interface verilmekte
            FindCriteria findCriteria = new FindCriteria(typeof(ICalculus));
            findCriteria.Scopes.Add(new Uri("http://www.adventure.com/Math/Calculus"));

            #region Asenkron erişim

            // Standart olay bazlı asenkron erişim tekniği kullanılır.

            disClient.FindCompleted += delegate(object sender, FindCompletedEventArgs e)
            {
                // Hata varsa bildir
                if (e.Error != null)
                {
                    Console.WriteLine(e.Error.Message);
                }
                else if (e.Cancelled == true) // İşlem iptal edilmişse bildir
                {
                    Console.WriteLine("İşlem iptali");
                }
                else // Aksi durumda işlemleri yürüt ve servis operasyonunu elde edilen adres üzerinden çalıştır
                {
                    FindResponse findResponse = e.Result;
                    EndpointAddress epAddress = findResponse.Endpoints[0].Address;

                    // Bulunan endPoint adresi, proxy' nin üretilmesinde kullanılıyor
                    CalculusClient client = new CalculusClient("CalculusEndpoint", epAddress);

                    Console.WriteLine("{0} adresi üzerinden çağrı yapılacaktır", epAddress.Uri.ToString());
                    double result = client.Sum(3, 5);
                    Console.WriteLine("{0} + {1} = {2}", 3, 5, result.ToString());
                }
            };

            disClient.FindAsync(findCriteria);
            Console.WriteLine("Arama işlemi başladı");
            Console.ReadLine();

            #endregion
        }
    }
}

Görüldüğü gibi tek yaptığımız Scopes koleksiyonuna yeni bir Uri bilgisini, FindCriteria nesne örneği üzerinden eklemektir. Aslında buradaki metod parametresinin Uri tipinden olması, servis tarafındaki scope niteliğine neden bir url formatı yazdığımızı açıklamaktadır. Uygulamamızı bu haliyle çalıştırdığımızda yine bir önceki örnekte olduğu gibi, servisin keşfedilip bulunduğunu ve başarılı bir şekilde çalıştırıldığını görürüz.

Özetle Scope eklentileri sayesinde istemcinin, servis keşfi yapması için gerekli ayarları tabir yerinde ise akord etmesi ve gerçekten ilgilendiği alanlara ait servisleri araması mümkün hale gelebilmektedir. Konu ile ilişkili olarak örneğin son halini link olarak vermiyorum. Lütfen burada yazılanları oraya taşıyıp denemekten üşenmeyiniz Laughing Tekrardan görüşünceye dek hepinize mutlu günler dilerim. 

Tags:   ,
Categories:   WCF 4.0 Beta 1
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share