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

Silverlight - JSON ile Çalışmak

Cuma, 13 Ağustos 2010 10:15 by bsenyurt

Merhaba Arkadaşlar,

Uzun süredir şöyle deliksiz uyuyamıyordum. Malum evde bir afacan var. Pek uyumayı sevmeyen, sürekli hareket halinde olmak isteyen Sarp Efe izin verdiğinde, eşim ve ben dinlenmek için çeşitli işlere dalıyoruz. Ben uzun süredir Bulmacalara takılmış durumdayım. Bir de şu eski dil karşılıklarını isteyen sorular olmasa. Geçtiğimiz günlerde yine böyle bir boşluk yakalamışken, kendimi bulmacalar arasında yüzerken buluverdim. Ancak bir süre sonra "...eski dildeki karşılığı..." sorularından sıkıldım ve televizyonda neler olduğuna bir akayım dedim.

Televizyonda yandaki resimde görülen adam vardı ve ismi Jason' dı. Açıkçası Jason Statham' ın fanatiği bir sinemasever olarak bu isim benzerliğinin, böyle korkutucu bir karakter üzerinde olması beni üzmüştü. Nitekim Jason ismini düşününce aklıma gıcır gıcır parlayan Audi marka arabalar gelmekteydi. Her neyse...Filme fazla takılmadım ama Jason, Jason derken, bu isim JSON diye dudaklarımdan süzülmeye başladı. Pek tabi bunun doğal sonucu olarak bilgisayarımın başına oturdum ve JSON ile ilişkili bir şeyler yazmaya karar verdim. İşte başlıyoruz Wink

Bildiğiniz üzere HTTP bazlı WCF servislerinden(WCF WebHttp Services - JSON Formatlı Response Üretmek) JSON(JavaScript Object Notation) formatında çıktılar yayınlanabilmektedir. Bazı durumlarda istemci tarafı, JSON veri içeriği ile çalışmayı tercih edilebilir. Özellikle XML ile karşılaştırıldığında, JSON formatının daha az yer tutan bir yapıya sahip olması, bu seçimin yapılmasında önemli bir etkendir. Biz bu yazımızda bir WCF WebHttp Service tarafından yayınlanan JSON formatlı veri çıktısının, örnek bir Silverlight istemcisi tarafından nasıl ele alınabileceğini incelemeye çalışıyor olacağız.

Silverlight tarafında JSON içeriği ile çalışabilmek adına geliştirilmiş JsonArray, JsonObject, JsonPrimitive gibi tipler bulunmaktadır. Bu tipler sayesinde JSON veri kümesinde yer alan string, number, Boolean gibi veri türleri kod içerisinde ele alınabilir. Ayrıca tek JSON nesnesi veya bir JSON nesne listesinin ele alınması da sağlanabilir. Bu geliştiriciler için önemlidir. Nitekim Web ortamında gelen JSON içeriğinin Parse edilme işlemleri ile uğraşılmasına gerek kalmamaktadır.

Dilerseniz hiç vakit kaybetmeden örnek bir Silverlight uygulaması üzerinden ilerlemeye çalışalım. İşe ilk olarak IIS üzerinde host edeceğimiz WCF Rest Service Application projesini ve aşağıdaki kod içeriğine sahip LogService servis örneğini geliştirerek başlayabiliriz.

using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;

namespace TraceLogServiceApplication
{
    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall)]
    public class LogService
    {
        [WebGet(UriTemplate = "Logs/All",ResponseFormat=WebMessageFormat.Json)]
        public List<Log> GetAllLogs()
        {
            return new List<Log>()
            {
                new Log{ Source="Sql Server", Content="Sql servisi başlatıldı", IsCritical=false, Level=5},
                new Log{ Source="Sql Server", Content="Sql Agent servisinde hata.", IsCritical=true, Level=1},
                new Log{ Source="DTC", Content="Dağıtık Transaction nesnesi üretildi.", IsCritical=false, Level=3},
                new Log{ Source="WF Runtime", Content="Süreç persist edildi", IsCritical=true, Level=2}
            };
        }
    }

    public class Log
    {
        public string Source { get; set; }
        public string Content { get; set; }
        public int Level { get; set; }
        public bool IsCritical { get; set; }
    }
}

LogService içerisinde yer alan GetAllLogs isimli servis operasyonu Log tipinden bir kaç eleman içeren basit bir List<Log> koleksiyonunu geriye döndürmektedir. Çalışma zamanında oluşturulacak olan bu içerik, istemci tarafına JSON formatında gönderilecektir. Bunun için dikkat edileceği üzere ResponseFormat özelliğinin değeri WebMessageFormat.Json sabiti olarak belirlenmiştir. Servisimizi bu haliyle test etmek istediğimizde adres satırından http://localhost:12043/LogService/Logs/All gibi bir çağrı yapmamız yeterli olacaktır. Bunun sonucunda aşağıdaki JSON içeriği üretilecektir.

[{"Content":"Sql servisi başlatıldı","IsCritical":false,"Level":5,"Source":"Sql Server"},{"Content":"Sql Agent servisinde hata.","IsCritical":true,"Level":1,"Source":"Sql Server"},{"Content":"Dağıtık Transaction nesnesi üretildi.","IsCritical":false,"Level":3,"Source":"DTC"},{"Content":"Süreç persist edildi","IsCritical":true,"Level":2,"Source":"WF Runtime"}]

Bu işlemin ardından servisi IIS alınta Publish etmemiz yeterlidir. Publish ayarlarını aşağıdaki resimde görüldüğü gibi belirleyebiliriz.

Eğer Publish işlemi başarılı olduysa(IIS üzerinden ilgili uygulamanın Web Application olarak set edilmesine-Convert to Application seçeneği dikkat ederekten) herhangibir tarayıcı uygulamadan, http://localhost/TraceLogServiceApplication/LogService/Logs/All şeklinde bir çağrıda bulunabiliyor olmamız gerekmektedir ki bu çağrının sonucu olarakta, yukarıdaki JSON içeriğine tekrardan ulaşabiliyor olmalıyız.

TraceLogServiceApplication.rar (30,47 kb) [Örnek Visual Studio 2010 Ultimate sürümü üzerinde test edilmiştir]

Tabi yapmamız gereken bir işlem daha bulunmaktadır. Hatırlayacağınız üzere Silverlight istemcileri için Cross-Domain Policy sorunsalı mevcuttur. Bu nedenle IIS üzerinde daha önceki yazılarda değindiğimiz ClientAccessPolicy.xml dosyasının içeriğini aşağıdaki gibi düzenlememiz ve TraceLogServiceApplication için gerekli garanti haklarını(grant-to) belirlememiz gerekmektedir.

Artık Silverlight 4.0 tabanlı istemci uygulamamızı geliştirmeye başlayabiliriz. Bu amaçla, JsonConsumer isimli Silverlight uygulamamız içerisindeki MaingPage.xaml ve kod içerikleri aşağıdaki gibi geliştirilebilir.

MainPage.xaml içeriği;

<UserControl x:Class="JsonConsumer.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">

    <Grid x:Name="LayoutRoot" Background="White">
        <Button Content="Get All Logs" Height="23" HorizontalAlignment="Left" Margin="24,20,0,0" Name="GetLogsButton" VerticalAlignment="Top" Width="75" Click="GetLogsButton_Click" />
        <sdk:DataGrid AutoGenerateColumns="True" ItemsSource="{Binding}" Height="204" HorizontalAlignment="Left" Margin="24,56,0,0" Name="LogsDataGrid" VerticalAlignment="Top" Width="347"/>
    </Grid>
</UserControl>

MainPage.xaml.cs içeriği;

using System;
using System.IO;
using System.Json;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;

// JsonArray, JsonObject gibi tiplerin kullanılabilmesi için Silverlight projesine System.Json.dll assembly' ının referans edilmesi gerekmektedir.

namespace JsonConsumer
{
    public partial class MainPage
        : UserControl
    {
        WebClient client = null;

        public MainPage()
        {
            InitializeComponent();

            client= new WebClient();
            client.OpenReadCompleted += new OpenReadCompletedEventHandler(client_OpenReadCompleted);
        }

        private void GetLogsButton_Click(object sender, RoutedEventArgs e)
        {
            client.OpenReadAsync(new Uri("http://localhost/TraceLogServiceApplication/LogService/Logs/All")); 
        }

        void client_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            Stream responseStream = e.Result;
            // JsonArray sınıfının static Load metodu, Http Web servisine yapılan talep sonrası dönen Stream örneğini alır.
            // Load metodu JsonValue tipinden bir referans döndürmektedir ve dizi olarak ele alabilmek için JsonArray tipine bilinçli bir dönüşüm yapılmıştır.
            JsonArray logs=(JsonArray)JsonArray.Load(responseStream);
            // Elde edilen JSON verisinden IsCritical değeri true olanlar çekilir ve LogInfo isimli tip içerisinde toplanır.
            var criticialLogs = from log in logs
                                where log["IsCritical"]
                                select new LogInfo
                                {
                                     Content=log["Content"].ToString(),
                                     Source=log["Source"].ToString(),
                                     Level=log["Level"]
                                };

            // Elde edilen veri kümesi DataGrid kontrolüne veri kaynağı olarak gösterilir
            LogsDataGrid.DataContext = criticialLogs;
        }
    }
    // Servis tarafındaki Log tipinin istemci tarafındaki karşılığı
    public class LogInfo
    {
        public string Source { get; set; }
        public string Content { get; set; }
        public int Level { get; set; }
        public bool IsCritical { get; set; }
    }
}

Hatırlayacağınız üzere WCF WebHttp Service örneklerine yapılacak olan istemci çağrıları için WebClient tipinden yararlanılmaktadır. Bu amaçla Button kontrolüne basıldığında, asenkron olarak söz konusu servise bir talepte bulunulmaktadır(OpenReadAsync). Talep sonuçlandığında ise geri bildirim olay metodu devreye girmektedir(OpenReadCompleted). İşte bu olay metodu içerisinde JSON veri içeriğinin ele alınması için gerekli işlemler gerçekleştirilmektedir.

Bu metoda ait kod parçasındaki en büyük yardımıcı JsonArray tipi ve Load fonksiyonudur . Bu fonksiyon, parametre olarak LogService isimli WCF WebHttp Servisine gönderilen talep sonucu, istemci tarafına indirilen Stream referansını kullanmaktadır. Sonuç daha sonradan basit bir LINQ sorgusu ile değerlendirilmiş ve örnek olarak kritik seviyedeki log bilgilerinin değerlendirilmesi amaçlanmıştır. Uygulamanın çalışma zamanı görüntüsü aşağıdaki gibi olacaktır.

Görüldüğü gibi JSON formatındaki içerik Silverlight tarafında başarılı bir şekilde ele alınmış ve veri bağlı bir kontrol(DataGrid) ile ilişkilendirilebilmiştir. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

JsonConsumer.rar (1,92 mb) [Örnek Visual Studio 2010 Ultimate sürümü üzerinde test edilmiştir]

Silverlight Tarafından Feed Okumak

Perşembe, 22 Temmuz 2010 18:05 by bsenyurt

Merhaba Arkadaşlar,

Yeni bir maceraya hazır mısınız? Hureyyy dediğinizi duyar gibiyim. Bildiğiniz üzere Internet kaynaklarının takibinin kolay bir şekilde yapılabilmesi adına RSS veya Atom formatındaki Feed içeriklerinden sıklıkla yararlanmaktayız. Blog, Community, News Group ve benzeri pek çok internet kaynağı, güncel içeriklerini yayınlamak amacıyla global olarak standart hale getirilmiş olan bu formatları kullanmaktalar. Pek tabi yayınlanan bu içeriklerin takip edilebilmesi içinde çeşitli istemci programlar söz konusu. FeedReader bu uygulamalara örnek olarak verilebilecek Windows tabanlı iddialı programlardan birisi. Feed içerikleri zaman zaman internet siteleri üzerinde kontrol şeklinde de barındırılmaktadır. Söz gelimi pek çok blog içerisinde bu durum söz konusudur ve hatta hazır Widget' lar yardımıyla entegrasyonları son derece kolaydır. Peki maceramız nerede başlıyor? Özellikle ambulans resminin bu konu ile alakası nedir? Sealed

Doğruyu söylemek gerekirse sıkıldığım bir ara ne yapayım diye düşünürken Silverlight 4.0 tabanlı olarak geliştirilen bir uygulamadan RSS içeriklerini nasıl okuyabileceğimi düşünmeye başladım. Daha önceden HTTP bazlı Get,Post,Put, Delete metodlarınaa cevap veren WCF tabanlı servislerin tüketilmesi için WebClient tipinden nasıl yararlanıldığını incelemiştim(Silverlight Tarafında HTTP Bazli Servisleri Kullanmak isimli yazıyı incelemenizi öneririm) Yine aynı şekilde devam ederek herhangibir RSS içeriğini örnek Silverlight uygulamama taşıyabileceğimi düşünerek kolları sıvadım ve heyecanlı bir şekilde aşağıdaki ekran görüntüsü ve XAML içeriğine sahip kontrolü oluşturdum.

XAML içeriği;

<UserControl x:Class="RSSReaderim.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="337" d:DesignWidth="394" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">

    <Grid x:Name="LayoutRoot" Background="White">
        <Button Content="RSS Oku" Height="23" HorizontalAlignment="Left" Margin="313,76,0,0" Name="ReadRSSButton" VerticalAlignment="Top" Width="75" Click="ReadRSSButton_Click" />
        <sdk:Label Height="29" HorizontalAlignment="Left" Margin="8,12,0,0" Name="label1" VerticalAlignment="Top" Width="69" Content="RSS Adresi" FontSize="10" />
        <ListBox Height="180" HorizontalAlignment="Left" Margin="6,105,0,0" Name="RSSListBox" VerticalAlignment="Top" Width="382" ItemsSource="{Binding}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Title.Text}" Foreground="BlueViolet" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <TextBox Height="23" HorizontalAlignment="Left" Margin="8,47,0,0" Name="RSSTextBox" VerticalAlignment="Top" Width="380" />
        <sdk:Label Height="40" HorizontalAlignment="Left" Margin="8,297,0,0" Name="RSSInfoLabel" VerticalAlignment="Top" Width="380" FontSize="9" />
    </Grid>
</UserControl>

Aslında teori son derece basitti. Kullanıcı TextBox kontrolü üzerinden bir RSS adresi girecekti. Sonra düğmeye basarak içeriğin ListBox kontrolüne dolmasını seyredecekti. Son derece basit ve masumane bir talep öyle değil mi? Undecided Tabi bu işlemler için kod tarafını da, heyecanlı bir şekilde aşağıdaki gibi geliştirmeye çalıştım.

using System;
using System.Net;
using System.ServiceModel.Syndication;
using System.Windows;
using System.Windows.Controls;
using System.Xml;

namespace RSSReaderim
{
    public partial class MainPage : UserControl
    {
        WebClient client;

        public MainPage()
        {
            InitializeComponent();
            // WebClient nesnesi örneklenir
            client = new WebClient();
            // RSS Adresinden okuma işlemi tamamlanınca devreye girecek olan olay metodu yüklenir
            client.OpenReadCompleted += new OpenReadCompletedEventHandler(client_OpenReadCompleted);
        }

        void client_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            if (e.Error == null) // Eğer okuma işlemi sırasında bir hata oluşmadıysa
            {
                // RSS bilgisi e.Result üzerinden Stream şeklinde elde edilir ve XmlReader nesnesinin örneklenmesi için kullanılır. Bu gereklidir nitekim SyndicationFeed.Load metodu XmlReader tipi ile çalışmaktadır.
                XmlReader xReader = XmlReader.Create(e.Result);
                // System.ServiceModel.Syndication.dll Assembly' ının projeye referans edilmesi gerekmektedir.
                SyndicationFeed feed = SyndicationFeed.Load(xReader);
                // Items koleksiyonu ListBox bileşenine veri kaynağı olarak bağlanır
                RSSListBox.ItemsSource = feed.Items;
            }
            else if(e.Error!=null)
            {
                // Bir hata oluştuysa istisna mesajını Label kontrolünde göster
                RSSInfoLabel.Content = String.Format("Bir Sorun oluştu. {0}", e.Error);
            }
        }

        private void ReadRSSButton_Click(object sender, RoutedEventArgs e)
        {           
            // Okuma işlemini başlat.
            // Örnek RSS Adresi : http://www.buraksenyurt.com/syndication.axd?format=rss
            if (!String.IsNullOrEmpty(RSSTextBox.Text))
                client.OpenReadAsync(new Uri(RSSTextBox.Text));
            else
                RSSInfoLabel.Content = "Lütfen bir RSS Adresi giriniz";
        }
    }
}

Kod parçasından da görüldüğü üzere WebClient tipini kullanarak TextBox kontrolüne girilen adres için bir talepte bulunulmaktadır. Söz konusu talepin sonucu elde edildiğinde devreye giren olay metodu içerisinde ise, öncelikli olarak bir hata kontrolü yapılmaktadır. Eğer herhangibir hata söz konusu değilse SyndicationFeed tipinden yararlanılarak elde edilen Stream referansının Feed olarak ele alınabilmesi amacıyla gerekli işlemler yapılmaktadır. Son olarak söz konusu içerik nesnesi üzerinden ulaşılan Items koleksiyonu, ListBox kontrolüne bağlanır.

Şimdi blog girdimizin başında yer alan resmi açıklayalım. Bu kadar süratli araba kullanırsanız duvara toslamanız an meselesi olabilir. Aynen örneğimizde şu an tosladığımız gibi Undecided İşte duvara tosladığımız anda saniyenin milyonda birinde şişen hava yastığı içinden fırlayan Exception mesajımız.

Hayda breeeee!!! Surprised İşte hızlı gitmenin doğal sonucu.

Aslında gözden kaçırdığımız çok önemli bir durum söz konusu. O da Silverlight tarafında önem arz eden konuların başında gelen Cross-Domain Policy vakası. Sonuç itibariyle RSS çıktısı için talepte bulunduğumuz Domain adresi ile örneği geliştirmekte olduğumuz Asp.Net Development Server' ın port numarası eşliğine açtığı Domain adresleri birbirlerinden farklı. Bu sebepten sunucu tarafının bir ClientAccessPolicy.xml dosyasına sahip olması ve içerisinde söz konusu talepler için gerekli garanti haklarını belirtmiş olması şart. Ancak bu senaryoya göre Silverlight istemcileri için Cross-Domain Policy desteği vermeyen hiç bir sunucudan RSS içeriğini okumamız mümkün değil. Peki öyleyse ne yapacağız? Çözüm olarak biraz dolambaçlı bir yol olsa da, aşağıdaki şekilde görülen planı izleyebiliriz.

Biliyoruz ki, Silverlight uygulamaları Asp.Net gibi Web uygulamaları içerisinde host edilebilmektedir. Planımıza göre Cross-Domain Policy sorunu ile karşılaşmayacak olan WCF Service' lerinin, Silverlight istemcilerinin talep edeceği RSS içeriklerini ele alması söz konusudur. Buna göre Silverlight istemcileri, RSS çıktılarına doğrudan talepte bulunmak yerine söz konusu taleplerini önce arada Proxy görevini üstlenen bir WCF servisine iletecektir. Bu WCF servisi, ilgili adres bilgisini alarak Feed çıktısını talep edecek ve elde ettiği içeriği tekrardan Silverlight tarafına gönderecektir. Şekildeki plana göre 1 ve 2 numaralı iki adet WCF servisi söz konusudur. Bunlardan hangisinin seçileceği tamamen tercihe bağlıdır. İstersek Silverlight uygulaması ile aynı Domain içerisinde yer alan bir WCF servisini, istersek IIS üzerinde konuşlandırılan ayrı bir WCF servisini kullanabiliriz. Tabi IIS üzerinde host edilen bir WCF Servisi söz konusu ise, Silverlight istemcisi ile olan iletişiminin güvenlik sorununa takılmaması için ClientAccessPolicy.xml kullanılması gerekecektir. Tercih tamamen geliştiriciye bağlıdır. Ancak aynı Web sunucusu üzerinde yer alan birden fazla Silverlight istemcisi söz konusu ise ilgili servisin IIS altında konuşlandırılması daha çok tercih edilebilir.

Not : Bu noktada hazır olarak Siverlight istemcilerine hizmette bulunabilen Feed servislerinden de yararlanabileceğimizi belirtmek isterim. Reading data and RSS with Silverlight and no cross-domain policy başlıklı yazıda Tim Heuer söz konusu servislerden bahsetmektedir.

Ben örneğimizde hız kesemeden devam edebilmek adına, aynı uygulamaya Silverlight destekli bir WCF servisini ekleyerek ilerlemeyi tercih ettim. İşte FeedReaderService isimli Silverlight servisinin kod içeriği.

Not : Silverlight destekli WCF Servicelerinin nasıl geliştirileceğini Screencast - Silverlight Enabled WCF Services isimli görsel dersten takip edebilirsiniz.

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Syndication;
using System.Web.Services.Protocols;
using System.Xml;

namespace RSSReaderim.Web
{
    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class FeedReaderService
    {
        [OperationContract]
        public List<SyndItem> ReadRss(string address)
        {
            List<SyndItem> feedItems = null;          
            try
            {
                // XmlReader.Create metodu parametre olarak address bilgisini almaktadır. Elde edilen Xml içeriği Load metodu yardımıyla çekilir ve SyndicationItem tipinden olan Items koleksiyonu çekilir.               
                var syndicationItems =SyndicationFeed.Load(XmlReader.Create(address)).Items;
                // SyndicationItem örneklerinin her biri ele alınıp yeni bir SyndItem örneklenmesinde kullanılır.
                feedItems = (from syndicationItem in syndicationItems
                             select new SyndItem
                             {
                                 Title = syndicationItem.Title.Text,
                                 PublishDate = syndicationItem.PublishDate.DateTime,
                                 Summary = syndicationItem.Summary.Text,
                                 Link=syndicationItem.Links[0].Uri
                             }
                           ).ToList();
            }
            catch(Exception excp)
            {
                throw new SoapException("Bir hata oluştu", new XmlQualifiedName("RssReadError"), excp);
            }
            return feedItems;
        }
    }

    // SyndicationItem tipi serileştirme sorununa neden olduğundan araya bir Surrogate tip alınmıştır. Bu tip içerisinde Silverlight tarafı için gerekli temel Feed bilgileri yer almaktadır.
    public class SyndItem
    {
        public string Title { get; set; }
        public DateTime PublishDate { get; set; }
        public string Summary { get; set; }
        public Uri Link { get; set; }
        //TODO: Diğer bilgilerde getirilmelidir. Örneği yazar bilgisi, son güncellenme tarihi veya kategoriler.
    }
}

Servis kodunda dikkat edilmesi gereken en önemli noktalardan birisi, ReadRss metodunun geriye SyndItem tipinden generic bir List koleksiyonu döndürmesidir. Bu noktada akla şu soru gelebilir. Neden List<SyndicationItem> gibi bir koleksiyon döndürmüyoruz? Wink Aslında buradaki sorun SyndicationItem tipinin serileştirme işlemi sırasında çalışma zamanı hatasına neden olmasıdır. Serileştirmedeki bu sıkıntı bizi alternatif bir yola itmiştir. Bu sebepten örnekte bir Surrogate tip kullanılmaktadır. Bu işlemin ardından artık Silverlight tarafı için gerekli geliştirmeler yapılabilir. İlk etapta aynı Domain içerisindeki(bir başka deyişle aynı Solution içerisindeki) WCF Servisinin Silverlight projesine eklenmesi gerekmektedir.

Sonrasında ise istemci tarafı için gerekli kodlar yazılabilir. Yeni örnekte XAML içeriği de aşağıdaki gibi düzenlenmiştir.

<UserControl x:Class="RSSReaderim.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="337" d:DesignWidth="394" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">

    <Grid x:Name="LayoutRoot" Background="White">
        <Button Content="RSS Oku" Height="23" HorizontalAlignment="Left" Margin="313,76,0,0" Name="ReadRSSButton" VerticalAlignment="Top" Width="75" Click="ReadRSSButton_Click" />
        <sdk:Label Height="29" HorizontalAlignment="Left" Margin="8,12,0,0" Name="label1" VerticalAlignment="Top" Width="69" Content="RSS Adresi" FontSize="10" />
        <ListBox Height="180" HorizontalAlignment="Left" Margin="6,105,0,0" Name="RSSListBox" VerticalAlignment="Top" Width="382" ItemsSource="{Binding}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Vertical" Margin="2" Background="Black">
                        <TextBlock Text="{Binding Title}" Foreground="Gold" />
                        <TextBlock Text="{Binding Link}" Foreground="LightCyan" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <TextBox Height="23" HorizontalAlignment="Left" Margin="8,47,0,0" Name="RSSTextBox" VerticalAlignment="Top" Width="380" />
        <sdk:Label Height="40" HorizontalAlignment="Left" Margin="8,297,0,0" Name="RSSInfoLabel" VerticalAlignment="Top" Width="380" FontSize="9" />
    </Grid>
</UserControl>

Bu kez ListBox.ItemTemplate içerisinde hem Title hemde Link bilgilerinin gösterilmesi sağlanmıştır. Yeni örnekte SnydItem isimli bir Surrogate tip söz konusu olduğundan ve bu tipin Title özelliği String tipten tanımlandığından, bir önceki XAML kodunda yer alan {Binding Title.Text} eşitlemesi kullanılmamalıdır. Gelelim kodlarımıza;

using System;
using System.Windows;
using System.Windows.Controls;
using RSSReaderim.FeedReaderServiceSpace;

namespace RSSReaderim
{
    public partial class MainPage : UserControl
    {
        FeedReaderServiceClient client = null;
        public MainPage()
        {
            InitializeComponent();
            client = new FeedReaderServiceClient();
            client.ReadRssCompleted += new EventHandler<ReadRssCompletedEventArgs>(client_ReadRssCompleted);
        }

        private void ReadRSSButton_Click(object sender, RoutedEventArgs e)
        {
            client.ReadRssAsync(RSSTextBox.Text);
        }
        void client_ReadRssCompleted(object sender, ReadRssCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                RSSInfoLabel.Content = e.Error;
            }
            else if (e.Cancelled)
            {
                RSSInfoLabel.Content = "İşlem iptal edildi";
            }
            else
            {
                RSSListBox.ItemsSource = e.Result;
            }           
        }
    }
}

Kod içeriğinden de görüldüğü üzere WCF servisine ait Proxy tipinden yararlanılarak Feed içeriğinin asenkron olarak ortama çekilmesi işlemi gerçekleştirilmektedir. İşte örnek çalışma zamanı çıktılarından birisi.

ve diğer bir örnek;

Görüldüğü üzere RSS içerikleri başarılı bir şekilde getirilebilmektedir. Elbetteki örnekte eksik olan bir çok kısım vardır. Söz gelimi RSS ile ilişkili olarak daha çok verinin getirilmesi daha iyi olacaktır. Söz gelimi Feed' in sahibi olan siteye ait bilgiler. Diğer yandan eksik kalan önemli noktalardan biriside ListBox' ta bir öğe seçildiğinde ilgili Feed adresine nasıl gidileceğidir. Sonuç itibariyle Silverlight uygulaması tarayıcı üzerinde çalışmaktadır ve ilgili Feed içeriğinin Content verisinin gösterilmesini herkes isteyecektir. İşte size güzel bir araştırma konusu ve ödev Smile Benden buraya kadar. Bir süre dinlenmeye çalışacağım. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

RSSReaderim_RTM.rar (1,48 mb)[Örnek Visual Studio 2010 Ultimate RTM sürümünde geliştirilmiş ve test edilmiştir]

Silverlight Tarafında HTTP Bazli Servisleri Kullanmak

Pazartesi, 12 Temmuz 2010 09:55 by bsenyurt

Merhaba Arkadaşlar,

Eğitmenlik yaptığım yıllarda Microsoft' un ders kitaplarında yer alan LAB çalışmalarını mümkün mertebe yapmaya ve yaptırmaya çalışırdım. Hatta çoğu zaman eğitimlere hazırlanırken sık sık bu lab çalışmalarını kendim yapar ve hatta ek ilaveler ile daha da eğlenceli hale getirmeye çalışırdım. Tabi bazen elimizde lab yapacağımız kitaplarımız olmazdı ki o ayrı bir hikaye. Lab çalışmaları öğrencinin adım adım yapması gerekenleri söylerek, konunun en yalın haliyle anlaşılmasını sağlamakta önemli rol oynamaktadır. Lab çalışmalarındakine benzer konu anlatımları benimde özümsediğim ve faydalı bulduğum öğrenme tekniklerinden birisidir. İşte bu yazımızda da bu kültüre uymaya çalışarak ilerlemeye çalışıyor olacağız. Hedefimiz Silverlight uygulamalarından, HTTP tabanlı taleplere göre operasyonel hizmetlerde bulunan servisleri nasıl kullanabileceğimizi, en yalın haliyle görmek. Haydi o zaman lab için gerekli materyalleri değerlendirerek yola koyulalım.

Adım 0 : Mevzumuz

Bilindiği üzere bazı servisler HTTP protokolü üzerinden GET, POST, PUT veya DELETE metod çağrıları ile kullanılabilmektedir. Bu anlamda WCF Eco System içerisinde yer alan WebHTTP servisleri, söz konusu tipteki hizmetleri sunmak üzere WCF alt yapısı üzerine oturmuş bir model sunmaktadır. Çok doğal olarak Silverlight tabanlı istemciler de bu servislerin tüketicileri olabilirler. Bu tip servislerin kullanıldığı senaryolarda istemci tarafında herhangibir Proxy tipi söz konusu olmadığı için, HTTP GET,POST,PUT veya DELETE metodlarının manuel olarak hazırlanması ve gönderilmesi gerekmektedir. Silverlight tarafında bu işlemler için WebClient veya HttpWebRequest tiplerinden yararlanılabilmektedir. Biz bu yazımızda WebClient tipinden yararlanarak, IIS(Internet Information Services) üzerinde konuşlandırılmış basit bir WebHttp Service örneğinin nasıl kullanılabileceğini incelemeye çalışıyor olacağız.

Adım 1 : WCF Rest Application Uygulaması ve Entity Data Model' in Oluşturulması

İşe ilk olarak WCF Rest Service Application şablonunda bir proje oluşturarak başlayabiliriz. Bildiğiniz üzere bu proje şablonu(Project Template) hali hazırda yüklü değilse Online Template' ler arasından install etmeniz gerekmektedir. Söz konusu örnekte Chinook veritabanında yer alan ve çok basit olarak ilerlemek istediğimizden sadece Album tablosunu içeren bir Entity Data Model kullanabiliriz. Aşağıdaki şekilde örneğimizde kullanmakta olduğumuz Entity Data Model yer almaktadır.

 

Adım 2 : WebHttp Service Örneğinin Geliştirilmesi

Entities isimli WCF WebHttp Service sınıfımızın içeriğini ise aşağıdaki gibi düzenlediğimizi düşünebiliriz.

using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;

namespace ChinookDataPortal
{
    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class Entities
    {
        [WebGet(UriTemplate = "Albums/All")]
        public List<Album> GetAlbums()
        {
            List<Album> albums = null;

            ChinookEntities entities = new ChinookEntities();
            albums=(from albm in entities.Albums
                    orderby albm.Title
                    select albm).ToList();

            return albums;
        }

        [WebGet(UriTemplate="Albums/{firstLetter}")]
        public List<Album> GetAlbumsByFirstLetter(string firstLetter)
        {
            List<Album> albums = null;

            ChinookEntities entities = new ChinookEntities();
            albums = (from albm in entities.Albums
                      where albm.Title.ToLower().StartsWith(firstLetter.ToLower())
                      orderby albm.Title
                      select albm).ToList();

            return albums;
        }
    }
}

Servis tipimiz iki operasyon içermekte olup her ikiside HTTP Get çağrılarına cevap verecek şekilde düzenlenmişlerdir. GetAlbums metoduna yapılan çağrılarda servis URL adresine Albums/All takısı eklenmelidir. Diğer yandan ilk harflerine göre albümleri listeleyen GetAlbumsByFirstLetter metodu, URL adresine Albums/{firstLetter} bilgisinin eklenmesini beklemektedir. Her iki metod ChinookEntities tipini kullanmakta ve basit LINQ sorguları ile sonuç üretmektedir. Servisimizi bu şekilde geliştirdikten sonra IIS altına Publish ederek devam edebiliriz.

Adım 3 : IIS Publish

Publish işlemleri için aşağıdaki şekilde görülen Profile ayarlarını kullanabilirsiniz.

Bu ayarlara göre servisimizin IIS üzerinde yer alan Default Web Site isimli Application Pool altına dağıtılacağı belirtilmiş olunur.

Not: IIS üzerinden Convert To Application işlemini yapmanız gerekebilir.

Sonuç olarak IIS içerisinde aşağıdaki gibi servisin üretilmiş olması gerekmektedir. Bu arada örneği geliştirdiğimiz makinede Windows 7 Enterprise işletim sisteminin ve IIS 7.5.7600.16385 sürümünün olduğunu belirtelim.

Bu noktadan sonra Silverlight uygulamasının geliştirilmesi aşamına geçilecektir. Ancak öncelikle gerekli testleri yapılmasında yarar vardır.

ChinookDataPortal.rar (46,54 kb) [Örnek Visual Studio 2010 Ultimate RC ortamında geliştirilmiş ve test edilmiştir]

Adım 4 : WebHttp Service Test

Silverlight tarafındaki uygulamamızı geliştirmeden önce servisimizi IIS üzerinden test etmemizde ve çalıştığından emin olmamızda yarar olacağı kanısındayım. İlk olarak yardım sayfasına ulaşıp ulaşamadığımızı öğrenelim. Bilindiği üzere WebHttp Service örnekleri aksi belirtilmedikçe hazır bir yardım sayfası sunmaktadır. Bu amaçla tarayıcı uygulamadan http://localhost/ChinookDataPortal/Entities/help şeklinde bir talepte bulunduğumuzda, aşağıdaki ekran çıktısı ile karşılaşmış olmalıyız.

Yardım sayfasının çalışıyor olması dışında servis tarafında yer alan operasyonel metodların da test edilmesinde yarar vardır. Örneğin tüm albümleri elde etmek için http://localhost/ChinookDataPortal/Entities/Albums/All şeklinde talepte bulunduğumuzda, aşağıdaki ekran görüntüsünde yer alan sonuçları elde etmiş olmamız gerekmektedir. Tabi veri içeriklerinde değişiklikler söz konusu olabilir. Ancak XML çıktısının şematik yapısının benzer olması gerekmektedir.

Son olarak örneğin Cake adı ile başlayan albümleri çekmek istediğimizi ve bu amaçla URL satırından http://localhost/ChinookDataPortal/Entities/Albums/Cake şeklinde bir talep gönderdiğimizi düşünelim. Bu durumda ekran çıktısının aşağıdakine benzer olması gerekmektedir.

Eğer bu sonuçları elde edebiliyorsak servisimizin çalıştığını ve Sliverlight tarafı için kullanılabilir olduğunu söyleyebiliriz. Lakin dikkat etmemiz gereken bir nokta daha vardır.

Adım 5 : Client Access Policy Ayarları

Silverlight uygulamamızın farklı bir Domain içerisinde host edilmesine karşılık, IIS üzerinde gerekli Client Access Policy ayarlarının bulunması gerekmektedir. Bu nedenle IIS root klasörü altında yer alması gereken ClientAccessPolicy.xml dosyasının içeriğini aşağıdaki gibi düzenleyebiliriz.

<?xml version="1.0" encoding ="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from>
        <domain uri="*" />
      </allow-from>
      <grant-to>
        <resource path="/WorldWeatherService" include-subpaths="true"/>
        <resource path="/ChinookDataPortal" include-subpaths="true"/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>

Burada görüleceği üzere ChinookDataPortal ve alt yollarına erişim izni verilmiştir. Artık Silverlight tarafını geliştirmeye başlayabiliriz.

Adım 6: Silverlight Application Projesinin Oluşturulması

Bu amaçla Visual Studio 2010 ortamında ConsumingHTTPBasedServices isimi ve Silverlight 4.0 tabanlı bir Application oluşturduğumuzu düşünelim. Söz konusu uygulamada RIA Service kullanılmayacağı için bu seçeneği pasif olarak bırakabiliriz. Bu işlem sonucu oluşturulan MainPage sayfasına ait XAML içeriğini ise aşağıdaki gibi geliştirebiliriz.

Adım 7 : MainPage.Xaml içeriği ve Kodun Yazılması

<UserControl x:Class="ConsumingHTTPBasedServices.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="517">

    <Grid x:Name="LayoutRoot" Background="White">
        <ListBox Height="203" HorizontalAlignment="Left" Margin="8,70,0,0" Name="AlbumListBox" VerticalAlignment="Top" Width="497" />
        <StackPanel Height="54" HorizontalAlignment="Left" Margin="9,10,0,0" Name="ButtonsStackPanel" VerticalAlignment="Top" Width="496" Orientation="Horizontal" />
    </Grid>
</UserControl>

MainPage içerisinde yer alan ListBox kontrolü içeriği A' dan Z' ye harfler ile doldurulacaktır. Herhangibir harfe basıldığında, WebHttp Service' imiz için bir HTTP Get talebi oluşturulacak ve sonuçların ListBox içerisinde gösterilmesi sağlanacaktır. Bu amaçla kod içeriğini aşağıdaki gibi geliştirmemiz yeterlidir.

using System;
using System.Linq;
using System.Net;
using System.Windows.Controls;
using System.Xml;
using System.Xml.Linq;

namespace ConsumingHTTPBasedServices
{
    public partial class MainPage
        : UserControl
    {
        // WebHttp Servisine basit HTTP metodları ile talepte bulunabilmemizi sağlayan WebClient nesnesi tanımlanır
        WebClient client;

        public MainPage()
        {
            InitializeComponent();

            // WebClient nesnesi örneklenir
            client=new WebClient();
            // Belirtilen URL adresine yapılan talep sonucu gerçekleşecek okuma işlemi tamamlandığında(bir başka deyişle veri istemci tarafında indirildiğinde) devreye girecek olan olay metodu tanımlanır.
            client.OpenReadCompleted += new OpenReadCompletedEventHandler(client_OpenReadCompleted);

            // A...Z Button üretimleri gerçekleştirilir
            for (int i = 65; i < 91; i++)
            {
                Button btn = new Button();
                btn.Width = 18;
                btn.Height = 18;
                btn.FontSize = 10;
                btn.Content = ((char)i).ToString();
                ButtonsStackPanel.Children.Add(btn);
                // Herhangibir Button tıklandığında
                btn.Click += (o, e) =>
                {                  
                    // Önce WebHttp Service' ne doğur yapılacak HTTP Get talebi için gerekli URI oluşturulur
                    Uri address = new Uri(String.Format("http://localhost/ChinookDataPortal/Entities/Albums/{0}", ((Button)o).Content));
                    // Belirtilen URI talebi asenkron olarak çalışan OpenReadAsycn metodu ile gönderilir
                    client.OpenReadAsync(address);                   
                };
            }
        }

        // URI ile belirtilen adres talebi gerçekleştirilip ilgili veri içeriği istemci tarafına indirildikten sonra devreye giren olay metodudur
        void client_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            // İçerik bir Stream olarak gelmektedir ve tasarlanan ChinookDataPortal WebHttp Servisi varsayılan olarak XML içerik göndermektedir.
            // Bu sebepten Stream XmlReader ile okunur
            XmlReader xReader = XmlReader.Create(e.Result);
            // XLINQ sorgusunun yapılabilmesi için XElement.Load metodu parametre olarak Stream' i kullanan XmlReader nesne örneğini alır
            XElement xElement = XElement.Load(xReader);
            // XLINQ sorgusu ile Title elementleri çekilir. XName.Get metodunun ikinci parametre XML Namespace' inin adıdır.
            var titles = from x in xElement.Elements().Elements(XName.Get("Title", "http://schemas.datacontract.org/2004/07/ChinookDataPortal"))
                         select x.Value;
            // Çekilen veri içeriği ListBox kontrolünün ItemsSource özelliğine bağlanır
            AlbumListBox.ItemsSource = titles;
        }
    }
}

Not: XElement tiplerini kullanabilmek ve XLINQ sorgularını yazabilmek için, Silverlight uygulamasına(ConsumingHTTPBasedServices.Web uygulamasına değil) System.Xml.Linq.dll Assembly' ının referans edilmesi gerekmektedir.

Adım 8 : Silverlight Uygulamasının Test Edilmesi

Dilerseniz uygulamanın çalışma zamanı sonuçlarına hemen bakalım. Böylece çalışma zamanı testlerini yapmış oluruz. Örneğin A başlıklı Button kontrolüne bastığımızda, aşağıdaki ekran görüntüsündekine benzer sonuçları almış olmalıyız. Yani Title alanındakilerden A harfi ile başlayanların listesinin elde edilebiliyor olması gerekmektedir.

Görüldüğü üzere ListBox içeriği baş harfi A olan albüm adları ile doldurulmuştur. Hemen bu işlemin arkasından örneğin C başlıklı Button kontrolüne basarsak aşağıdaki sonuçlar ile karşılaştığımız görürüz.

Süper değil mi? Wink

Özet

Tabi bu örnekte dikkat edilmesi gereken noktalardan birisi de, istemci tarafında herhangibir Proxy tipinin olmayışıdır. Bunun yerine HTTP Get metodu ile talepte bulunulmuş ve elde edilen Stream üzerindeki XML içeriği değerlendirilmiştir. Diğer yandan çok doğal olarak Servis tarafında kullanılan Entity Data Model içerisindeki tiplerin istemci tarafındaki karşılıkları bulunmamaktadır. Eğer bu tiplerin istemci tarafında ele alınması arzu edilirse açık bir şekilde oluşturulmaları gerekecektir. Tabi böyle bir senaryoda gelen XML veya JSON tipindeki içeriğinde ilgili tiplere dönüştürülmesi gibi bir işlem söz konusu olacaktır.

Ödev Smile

  1. Servisin XML yerine JSON(JavaScript Object Notation) formatında bir çıktı vermesi halinde, Silverlight tarafında gerekli olan kod düzenlemelerini yapınız.
  2. Servis üzerinden HTTP Put metod ile güncelleme işlemi yapabilmenizi sağlayacak bir geliştirmeyi aynı örnek üzerinden yapmaya çalışınız.

Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

ConsumingHTTPBasedServices_RTM.rar (273,75 kb)[Örnek Visual Studio 2010 Ultimate RC Sürümü üzerinde geliştirişmiş ve RTM sürümü üzerinde test edilmiştir]

WCF Web Http Services - ETags

Cuma, 9 Nisan 2010 14:05 by bsenyurt

Merhaba Arkadaşlar,

WCF WebHttp Service' leri ile ilişkili yazılarımıza kaldığımız yerden devam ediyoruz. Bu yazımızda ETag(Entity Tag) kullanarak sunucu ile istemci arasındaki veri trafiğini nasıl azaltabileceğimizi incelemeye çalışacağız. Öncelikle istemci ile servis arasındaki iletişimi düşünerek ilerlemeye çalışalım. İstemci, sunucu üzerinde yer alan bir operasyon için talepte bulunduğunda bir cevap üretilecek ve buna bağlı bir içerik verisi istemci tarafına indirilecektir. Bu süreç tipik olarak Request-Response senaryosundan farklı bir işleyiş değildir. İstemci sonraki bir zaman diliminde aynı operasyona yeni bir talepte bulunduğunda ise, üretilecek olan sunucu cevabının(Response) bir öncekine göre hiç değişmemiş olma ihtimalide bulunmaktadır. Eğer istemci tarafı bir şekilde gönderdiği talebin karşılığı olan cevabın değişmediğini anlayabilirse ve kendisinde bu içerik zaten tampon alanda duruyorsa, aynı içeriğin sunucudan istemci tarafına bir kere daha indirilmesine gerek yoktur. İşte ETag takısının devreye girdiği nokta burasıdır.

Peki ETag(Entity Tag) tam olarak nedir? Entity Tag, sunucudan istemci tarafına gönderilen Response paketlerinin Header kısmında kullanılabilen bir takıdır. Bu takı yardımıyla bilginin değişikliğe uğrayıp uğramadığı kolayca anlaşılabilir. Bu ayrım bize performans açısından bir kazanım sağlayabilir. Öyleki, sunucu aynı ETag verisine sahip iki Response ürettiğinde, aslında istemcinin talebinin karşılığının bir öncekisi ile aynı olduğu sonucuna varılabilir. Bu noktada istemcinin içeriği tampon bir bölgede tuttuğu düşünüldüğünde, bir önceki ile aynı olan veri içeriğini sunucudan indirmesine gerek kalmayacaktır. Böyle bir durumda sunucun istemci tarafına HTTP 304 Not Modified bilgisi göndermesi söz konusudur. Tabi burada ETag içerisine yazılacak verinin nasıl üretileceği de önemlidir. Genellikle Entity ile alakalı olaraktan son güncelleme zamanı veya checksum kullanılabilir. Hatta SQL veritabanında kullanılabilen Timestamp tipide ETag verisi olarak ciddi anlamda düşünülebilir. Sonuç itibariyle ETag kavramının Caching modeli için çok önemli bir kriter olduğunu söyleyebiliriz.

Şimdi konuyu aşağıdaki içeriğe sahip bir WebHttp Service örneği üzerinden değerlendirmeye çalışalım.

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;

namespace Lesson2
{
    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class ProductService
    {
        static List<Product> products = new List<Product>
        {
            new Product{ ProductId="1",Name="Zedojen 500 Gb HDD", Version=Guid.NewGuid()},
            new Product{ ProductId="2",Name="Zedojen 750 Gb HDD", Version=Guid.NewGuid()},
            new Product{ ProductId="3",Name="Leveno Laptop XK 5301", Version=Guid.NewGuid()}
        };

        [WebGet(UriTemplate = "Products/{productId}")]
        public Product GetProduct(string productId)
        {
            var product = (from p in products
                           where p.ProductId == productId
                           select p).FirstOrDefault();
            // Eğer bir Product nesne örneği mevcutsa...
            if (product != null)
            {
                // İstemciden gelen paket Header' ındaki If-None-Match değeri alınır ve sunucu tarafında bulunan Product nesne örneğinin Version özelliğinin değeri ile kıyaslanır. Eğer aynı ise bu istemci tarafına HTTP 304 Not Modified döndürüleceği anlamına gelir.
                WebOperationContext.Current.IncomingRequest.CheckConditionalRetrieve(product.Version);
                // Response içerisinde yer alan HTTP Header içerisindeki ETag değeri sunucu tarafından bulunan Product nesne örneğinin Version özelliği ile set edilir.
                WebOperationContext.Current.OutgoingResponse.SetETag(product.Version);
            }

            return product;
        }               
    }

    public class Product
    {
        public string ProductId { get; set; }
        public string Name { get; set; }
        public Guid Version { get; set; }
    }
}

Servisimizde yer alan GetProduct isimli operasyon geriye Product tipinden bir nesne içeriği döndürmektedir. Product tipinin en önemli özelliklerinden birisi ise Guid tipinden olan Version' dur. Burada veritabanında yer alan bir ürünün ETag için kullanılabilecek veri tipi simüle edilmeye çalışılmaktadır. Yazımızın başında da belirttiğimiz gibi tablo bazlı kaynağın söz konusu olması halinde, Guid yerine Timestamp tipinden bir alan da tercih edilebilir.

GetProduct metodu içerisinde Exception kontrolü yapılmamaktadır. Daha çok üzerinde durmak istediğimiz nokta ETag veri kontrolü ve üretimidir. Bu nedenle WebOperationContext.Current üzerinden çağırılan CheckConditionalRetrieve ve SetETag metodlarına konsantre olmamızda yarar vardır. CheckConditionalRetrieve metodu istemciden gelen talebe ait içerikteki If-None-Match değeri kontrolünü yapmaktadır. Eğer istemci aynı içeriği bir kere daha talep etmişse, bu durumda istediği Product tipinin Version değeri ile gönderdiği If-None-Match değeri aynı olmalıdır. Tabiki buradaki örnek senaryomuzda şu an için veri tarafında yer alan Version alanının değişmediğini düşünüyoruz. Özellikle tarih bazlı olarak tutulan veri içeriklerinde ve örneğin son güncelleme tarihinin ETag olarak kullanıldığı durumlarda ya da herhangibir değişiklik sonrası ilgili versiyon kontrolü alanlarının değerlerinin değiştirildiği hallerde, sunucu tarafından istemciye doğru veri indirilmesi(Download) işlemi yinelenecektir. SetETag metodu ise ilk gelen talep sonrası veya içeriğin istemciye indirilmesi gerektiği talep sonrası, Response' a ait içeriğe o anki Product nesne örneğinin Version değerini atayacaktır. Her iki metodunda farklı tipte aşırı yüklenmiş(Overload) versiyonları bulunmaktadır. Bu versiyonlardan birisi de örneğimizde ele aldığımız Guid veri tipi ile çalışanıdır. Dilerseniz durumu daha iyi anlamak için hemen testlerimize başlayalım. Örneğimizi tarayıcı uygulama üzerinden talep ettiğimizde ilk etapta aşağıdaki örnek sonuçlar ile karşılaşırız.

Not : Burada ipv4.fiddler:1000 şeklindeki kök adres kullanımı mutlaka dikkatinizi çekmiştir. Bunu Fiddler üzerinden örneğimize ait HTTP paketlerini debug edebilmek için kullandığımızı belirtmek isterim.

Örnekte ProductId değeri 1 olan ürüne ait bilgilerin elde edildiği görülmektedir. Bundan sonra 1 numaralı ürünü tekrardan talep edecek olursak Fiddler tarafından aşağıdaki HTTP hareketliliklerinin yakalandığını görebiliriz.

Dikkatinizi çeken bir şey var mı? Wink İlk talep sonrasında sunucudan istemiye HTTP 200 Ok bilgisi dönmüş ve 237 Byte' lık bir Body içeriği indirilmiştir. Diğer yandan aynı talebin ikinci kez yapılması sonrasında istemci tarafına HTTP 304 Not Modified mesajının döndürüldüğü görülmektedir. Üstelik ikinci talep sonrası Body içeriği 0 byte uzunluğundadır. Volaaa!!! Laughing Yani ikinci talebin ilki ile aynı veri üretimine sahip olduğu anlaşılmış ve bu sebepten üretilen paketin istemci tarafına yeniden indirilmesine gerek kalınmamıştır. Örneğimize göre byte seviyesinde bu çok önemli bir performans kazanımına neden olmamaktadır. Ne varki video, resim, müzik gibi büyük boyutlu binary içeriklerin yer aldığı paketlerde 304 döndürülmesinin büyük önemi vardır. Nitekim az önce üretilip istemci tarafına indirilen büyük boyutlu içeriğin, ikinci talep sonrası zaten istemci tarafındaki tamponda duran versiyonu ile aynı olması nedeniyle, yeniden gönderilmesi durumu ortadan kaldırılmakta ve böylece istemci ile sunucu arasındaki ağ trafiğinden akan veri boyutu minimize edilmektedir.

Şimdi Fiddler aracı yardımıyla paketlerin içeriklerine biraz daha yakından bakalım. İlk talep sonrası istemcinin gönderdiği içerik aşağıdaki gibidir.

GET http://127.0.0.1:1000/Adventures/Products/1 HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, */*
Accept-Language: tr
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; OfficeLiveConnector.1.4; OfficeLivePatch.1.3; .NET4.0C; .NET4.0E; MS-RTC LM 8)
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Host: 127.0.0.1:1000

Bu talebe karşılık sunucunu cevabı ise aşağıdaki gibi olacaktır.

HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Fri, 26 Feb 2010 09:43:00 GMT
X-AspNet-Version: 4.0.30128
Content-Length: 237
ETag: "2c1cd636-3058-4985-8f10-3d3cb8c9e5fa"
Cache-Control: private
Content-Type: application/xml; charset=utf-8
Connection: Close

<Product xmlns="http://schemas.datacontract.org/2004/07/Lesson2" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Name>Zedojen 500 Gb HDD</Name><ProductId>1</ProductId><Version>2c1cd636-3058-4985-8f10-3d3cb8c9e5fa</Version></Product>

Dikkat edileceği üzere Response içerisinde bir ETag değeri olduğu görülmektedir. Sizce bu değerin 1 numaralı Product' ın güncel Version değeri ile aynı olması bir tesadüf müdür? Wink Ayrıca içeriğin uzunluğu 237 byte' tır.

Gelelim ikinci talebe. İstemci tarafından sunucuya gönderilen ikinci talebin içeriği aşağıdaki gibidir.

GET http://127.0.0.1:1000/Adventures/Products/1 HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, */*
Accept-Language: tr
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; OfficeLiveConnector.1.4; OfficeLivePatch.1.3; .NET4.0C; .NET4.0E; MS-RTC LM 8)
Accept-Encoding: gzip, deflate
If-None-Match: "2c1cd636-3058-4985-8f10-3d3cb8c9e5fa"
Host: 127.0.0.1:1000
Connection: Keep-Alive

Dikkat edileceği üzere Guid değerine sahip olan If-None-Match isimli element bulunmaktadır. Bu değer biraz önceki talep sonucu istemciye gönderilen ETag değeridir aslında. Şimdi bu noktada sunucu üzerinde yer alan 1 numaralı ürünün içeriğinin değiştirilmediği ve bu nedenle Guid değerinin de aynı olduğu düşünülmektedir. Bu sebepten sunucu tarafından istemciye gönderilen cevabın içeriği aşağıdaki gibi olacaktır.

HTTP/1.1 304 Not Modified
Server: ASP.NET Development Server/10.0.0.0
Date: Fri, 26 Feb 2010 09:43:05 GMT
X-AspNet-Version: 4.0.30128
ETag: "2c1cd636-3058-4985-8f10-3d3cb8c9e5fa"
Cache-Control: private
Connection: Close

Her hangibir içerik(Content) yoktur. Hatta 0 byte uzunluğunda bir Content mevcuttur. Ama daha önemlisi yine ETag elementi vardır ve Guid değerini içermektedir. Ayrıca HTTP 304 Not Modified bilgisinin döndürüldüğü dikkatlerden kaçmamalıdır.

Peki sunucu tarafındaki Product içeriğinde ve dolayısıyla Version değerinde bir değişme olursa? Bir veritabanı örneği geliştirmediğimiz için bu durumu şu şekilde simüle edebiliriz
; 1 numaralı ProductId değerine sahip ürünün adını Visual Studio 2010 ortamında değiştirip örneği tekrardan build ederek. Yeniden build işlemi sonucu static olarak tanımlanan List<Product> koleksiyon içeriğinin üretimi yinelenecektir. Bu da yeni Guid değerlerinin üretimi anlamına gelmektedir. Bu durumda servis operasyonuna yeniden talepte bulunursak aşağıdaki cevabı aldığımız görürüz.

HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Fri, 26 Feb 2010 11:52:38 GMT
X-AspNet-Version: 4.0.30128
Content-Length: 237
ETag: "2fadf1be-d5c3-4fe0-a9c6-ecf20437ffe4"
Cache-Control: private
Content-Type: application/xml; charset=utf-8
Connection: Close

<Product xmlns="http://schemas.datacontract.org/2004/07/Lesson2" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Name>Zedojen 250 Gb HDD</Name><ProductId>1</ProductId><Version>2fadf1be-d5c3-4fe0-a9c6-ecf20437ffe4</Version></Product>

Dikkat edileceği üzere Guid değeri bir öncekinden farklıdır ve istemciye HTTP 200 Ok koduyla Product içeriği tekrardan gönderilmiştir. Tabi bunun sonrasında 1 numaralı ürünü yeninden talep edersek yine HTTP 304 Not Modified durumu ile karşılaşırız.

Buraya kadar her şey iyi gitti. Ancak testlerimizi farkettiğiniz üzere Internet Explorer gibi tarayıcı uygulamalar üzerinden gerçekleştirdik. Oysaki istemci uygulamayı biz yazıyorsa ETag kullanımı için de yapmamız gereken ekstra işlemler söz konusudur. Bu amaçla az önce geliştirdiğimiz servis uygulamasını test edeceğimiz basit bir Console Application geliştirdiğimizi düşünelim. Kod içeriğini aşağıdaki gibi yazmamız ETag desteği için yeterli olacaktır.

using System;
using Microsoft.Http;
using Microsoft.Http.Headers;

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            Uri serviceAddress = new Uri(@"http://ipv4.fiddler:1000/Adventures/");
            using (HttpClient client = new HttpClient(serviceAddress))
            {
                EntityTag eTag=null;
                Process(client, "1",ref eTag);
                Process(client, "1",ref eTag);
            }
        }

        static void Process(HttpClient client, string ProductId,ref EntityTag ETag)
        {
            Console.WriteLine("***{0} için Talep***\n",ProductId);
            // Talebin hazırlanması ve gönderilmesi
            using (HttpRequestMessage request = new HttpRequestMessage("GET", "Products/"+ProductId))
            {
                // Metoda referans olarak gelen EntityTag tipinden olan ETag değerine bakılır. Eğer null değil ise ki ilk talep sonrası sunucu tarafından ürünün Version değeri ile doldurulacaktır; bu durumda Header kısmına If-None-Match değerinin eklenmesi sağlanır.
                if (ETag != null)
                    request.Headers.IfNoneMatch.Add(ETag);

                // If-None-Match değeri içeren talep gönderilir
                using (HttpResponseMessage response = client.Send(request))
                {
                    // ETag değeri gelen cevaptan alınır ve ref tipinden olan metod parametresine aktarılır. Böylece Process metoduna yapılacak olan sonraki çağrılarda aynı ETag değerinin taşınması kolaylaşmaktadır.
                    ETag = response.Headers.ETag;
                    // Sonuçlar ekran yazdırılır.
                    Console.WriteLine("StatusCode : {0}\n", response.StatusCode);
                    Console.WriteLine("Content : {0}\n",response.Content.ReadAsString());
                    Console.WriteLine("ETag Değeri : {0}\n", ETag.Tag);
                }
            }
            Console.WriteLine("*******");
        }
    }
}

Uygulamamızı çalıştırdığımızd aşağıdaki sonuçlar ile karşılaşırız.

Görüldüğü gibi, ikinci talep sonrasında istemci tarafına HTTP 304 Not Modified bilgisi ve 0 Byte uzunluğunda içerik gönderilmiştir. Böylece WCF WebHttp Service' leri ile ilişkili bir yazımızın daha sonuna geldik. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Lesson9_RC.rar (175,04 kb) [Örnek Visual Studio 2010 Ultimate RC sürümü üzerinde geliştirilmiş ve test edilmiştir]

WCF WebHttp Services - Özel Formatta Mesaj Döndürmek

Perşembe, 8 Nisan 2010 16:39 by bsenyurt

Merhaba Arkadaşlar,

Bu yazımızda bizleri uzun, zorlu ve yorucu bir macera bekliyor. Şimdiden söylemek isterim ki yanınızda tatlı(Mesela kolalı jelibon olabilir), tuzlu yiyecek bir şeyler, boğaz kuruluğunuzu giderecek içecekler veya daha fazla oksijen çekmenizi sağlayacak sakızlar olsun. Unutmadan birde aspirin. Baş ağrısı için Laughing  Gelelim bu günkü konumuza.

Bu yazımızda, son günlerde sıklıkla üzerinde durduğumuz WCF WebHttp Service' lerinde, istemciden gelen root adres bazlı taleplerin nasıl karşılanacağını ve özel formatta mesajların nasıl döndürüleceğini incelemeye çalışıyor olacağız. Ancak işe başlamadan önce ihtiyacın ne olduğundan bahsetmemizde yarar var. Bu amaçla bir Web uygulaması üzerinden host edilen birden fazla WebHttp servisimiz olduğunu düşünerek ilerleyelim. Bu servisler içerisinde de örneğin HTTP Get taleplerinin karışılığında çeşitli tipte koleksiyonları döndüren operasyonlarımız olduğunu farz edelim. Bu durumda global.asax dosyasındaki kodlarda yönlendirme tablosuna ekleyeceğimiz adres bilgilerine göre, gelen talepleri uygun olan servislere yöndermemiz mümkün olacaktır. Bunu zaten daha önceki bir yazımızda incelemiştik.

Söz gelimi http://makineadı:port numarası/CompanyServices/AdventureWorks/Products ile http://makineadı:port numarası/CompanyServices/Chinook/Albums gibi iki talep gönderildiğini düşünelim. Bu taleplerin aynı web uygulamasından host edilen iki farklı servis tipi tarafından değerlendirildiği bir durumda, doğru yönlendirme tekniği ile uygun olan servis ve operasyonunun çağırılması mümkündür. Oysaki istemciler http://makineadı:port/CompanyServices/ adresine de talepte bulunulabilir. Böyle bir durumda ne olur? Gelin bunu açıklamak için aşağıdaki örnek servis sınıflarını içeren bir WCF REST Service Application projemiz olduğunu düşünelim.

AdventureWorksService sınıfı;

using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;

namespace Lesson8

    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class AdventureWorksService
    {
        [WebGet(UriTemplate = "Products")]
        public List<Product> GetProducts()
        {
            return new List<Product>
            {
                new Product{ Id=1, Name="Büyüteç", ListPrice=1.24M},
                new Product{ Id=2, Name="Stabilo Pen 68", ListPrice=2.35M},
                new Product{ Id=3, Name="Temizleme Spreyi", ListPrice=4.19M}
            };
        }
    }
}

Söz konusu sınıf içerisinde yer alan GetProducts isimli operasyon geriye Product tipinden bir ürün listesi döndürmekle görevlendirilmiştir. Tahmin edeceğiniz üzere şu anda kendi kendimizi yetiştirmeye çalıştığımızdan sadece anlamsız bir liste üretimi söz konusudur.

ChinookService sınıfı;

using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;

namespace Lesson8

    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class ChinookService
    {
        [WebGet(UriTemplate = "Artists")]
        public List<Artist> GetArtists()
        {
            return new List<Artist>
            {
                new Artist{ Id=1, Name="Ayrosimit",IsGroup=true},
                new Artist{ Id=2, Name="Megadet",IsGroup=true},
                new Artist{ Id=3, Name="Metalika",IsGroup=true},
                new Artist{ Id=4, Name="Co Satriani",IsGroup=false}
            };
        }
    }
}

ChinookService sınıfıda, AdventureWorksService tipine benzer bir şekilde ama bu kez Artist tipinden liste döndüren tek bir operasyon içermektedir. Buraya kadar zaten bir sorun bulunmamaktadır. Ancak aynı web uygulamasında birden fazla servisi host etmek istediğimizde, RouteTable nesnesinin Routes koleksiyonu içerisinde gerekli düzenlemelerin de yapılması gerekmektedir. Bu nedenle global.asax.cs içeriğinin aşağıdaki gibi olduğunu düşünebiliriz.

using System;
using System.ServiceModel.Activation;
using System.Web;
using System.Web.Routing;

namespace Lesson8
{
    public class Global
        : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            RegisterRoutes();
        }

        private void RegisterRoutes()
        {
            RouteTable.Routes.Add(new ServiceRoute("AdventureWorks", new WebServiceHostFactory(), typeof(AdventureWorksService)));
            RouteTable.Routes.Add(new ServiceRoute("Chinook", new WebServiceHostFactory(), typeof(ChinookService)));
        }
    }
}

Buna göre istemciden gelecek olan http://localhost:10843/CompanyServices/AdventureWorks/Products ve http://localhost:10843/CompanyServices/Chinook/Artists talepleri sorunsuz bir şekilde karşılanacaktır. Ancak doğrudan Web uygulamasının Root adresine yapılan http://localhost:10843/CompanyServices/ gibi bir talepte aşağıdaki ekran görüntüsü ile karşılaşılacaktır.

Elbette Help sayfalarına gidilerek servislere nasıl talepte bulunulabileceği öğrenilebilir. Ancak elimizde iki servis olduğundan söz konusu yardım sayfalarına gitmek için http://localhost:10843/CompanyServices/AdventureWorks/help veya http://localhost:10843/CompanyServices/Chinook/help gibi taleplerinin gönderilmesi gerekmektedir.

Sanıyorum ki nihayet ne yapmak istediğimize gelebildik. İstediğimiz şey http://localhost:10843/CompanyServices/ adresine yapılan talep ile Web uygulamasından sunulan servisleri bildirmek olacak. Üstelik bu talebe karşılık dönecek mesajın içeriğini kendimiz tasarlayacağız. Yapabilir miyiz? Evet yapabiliriz. Çünkü gerekli tüm tipler Framework içerisinde çoktandır mevcutlar. Özetle talebe uygun bir formatta(XML, JSON, ATOM gibi) kendi veri yayınımızı yapacağımızı ifade edebiliriz.

İşe ilk olarak yeni bir servis sınıfını geliştirerek başlamamız gerekiyor. Bu sınıf içerisinde yer alan operasyonumuz geriye, System.ServiceModel.Channels isim alanında yer alan Message tipinden bir değer döndürüyor olacak. İşte EntranceService isimli yeni sınıfımızın içeriği;

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Channels;
using System.ServiceModel.Syndication;

using System.ServiceModel.Web;

namespace Lesson8

    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class EntranceService
    {
        // İstemci tarafına sunulacak olan kaynakların listesi Resource tipinden generic bir koleksiyonda tutulur.
        public static readonly List<Resource> Resources = new List<Resource>
        {
            new Resource{ Name="Chinook List", Description="Chinook nesneleri listesi", RequestUri=new Uri("Chinook",UriKind.Relative),HelpUri=new Uri("Chinook/help",UriKind.Relative)},
            new Resource{ Name="Adventure Works List", Description="Adventure Works nesneleri listesi", RequestUri=new Uri("AdventureWorks",UriKind.Relative),HelpUri=new Uri("AdventureWorks/help",UriKind.Relative)}
        };

        [WebGet(UriTemplate="")]
        public Message GetResources()
        {
            WebOperationContext optContext = WebOperationContext.Current;
            IncomingWebRequestContext incomingRequest = optContext.IncomingRequest;

            string mType=string.Empty;
            foreach (var acceptType in incomingRequest.GetAcceptHeaderElements())
            {
                // Gelen talebin Header bilgisinde yer alan MediaType değerine göre geriye Xml, Json veya Atom formatında bir içerik döndürülmesi sağlanır.
                 mType= acceptType.MediaType.ToLower();

                 if (mType == "application/xml" || mType == "text/xml")
                     return optContext.CreateXmlResponse(Resources); // Xml formatında dönüş
                 else if (mType == "application/json")
                     return optContext.CreateJsonResponse(Resources);
                 else if (mType == "application/atom+xml") // Json formatında dönüş
                 {
                     // Atom formatında dönüş için SyndicationFeed nesnesinin örneklenmesi gerekmektedir. Resource değişkeninin işaret ettiği koleksiyon bu nesne örneği içerisindeki Items koleksiyonuna atanır
                     return optContext.CreateAtom10Response(
                         new SyndicationFeed(
                             "Company Services Resources",
                             "Adventure Works & Chinook Kaynakları",
                             new Uri("", UriKind.Relative),
                             Resources.Select(r => new SyndicationItem(r.Name, r.Description, r.RequestUri)
                             )));
                 }
            }
            // Varsayılan olarak çıktı XML formatında verilir
            return optContext.CreateXmlResponse(Resources);
        }
    }

    // Servisten sunulan servislerin birer kaynak olduğu düşünüldüğünde bu servislere ait ad, açıklama, root Uri ve help page uri bilgilerinin saklandığı tip
    public class Resource
    {
        public string Name { get; set; }
        public string Description { get; set; }
        public Uri RequestUri { get; set; }
        public Uri HelpUri { get; set; }
    }
}

GetResources isimli servis operasyonunun en önemli özelliği WebGet niteliğinde boş bir template kullanılması ve tabiki geriye Message tipinden bir değer döndürmesidir. Servis operasyonu dikkatlice incelendiğinde, istemciden gelecek olan taleplere ait Header kısımlarında yer alan mesaj formatı bilgisine göre bir çıktı üretildiği görülebilir. Buna göre standart olarak XML, JSON ve ATOM formatlarında bir üretim söz konusudur. ATOM formatındaki çıktının hazırlanması sırasında bir SyndicationFeed nesnesinin örneklendiğine dikkat edilmelidir. Tüm bu formatlama işlemlerinde o anki Web içeriği referansını taşıyan WebOperationContext tipine ait Create... metodlarından yararlanılmaktadır.

Tabi işimiz bu servis sınıfını yazmakla bitmiş değil. Fark ettiğiniz üzere üçüncü bir servis sınıfımız oldu ve bu sınıfa gelen talepler UriTemplate bilgisine göre Web uygulamasının Root adresine yapılmakta. Dolayısıyla boş template için gerekli yönlendirme bilgisinin global.asax.cs içerisinde bildirilmesi gerekiyor. Aynen aşağıda olduğu gibi.

using System;
using System.ServiceModel.Activation;
using System.Web;
using System.Web.Routing;

namespace Lesson8
{
    public class Global
        : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            RegisterRoutes();
        }

        private void RegisterRoutes()
        {
            RouteTable.Routes.Add(new ServiceRoute("", new WebServiceHostFactory(), typeof(EntranceService)));
            RouteTable.Routes.Add(new ServiceRoute("AdventureWorks", new WebServiceHostFactory(), typeof(AdventureWorksService)));
            RouteTable.Routes.Add(new ServiceRoute("Chinook", new WebServiceHostFactory(), typeof(ChinookService)));
        }
    }
}

Buna göre örnek bir tarayıcı uygulama üzerinden http://localhost:10843/CompanyServices/ adresine yapacağımız bir talebin sonucu aşağıdaki gibi olacaktır.

Her şey yolunda görünüyor. Ancak ufak bir pürüz var. EntranceService tipine boş Uri bilgisi üzerinden bir başka deyişle Web uygulamasına ait Root adresten gidilebildiği için, http://localhost:10843/CompanyServices/help şeklinde gönderilen bir talepte aşağıdaki ekran görüntüsü ile karşılaşılacaktır.

Oysaki bu Help sayfasının çıkmasına pekte gerek yoktur. Bu bir zorunluluk değildir ama olmasının da bir anlamı yoktur. Dolayısıyla pasif hale getirmemiz gerekmektedir. Bu amaçla WCF 4.0 ile birlikte konfigurasyon dosyasına getirilen yeniliklerden yararlanarak gerekli sonucu elde edebiliriz. Tek yapmamız gereken Web.config dosyasını aşağıdaki gibi düzenlemek.

<?xml version="1.0"?>
<configuration>
 
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
    </modules>
  </system.webServer>

  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
    <standardEndpoints>
      <webHttpEndpoint>
        <!-- Diğer servislerin help sayfalarının disable olmaması için ilk standartEndpoint elementine dokunulmamalıdır-->
        <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true"/>
        <standardEndpoint name="EntranceServiceEndPoint"/><!-- Varsayılan olarak helpEnabled özelliği false değere sahiptir-->
      </webHttpEndpoint>
    </standardEndpoints>
    <services>
      <service name="Lesson8.EntranceService">       
        <endpoint contract="Lesson8.EntranceService" kind="webHttpEndpoint" endpointConfiguration="EntranceServiceEndPoint"/>
      </service>
    </services>
  </system.serviceModel>

</configuration>

Zaten varsayılan web.config dosyası içeriğine göre, tüm servis talepleri otomatik olarak standart bir Endpoint tipine yönlendirilir. Ancak senaryomuza göre ana adres üzerinden yapılan yardım sayfası talebi geçersiz olmalı, diğerleri ile kullanılabilir durumda kalmalıdır. Bu nedenle EntranceService isimli hizmet için de bir Endpoint tanımlaması yapılmış ve webHttpEndpoint içerisindeki farklı bir ayara yönlendirilmiştir. Buradaki düzenlemeye göre EntranceService dışındaki tüm servislerin help sayfalarına ulaşılabilmektedir. Ancak EntranceService için help sayfası gösterilmemektedir. Buna göre http://localhost:10843/CompanyServices/help adresine bir talepte bulunulduğunda aşağıdaki ekran görüntüsü ile karşılaşılacaktır.

Servis tarafında pek çok işimizi hallettik. Ancak test etmemiz gereken bir husus daha var. İstemcinin, XML dışında ATOM veya JSON formatlı mesaj taleplerine karşılık olarak nasıl sonuçlar alacağı. Nitekim tarayıcı üzerinden yaptığımız taleplerde standart olarak XML çıktısı aldığımızı gördük ve biliyoruz. Peki ya diğer formatlar? Bu durumu test etmek için yine basit bir Console uygulaması geliştiriyor olacağız. Her zamanki gibi HttpClient tipinden yararlanacağız. Bu sebepten REST Starter Kit ile gelen Microsoft.Http ve Microsoft.Http.Extensions Assembly referanslarını eklemeyi unutmayalım. İşte Console uygulamamıza ait kodlarımız.

using System;
using Microsoft.Http;

namespace ClientApp
{
    class Program
    {
        static void Main(string[] args)
        {
            using (HttpClient client = new HttpClient("http://localhost:10843/CompanyServices/"))
            {
                Execute(client,"application/json"); // Json formatında talep gönderilir
                Execute(client, "application/atom+xml"); // atom formatında talep gönderilir
                Execute(client, "noFormat"); // Olmayan bir format için talep gönderilir
            }
        }

        private static void Execute(HttpClient client,string acceptFormat)
        {
            // HttpRequestMessage nesnesi örneklenirken kullanılan ikinci parametreye göre EntranceService tarafından karşılanacak bir talep oluşturulur
            using (HttpRequestMessage requestMessage = new HttpRequestMessage("GET", String.Empty))
            {
                // Accept özelliğinin Add metodu yardımıyla Header' a eklenen bilgiye göre hangi formatta mesaj istendiğin belirtilir.
                requestMessage.Headers.Accept.Add(acceptFormat);
                using (HttpResponseMessage responseMesssage = client.Send(requestMessage))
                {
                    Console.WriteLine("\n{0}\n", responseMesssage.Content.ReadAsString());
                }
            }
        }
    }
}

Kodlarımızı çalıştırdığımızda aşağıdaki ekran görüntüsünde yer alan sonuçları elde ederiz.

Vuuuvvvv!!! Şu anda masamdaki şekerlerin oranına bakıyorum da...Baya bir tüketmişim. Sealed Artık dinlenmeye çekilmenin vakti geldi sanırım. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Lesson8_RC.rar (181,52 kb) [Örnek Visual Studio 2010 Ultimate Beta 2 Sürümünde geliştirilmiş ancak RC sürümü üzerinde de test edilmiştir]

WCF WebHttp Services - Client Bazlı Cache

Salı, 30 Mart 2010 16:30 by bsenyurt

Merhaba Arkadaşlar,

Bir önceki yazımızda(WCF WebHttp Services - Server Bazlı Cache) hatırlayacağınız üzere WCF WebHttp Service' lerinde sunucu taraflı ön belleklemeyi(Server-Based Caching) incelemeye çalışmış ve bu işin birde istemci taraflı olanından bahsetmiştik. Aslında sunucu ve istemci taraflı ön bellekleme işleyişleri birbirlerinden tamamen farklıdır. Sunucu taraflı ön bellekleme işleyişinde, tamponlanan veriyi üreten operasyonun duration süresi dolana kadar çalıştırılmaması söz konusudur. Yani istemciden gelen ilk talebin sonucunun ön belleğe alınmasını takiben gelen taleplerde, sunucu tarafındaki operasyon kodları icra edilmemektedir. Ne varki istemci taraflı ön belleklemenin işleyişine göre sunucu tarafındaki kodlar icra edilir ve ön bellekleme yapılacağı, HTTP Cache-Control bilgisinin istemciye gönderilen cevabın(Response) Header kısmına eklenmesi ile anlaşılır. Bir başka deyişle ilk talepten sonra gelecek taleplerde yine servis kodunun çalıştırılması gündemdedir. Dolayısıyla ispatı ve analizi pekte kolay olmayan bir konu ile karşı karşıyayız. Bu yüzden en azından nasıl hayata geçirilebileceğini görmeye çalışacağız. Elbette bir örnek geliştirerek. Gelin tembellik etmeyerek bir önceki uygulamamızdan devam etmek yerine yeni bir örnek üzerinden istemci taraflı ön belleklemenin nasıl yapılacağını araştıralım. Öncelikle aşağıdaki servis kodlarına sahip olan bir WCF REST Service Application projemiz olduğunu düşünelim.

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Web;

namespace Lesson7
{
    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class FunnyService
    {
        List<Wallpaper> wallpapers = new List<Wallpaper>
            {
                new Wallpaper{ Id=1000, Name="Ahhh!", Image=File.ReadAllBytes(HttpContext.Current.Server.MapPath("~/Images/Ahhh.jpg"))},
                new Wallpaper{ Id=1001, Name="Blue Light", Image=File.ReadAllBytes(HttpContext.Current.Server.MapPath("~/Images/BlueLight.jpg"))},
                new Wallpaper{ Id=1002, Name="My Dear Manager", Image=File.ReadAllBytes(HttpContext.Current.Server.MapPath("~/Images/Manager.jpg"))},
                new Wallpaper{ Id=1003, Name="No Sacrifice No Victory", Image=File.ReadAllBytes(HttpContext.Current.Server.MapPath("~/Images/Sacrifice.jpg"))}
            };
               
        [AspNetCacheProfile("WallpaperCache")] //web.config dosyasında WallpaperCache ismi ile output cache konfigurasyonu yapılmaktadır.
        [WebGet(UriTemplate = "Wallpapers/{Name}")]
        public List<Wallpaper> GetWallpapers(string Name)
        {
            var result = (from w in wallpapers
                         where w.Name.ToLower().Contains(Name.ToLower())
                         select w).ToList();
            return result;
        }
    }

    public class Wallpaper
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public byte[] Image{ get; set; }
    }
}

GetWallpapers isimli operasyonun çalıştırılması sonucunda istemci tarafına, Images klasörü altında yer alan jpg uzantılı resim dosyalarına ait byte[] içeriklerini taşıyan ve Wallpaper tipinden nesne örneklerinden oluşan generic List<T> koleksiyonu döndürülmektedir(Elbette varsayılan mesaj formatına göre XML olarak). AspNetCacheProfile niteliği yardımıyla söz konusu operasyon için Caching işleminin icra edileceği bildirilir. Bu ön belleklemenin hangi kriterlere göre yapılacağına ait tanımlamalar ise web.config dosyası içerisinde aşağıdaki gibi belirlenir.

<?xml version="1.0"?>
<configuration>
 
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
    <caching>
      <outputCache enableOutputCache="true"/>
      <outputCacheSettings>
        <outputCacheProfiles>
          <!-- İstemci taraflı ön bellekleme query string parametresi veya Header kısımlarını dikkate almaz. Bu nedenle varyByParam niteliğine none değerini atanmış ve varyByHeader niteliği kullanılmamıştır. Dikkat edileceği üzere ön belleklemenin istemci taraflı olacağı location niteliği ile belirlenmiştir.-->
          <add name="WallpaperCache" duration="120" varyByParam="none" location="Client"/>
        </outputCacheProfiles>
      </outputCacheSettings>
    </caching>
  </system.web>

Konfigurasyon dosyasının devamı...

OutputCacheProfiles içerisine eklenen WallpaperCache profiline göre Cache süresi 120 saniyedir. Diğer taraftan istemci taraflı ön bellekleme işlemlerinde Querystring kullanımı bir faktör olmadığından varyByParam niteliğine none değeri atanmalıdır. Bu profilde belkide en önemli nokta location niteliğinin değerinin Client olarak belirlenmesidir. Böylece ön bellekleme işleyişinin istemci taraflı yapılacağı tayin edilimiş olur.

Tabi bu durumu incelemek için istemci tarafının da geliştirilmesi gerekmektedir. Nitekim istemci tarafına gönderilen cevabın Header kısmında, Caching ile ilgili bilgilerin bulunması gerekmektedir. Bu amaçla çok basit olarak aşağıdaki kodlara sahip bir Console uygulaması yazdığımızı düşünebiliriz (HttpClient ve diğer yardımcı tiplerin kullanımı söz konusu olduğundan REST Starter Kit' in parçası olan Microsoft.Http ve Microsoft.Http.Extension assmebly' larının referans edilmesi gerektiğini unutmayalım)

using System;
using Microsoft.Http;

namespace ClientApp
{
    class Program
    {
        static void Main(string[] args)
        {
            // HttpClient nesnesi url adresine göre oluşturulur.
            HttpClient client = new HttpClient("http://localhost:1000/");

            Send(client);

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

        }
        static void Send(HttpClient client)
        {
            Console.WriteLine("\nAradığınız kelimeyi girin\n");
            string word = Console.ReadLine();
            // Wallpapers için gerekli request string oluşturulur. word bilgisi kullanıcı tarafından girilmektedir.
            string requestString = String.Format("Wallpapers/{0}", word);

            // Get metodundan yararlanılarak sunucu tarafına ilgili talep gönderilir
            using(HttpResponseMessage responseMessage = client.Get(requestString))
               {
   
             // Sunucudan gelen cevap içerisinde yer alan Cache bilgisi ve ayrıca HTTP Statü koduna bakılır
                Console.WriteLine("{0}\n{1}\n",responseMessage.Headers.CacheControl.MaxAge,responseMessage.StatusCode);
           
                // İçerik okunur
               Console.WriteLine(responseMessage.Content.ReadAsString());
            }
        }
    }
}

İstemci uygulamadan kullanıcının girdiği kelimeye göre HTTP Get formatında bir talebinin gönderilmesi sağlanmaktadır. Sunucu tarafından çağırılan operasyona ait ön bellekleme işleminin istemci bazlı olduğu, istemci tarafına gönderilen cevap(Response) içerisinden öğrenilebilir. Bu amaçla örnek olarak HttpResponseMessage nesnesinin Header.CacheControl özelliği üzerinden yakalanan MaxAge değerine bakılmıştır. Bu değer dikkat edileceği üzere sunucu üzerinde belirtilen duration süresinin Timespan karşılığıdır(örneğimize göre 2 dakika).

Tabiki Caching özelliğini sunucu tarafında kapatırsak ki bunun için web.config dosyasında ilgili cache profile' in enabled niteliğine false değeri atamamız yeterlidir,

<add name="WallpaperCache" duration="120" varyByParam="none" location="Client" enabled="false"/>

ve aynı örneği yeniden test edersek aşağıdaki sonuçlar ile karşılaşırız.

çok doğal olarak bir ön bellekleme süresi olmayacaktır. Örneği Fiddler gibi bir araç yardımıyla incelediğimizde ise istemci tarafına dönen cevaba ait Cache bilgisini daha detaylı bir şekilde görebiliriz.

WCF WebHttp Service' ler ile ilişkili serimizde yer alan bir yazımızın daha sonuna geldik. Bu yazımızda istemci bazlı ön bellekleme işlemleri için ne gibi hazırlıklar yapılması gerektiğini gördük. En önemli noktanın web.config dosyası içerisinde belirtilen location niteliği olduğunu özetleyebiliriz. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Lesson7_RC.rar (192,46 kb) [Örnek Visual Studio 2010 Ultimate Beta 2 Sürümünde geliştirilmiş ancak RC sürümü üzerinde de test edilmiştir]

WCF WebHttp Services - Server Bazlı Cache

Salı, 30 Mart 2010 00:05 by bsenyurt

Merhaba Arkadaşlar,

Şirketimi çok seviyorum. Nazar değmesin ama araştırma yapmam, yeni bir şeyler öğrenmem ve bunları ekip arkadaşlarımla paylaşmam için beni özellikle teşvik eden bir şirkette bulunmaktayım. Çalıştığım şirketin en güzel özelliklerinden biriside, Cuma günleri yapılan minik ikramları. Wink Her cuma değişik bir yiyecek ile karşılaşıyoruz. Geçtiğimiz Cuma' lardan birisinde ise yanda çektiğim resimde görülen gülen kurabiyelerimiz vardı. E böylesine güler yüzlü kurabiyeler ile gerekli glikozu aldıktan sonra içimden hemen eve gitmek gelmedi. Bunun yerine mesai sonrasında çalışma masamda oturup, etrafın sakinleşmesi ve sessizliğin artması ile birlikte bloğuma bir şeyler yazmaya karar verdim. Bir süredir WCF Eco System' in parçaları üzerinde yazmakta olduğum bir seri bulunmaktaydı. Bunu devam ettirmek ile Cuma gecesini güzelce tamamlayabileceğimi düşündüm. İşte bu günkü konumuz...WCF WebHttp Service' lerinde ön bellekleme(Output Caching).

WCF WebHttp Service' leri bildiğiniz üzere Web ortamı üzerinden sunulan hizmetlerdir. Bu sebepten Web tarafının sunucu ve istemci bazlı bazı yeteneklerini kullanabilirler. Örneğin Asp.Net Compatibility Mode ile çalıştırıldıklarında Asp.Net dünyasının Output Cache yeteneğine sahip olurlar. Bildiğiniz üzere Output Cache mekanizması sayesinde Web içeriklerine gelen taleplerin ön bellekten karşılanması ve bu sayede arka planda ilgili HTML çıktılarının üretilmesi için gerekli işlemlerin otomatikman atlanılması sağlanabilmektedir. Bu, özellikle üretim maliyeti yüksek olan ama belirli bir süre zarfı içerisinde değişmeyen sayfa içeriklerinin üretiminde oldukça performans arttırıcı bir tekniktir. Madem Web tarafında böyle bir yeteneğimiz bulunmaktadır, o halde neden bu kabiliyeti WCF Servislerinde de kullanamayalım? İşte bu yazımızda Asp.Net tarafında hazır olan bu alt yapının WebHttp Service' lerinde nasıl kullanıldığını incelemeye çalışıyor olacağız. İlk olarak konuyu sunucu bazlı ön bellekleme(Server Side Caching) olarak değerlendireceğiz. Serinin sonraki bölümünde ise istemci taraflı ön belleklemeyi ele alacağız. Dilerseniz vakit kaybetmeden kodlamaya başlayalım. Öncelikli olarak aşağıdaki WebHttp Service içeriğine sahip bir WCF REST Service Application geliştirdiğimizi düşünelim.

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Web;

namespace Lesson6
{  
    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class EinsteinService
    {
        // GetCategories isimli metodun çıktısının ön bellekleneceği AspNetCacheProfile niteliği ile belirtilir. Nitelikte parametre olarak kullanılan string bilgi ile web.config dosyasında bu metod için nasıl bir ön bellekleme yapılacağı set edilir. Ne kadar süre ile tutulacağı, parametre bazlı olup olmayacağı gibi...
        [AspNetCacheProfile("CategoriesCache")]
        [WebGet(UriTemplate = "Categories")]
        public List<string> GetCategories()
        {           
            string[] categories=File.ReadAllLines(HttpContext.Current.Server.MapPath("~/Kategoriler.txt"));
            return new List<string>(categories);
        }

        [AspNetCacheProfile("CategoriesByFirstLetterCache")]
        [WebGet(UriTemplate = "Categories/{firstLetter}")]
        public List<string> GetCategoriesByFirstLetter(string firstLetter)
        {
            string[] categories = File.ReadAllLines(HttpContext.Current.Server.MapPath("~/Kategoriler.txt"));
            return (from category in categories
                         where category.StartsWith(firstLetter,true,null)
                         select category).ToList();
        }
    }
}

Örneğimizde yer alan servisimizde iki operasyon yer almaktadır. Her iki operasyonda aşağıda örnek çıktısı olan Kategoriler.txt isimli text tabanlı dosyayı kullanmaktadır.

GetCategories operasyonu text dosyası içerisindeki tüm satırları string tipinden generic List koleksiyonu olarak geriye döndürmektedir. GetCategoriesByFirstLetter operasyonu ise aynı çıktıyı baş harflere göre üretmektedir. Bizim odaklanmamız gereken nokta ise her iki operasyon başında uygulanan AspNetCacheProfile niteliğidir(Attribute). Bu niteliklerin uygulanması ile söz konusu operasyonların çıktılarının ön bellekleneceği, çalışma zamanı ortamına bildirilmektedir. Her iki nitelikte birbirlerinden benzersiz olan takma adlar(Alias) ile işaret edilmektedir. Peki bu isimleri nerede değerlendireceğiz? Bu sorunun cevabı Web.config dosyasında yer alan Asp.Net Output Cache ayarlarında gizlidir...Wink Buradaki ayarlar ile hangi operasyon için nasıl bir ön bellekleme işleminin uygulanacağını belirtebiliriz. Örneğin operasyonların sonuçlarının ne kadar süreyle ön bellekte tutulacaklarını farklılaştırabiliriz. Yada parametre bazlı olanları...Örneğin GetCategoriesByFirstLetter operasyonunun çalışma zamanı HTML çıktılarının firstLetter bilgisine göre ön belleklenebileceğini belirtebiliriz. Tüm bu ayarlamalar için web.config dosyasına aşağıdaki eklemeleri yapmamız yeterlidir.

<?xml version="1.0"?>
<configuration>
 
  <system.web>
    <compilation debug="true" targetFramework="4.0" />

    <caching>
      <outputCache enableOutputCache="true"/>
      <outputCacheSettings>
        <outputCacheProfiles>
          <add name="CategoriesCache" duration="120" location="Server" varyByParam="none" varyByHeader="Accept"/>
          <add name="CategoriesByFirstLetterCache" duration="300" location="Server" varyByParam="firstLetter" varyByHeader="Accept"/>
        </outputCacheProfiles>
      </outputCacheSettings>
    </caching>
  </system.web>

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
    </modules>
  </system.webServer>

  <system.serviceModel>
    <!--Asp.Net Output Caching alt yapısını ve yeteneklerini kullanmak istediğimiz için, AspNet Compatibility modun açık olması önemlidir.-->
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
    <standardEndpoints>
      <webHttpEndpoint>
        <!--
            Configure the WCF REST service base address via the global.asax.cs file and the default endpoint
            via the attributes on the <standardEndpoint> element below
        -->
        <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true"/>
      </webHttpEndpoint>
    </standardEndpoints>
  </system.serviceModel>

</configuration>

Buradaki ayarlamalara göre CategoriesCache adı ile belirlenen operasyonlar 120 saniye süreyle ön bellekten getirilecektir. Ön bellekleme ortamının sunucu taraflı olduğu location niteliği ile belirtilmektedir. Bu ön bellek ayarında parametre kullanılmadığı(varyByParam="none") ve talebin(request) Header kısmına göre Accept tipinde olanların göz önüne alındığı ifade edilmektedir. Diğer yandan CategoriesByFirstLetterCache bildirimine bakıldığında sürenin 300 saniye olduğu ama bir öncekinden farklı olarak firstLetter parametresine göre ön bellekte ayrı ayrı görüntüler tutulacağı belirtilmektedir. Buna göre Categories/A gibi bir talep ile Categories/s gibi bir taleb için üretilen HTML çıktıları ön bellekte ayrı ayrı tutulacaktır. Tabi birden fazla parametrenin değerlendirilmesi gerektiği durumlarda ; işareti kullanılarak bildirilmeleri gerekmektedir. Örneğin varyByParam="firstLetter;productCount" vb.

Tabi yapımış olduğumuz bu anlatımın sonuçlarını test ederek görmemiz gerekiyor. Burada debug modu kullanarak talepler sırasında operasyon metodlarının içerisine ne zaman girilip ne zaman girilmediğini tespit ederek gerekli kontrolleri yapabiliriz. İşte size örnek test senaryoları;

1 - Önce URL üzerinden Categories talebini gönderin. Bu durumda tüm kategorilerin aşağıdaki şekilde olduğu gibi geldiğini göreceksiniz.

Şimdi 120 saniyelik süre dolmadan Kategoriler.txt üzerinde bir değişiklik yapın. Örneğin Mücehver isimli yeni bir kategori ekleyin veya bir kaç kategorinin ismini değiştirin yada silin. 120 saniyelik zaman dilimi içerisinde yeniden Categories talebinden bulunursanız yapmış olduğunuz değişiklilerin tarayıcıya getirilmediğini görebilirsiniz. Ancak 120 saniyelik ön bellek süresi dolduktan sonra değişiklikleri görebileceksiniz ve hatta Debug moddaysanız ilgili operasyon kodu içerisine tekrardan girildiğini fark edeceksiniz. Bu zaten ön belleklemenin çalıştığının ispatıdır.

2 - İkinci olarak parametre bazlı ön bellekleme yapıldığını test edebilirsiniz. Bu amaçla tarayıcı üzerinden örneğin Categories/A ve Categories/M taleplerinde bulunun. Debug modda ilerlerseniz testinizi daha başarılı bir şekilde yapabilirsiniz. Özellikle 300 saniyelik ön bellekleme süreleri dolmadan farklı harfler ile denemeler yaptığınızda, ön belleklenenler için operasyon koduna girilmediğini ama daha önceden talep edilmeyenler veya 300 saniyelik ön bellekte kalma süresini dolduranlar için kodun tekrar çalıştırıldığını gözlemleyebilirsiniz.

Bu sayede geliştireceğimiz WebHttp Service' lerin operasyonlarının hızlı sonuçlar üreterek daha performanslı ve verimli olmasını sağlayabiliriz. Bu yazımızdaki örneğimizde sunucu tarafında ön bellekleme işlemlerini gerçekleştirdik. Ancak birde istemci taraflı ön bellekleme(Client Based Caching) işlemlerinin söz konusu olduğunu belirtelim. Bunu serinin sonraki yazısında incelemeye çalışacağız. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Lesson6_RC.rar (20,41 kb) [Örnek Visual Studio 2010 Ultimate Beta 2 Sürümünde geliştirilmiş ancak RC sürümü üzerinde de test edilmiştir]

Screencast - WCF RIA Services, OData, Excel PowerPivot

Salı, 23 Mart 2010 09:05 by bsenyurt

Merhaba Arkadaşlar,

Yeni bir maceraya daha hazır mısınız? Evet dediğinizi duyar gibiyim. Bu sefer kobay haline getirdiğimiz Chinook veritabanına yine Entity Framework 4.0 üzerinden bir WCF RIA Service yardımıyla erişiyoruz. Ancak Domain Service tipini üretirken OData(Open Data Protocol) desteği vereceğini belirtiyoruz. Bu durumda servisimizin çıktı olarak ürettiği Feed içeriğinin OData standartlarını destekleyen herhangibir ürün tarafından kullanılabileceğini belirtmiş oluyoruz. İstemci tarafında ise Excel 2010 Beta ürününe bir AddIn olarak gelen PowerPivot aracından yararlanıyoruz. Buna göre servisin sunduğu veri içeriğini, sadece bir iki hareketle Excel üzerinde kullanılabilir halde çekmiş oluyoruz. Söz konusu fonksiyonellik sayesinde veriyi ister ızgara görünümünde bırakabilir ister detaylı grafiklerini çıkartabiliriz. İşin güzel yanı, servisi dünyanın herhangibir noktasındaki bir sunucu üzerinde host edebilecek olmamız. Dolayısıyla Excel PowerPivot aracı söz konusu servise bağlanabildiği sürece verinin en güncel halini çekebiliyor olacak. E ne duruyorsunuz? NedirTv? sponsorluğundaki görsel dersimizi izlemeye buyrun.

Süre : 14:52

Boyut : 24.5 Mb

Download etmek veya izlemek için

Screencast - Entity Framework, WCF RIA Services, Silverlight 4.0

Cuma, 19 Mart 2010 22:56 by bsenyurt

Merhaba Arkadaşlar,

Bildiğiniz üzere 2010 yılı Microsoft geliştiricileri açısından epey bir hareketli başladı. Aslında ayak sesleri 2008 yılındaki Profesyonel Geliştiriciler Konferansında(Microsoft PDC) duyulan pek çok ürünün artık nihai sürümlerinin çıkacağı günlere yaklaşıyoruz. Aldığımız duyumlar Nisan ayı içerisinde .Net Framework 4.0 ve Visual Studio 2010 tarafında Relase sürümlerinin en azından RTM sürümlerinin çıkacağı yönünde. 2008 ve belki de daha öncesinden beri süre gelen zaman içerisinde beni en çok şaşırtan ürünlerinden birisi de Silverlight. Daha dün gibi ilk versiyonunu hatırladığımız ürünün geçtiğimiz günlerde MIX2010 ile birlikte 4.0 sürümünün çıktığı duyruldu. Visual Studio 2010 Beta 2 ile kullanıp bakabildiğimiz ama RC sürümünde kullanılamayan sürüm artık kullanılabilir halde Wink Bunun için Silverlight resmi sitesinden gerekli kurulumları yapmanız yeterli olacaktır. Şu aşamada Visual Studio 2010 RC ve Visual Studio 2008 SP1 sürümlerinde ele alabiliyorıuz.

Silverlight tarafında beni en çok ilgilendiren konuların başında ise sunucu kaynaklarının istemci tarafından kullanılabilmesinde önemli bir rol üstelenen WCF RIA Service' ler gelmekte. Eski adıyla .NET RIA Service' lerin WCF Eco System içerisinde WCF RIA Service olarak anılmaya başlandığını biliyoruz. Daha öncesinde WCF RIA Service' ler ile ilişkili çeşitli blog yazılarım oldu ancak görsel anlatım ne denli güçlü olduğunu hepimiz gayet iyi biliyoruz. İşte bu felsefe ile ve NedirTv? desteğiyle hazırladığımız bu görsel dersimizde WCF RIA Service' lerinin kullanımına dair basit bir Hello World uygulaması geliştiriyor olacağız. Üstelik Domain Service tarafında Entity Framework 4.0 sağlayıcısından yararlanarak veri sunumunu gerçekleştireceğiz. İyi seyirler dilerim.

Boyut : 23.2 Mb

Süre : 15:32

Download Etmek veya İzlemek İçin

SilverlightApplication2.rar (2,37 mb) [Örnek Visual Studio 2010 RC sürümü üzerinde geliştirilmiş ve test edilmiştir]

Screencast - WCF Data Services - Projections

Pazartesi, 8 Mart 2010 11:10 by bsenyurt

Merhaba Arkadaşlar,

Yazılımcı bile olsak bu arada sırada spor yapmadığımız anlamına gelmemeli. Bende çalışma arkadaşlarım ile sık sık spor aktivitelerinde bulunuyorum. Zaman zaman parke zeminde o muhteşem NBA yıldızları gibi giyinerek basketbol oynuyor, zaman zaman Ping Pong...Geçtiğimiz günlerde ise bir halı saha maçı organizasyonundaydım. Bu organizasyonun bitmesinden hemen sonra duşumu aldım, günlük kıyafetlerimi giydim ve akşamın sekizinde bilin bakalım nereye gittim...Şirkete Laughing Deli mi bu adam...Eve gitse ya...Amacı ne? Nitekim maç sırasında sürekli aklımda olan ve beni dürten bir mesele vardı. Uzun zamandır çekmek istediğim bir görsel ders için şu saatlerde sessiz olan şirket en ideal mekandı.

NedirTv? aracılığıyla hazırladığımız bu görsel dersimizde, WCF Eco System' in bir parçası olan WCF Data Service' lerinde Projections sorgularının nasıl kullanılabileceğini incelemeye çalışıyoruz. Aslında kod adı Astoria olan Ado.Net Data Service' lerin 1.5 CTP2 sürümünde de duyurulan bu yetenek, zaten .Net Framework 4.0 içerisine gömülü olarak gelen WCF Data Service' ler için standartlaştırılmış bir özellik. Bu amaçla hazırladığımız ve 7,5 dakikayı aşmayan görsel dersimizin faydalı olacağını ümit ediyorum. İyi seyirler dilerim.

Dosya Boyutu : 14.3 Mb

Süre : 7:35

İzlemek veya Download Etmek için