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

.Net RIA Servisleri - Özel Doğrulama(Custom Validation)

Pazar, 31 Mayıs 2009 13:03 by bsenyurt

Merhaba Arkadaşlar,

Bir önceki blog yazımızda, .Net RIA Servislerin kullanıldığı Silverlight uygulamalarında doğrulama(Validation) işlemlerinin nasıl yapılabileceğini incelemeye çalışmıştık. Bu yazımızda ise, Range, Required, StringLength, RegularExpression gibi built-in niteliklerle(attribute) gerçekleştirilen doğrulamalar haricinde kalan özel durumlar için nasıl ilerleyebileceğimizi araştıracağız. Konuyu adım adım irdelersek, aşağıdaki işlemleri yapmamız gerekmektedir.

  • Sunucu uygulama tarafında(Web App) shared niteliği ile işaretlenmiş bir sınıf tasarlanır ve içerisine özel doğrulama operasyonları ilave edilir.
  • Özel doğrulamaların uygulanacağı sınıf veya üyelerine, CustomValidation niteliği yardımıyla geliştirilen Validator tipi bildirilir.

Gördüğünüz gibi gayet basit. Wink Önceki blog yazımızda geliştirdiğimiz örnek proje için bu adımları uygulamaya başlayabiliriz. Örnek olarak ProductName alanı için özel bir doğrulama fonksiyonelliği geliştireceğiz. Bu doğrulamaya göre, ProductName ile ilişkili veri giriş alanı içerisinde Select, Where, Delete gibi SQL kelimelerinin olmamasını sağlamaya çalışacağız. Bu tabiki konunun anlaşılması için öne sürdüğümüz bir senaryo. Şu an için önemli olan, tekniğin nasıl uygulandığıdır. Bu amaçla web projesi tarafında ProductNameValidator.shared.cs isimli bir kod dosyası oluşturarak işe başlayabiliriz. Bu kod dosyasının adında shared kelimesinin eklenmesinin geliştirme ortamı(IDE) içinde özel bir anlamı vardır. SınıfAdı.shared.cs/vb formatında yazılan dosya adı sayesinde, istemci tarafı içinde otomatik kod üretiminin gerçekleştirilmesi sağlanmış olmaktadır.

Söz konusu sınıfın kod içeriği ise aşağıdaki gibidir.

using System.ComponentModel.DataAnnotations;
using System.Web.Ria.Data;

// Dosya adında shared kullanılmasının bir nedeni vardır. Bu isimlendirme standardı sayesinde, derleme zamanı alt yapısının istemci tarafı için otomatik dosya üretimi gerçekleştirmesi sağlanmış olunur.
namespace ValidationSystem.Web
{
    [Shared]
    public static class ProductNameValidator
    {
        public static bool QueryCheck(string productName, ValidationContext context, out ValidationResult result)
        {
            // sembolik olarak eklenmiş kontrol değerleri
            string[] keywords = { "Select", "Where", "Delete", "Create" };

            // ürün adının, yasaklı kelimelerden herhangibirini içerip içermediğine bakılır.
            foreach (string keyword in keywords)
            {
                // Bir tane bile içeriyorsa, ValidationResult nesne örneği oluşturulurken hata mesajı bildirimi yapılır ve geriye false değer döndürülür
                if (productName.Contains(keyword))
                {
                    result = new ValidationResult("Tehlikeli kelimeler yer almakta");
                    return false;
                }
            }
            // Eğer doğrulama işlemi başarılıysa ValidationResult nesne örneğine null değer atanır ve geriye true değer döndürülür
            result = null;
            return true;
        }
    }
}

Static olarak tanımlanan sınıfın shared niteliği ile imzalandığına dikkat edilmelidir. Diğer taraftan doğrulama işlemi için kullanılacak olan metod(metodlar), ilk parametre olarak doğrulanacak veri içeriğini taşıyabilecek tipte bir değişken kullanırlar. ProductName alanı tablo üzerinde nvarchar tipinden tanımlanmış ve bu nedenle Entity içerisinde string olarak ele alınmıştır. Dolayısıyla ilk parametrenin string tipinden tasarlanmış olması doğru bir tercihtir. Diğer taraftan ikinci parametre olarak ValidationContext ve üçüncü parametre olarakta ValidationResult tiplerinden değişkenler tanımlanmıştır. Her ne kadar örneğimizde ValidationContext parametresini kullanmamış olsakta, çalışma zamanında doğrulamaya tabi olan içeriğin sahibi tipe ait bilgileri içerdiğini söyleyebiliriz. Dolayısıyla bu değişken ile, doğrulamaya tabi olan ProductName değerine sahip Products nesne örneğine ulaşabilir ve doğrulamayı farklı açılardan ele alabiliriz. Aşağıdaki ekran görüntüsünde bu durum daha net bir şekilde görülebilmektedir.

Gelelim ValidationResult tipine. Sonuç olarak doğrulamanın başarılı veya başarısız olma durumu söz konusudur. Başarısız olunması halinde, istemci tarafında hata mesajı gibi bilgileri içeren bir nesne örneğinin var olması gerekmektedir. İşte ValidationResult nesne örneğinin üretilmesi ile, doğrulamanın başarısız olması durumunda geriye nasıl bir bilgi döndürüleceği belirtilmektedir. Tabi metodun böyle bir durumda geriye false değer döndürmeside gerekmektedir. Elbetteki doğrulama işleminin başarılı olması halinde geriye true değer döndürülmesi ve ayrıca ValidationResult nesne örneğinin null olarak aktarılması sağlanmalıdır.

Sırada ikinci adım var. Geliştirilen bu doğrulama tipinin, çalışma zamanı tarafından ele alınması gerekmektedir. Tabiki hal böyle olunca devreye niteliklerin(attribute) girmeside kaçınılmazdır. Neyseki kendi niteliklerimizi yazmak yerine, herhangibir validator tipini, istediğimiz özellik veya sınıfa uygulamamızı sağlayan tek bir built-in nitelik mevcuttur. Wink CustomValidation. Dolayısıyla metadata dosyası içerisinde, ProductName özelliğinin aşağıdaki hale getirilmesi yeterli olacaktır.

[Required(ErrorMessage="Lütfen ürün adını giriniz")]
[CustomValidation(typeof(ProductNameValidator),"QueryCheck")]
public string ProductName;

CustomValidation niteliği ilk parametre olarak doğrulama tipini almaktadır. İkinci parametrede ise, takip öden özelliğin(veya sınıfın) kontrolünü gerçekleştirecek olan metod adı belirtilmektedir. Uygulama bu son haliyle derlendiğinde, istemci projesindede aşağıdaki şekilde görülen ek dosyanında üretildiği gözlemlenebilir.

Artık uygulamayı test etmeye başlayabiliriz. Bu amaçla herhangibir ürünün güncellenmeye çalışıldığını düşünelim ve ürün adında Delete kelimesini kullandığımızı varsayalım. İşte sonuç...

Tataaaa!!! Laughing Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

ValidationSystem2.rar (1,86 mb)

.Net RIA Servisleri - Doğrulama(Validation)

Cumartesi, 30 Mayıs 2009 12:01 by bsenyurt

Merhaba Arkadaşlar,

Bildiğiniz gibi bir süredir PLINQ(Parallel Language INtegrated Query) ile ilişkili araştırmalarıma devam etmekteyim. Nevarki dün gece uzun süredir ara verdiğim bir konuyu farkettim. .Net RIA Servisleri. Bunun üzerine yaz sıcaklarının kendini iyiden iyiye hissettirmeye başladığı şu günlerde serinlemek için deniz kenarında bir yerlere gitmeden önce,(Örneğin güzelim Ortaköy sahilinde denize karşı çay içmek gibi) esinti ferahlığı verecek, hafif ve basit bir konuyu araştırmaya niyetlendim. Bunun üzerine, verinin eklenmesi veya güncellenmesi sırasında doğrulama işlemlerinin nasıl yapılabileceğini incelemeye karar verdim. Özellikle Asp.Net veya Windows Forms, WPF gibi, kullanıcı ile etkileşimde olan arayüzlerin kullanıldığı projelerde, verinin çeşitli nedenler ile doğrulanması gerekebilir. Doğruluğu kanıtlanan veri, kaynağa eklenebilir, güncelleştirilebilir. Tabiki verinin doğruluğunu kontrol etmek adına pek çok stratejiden faydalanılabilir. Örneğin, Asp.Net web tabanlı uygulamalarda kullanılan doğrulama işlemleri, istemci tarafında Javascript savunması ile başlayıp sunucu tarafında devam eder. En büyük amaç, maliyeti yüksek olan veri işlemlerinden önce çeşitli kriterlerin sağlandığını kontrol etmek olarak düşünülebilir. Tabiki bu maliyet hesabına, güvenlik kontrolünüde eklediğimizde, doğrulamanın aslında son derece önemli bir cephe olduğu ortaya çıkmaktadır. Doğrulama kontrolleri, bir ürün fiyatının belirli aralıkta olması şeklinde düşünülebileceği gibi, bir kredi kartı numarasının Lhun algoritmasına uygun olup olmadığı gibi karmaşık bir denetim mekanizması olarak ta düşünülebilir. Peki Silverlight, .Net RIA Services ve üretilen Entity tipleri göz önüne alındığında söz konusu doğrulama işlemleri acaba nasıl yapılabilir?

Dilerseniz konuyu basit bir örnek üzerinden ele almaya çalışalım. İşe biraz daha renk katmak adınada Silverlight tarafında DataForm veri kontrolünü kullanabiliriz. Nitekim bu kontrol özellikle doğrulama işlemleri sırasında olan hataları gayet hoş bir biçimde gösterebilmektedir. Tabi işe ilk olarak Silverlight projesini oluşturarak başlamak gerektiğini hepimiz biliyoruz. Söz konusu projede .Net RIA Servislerini ele alacağımızdan, veri erişim katmanında(Data Access Layer) Ado.Net Entity Framework veya LINQ to SQL modellerinden birisini kullanmayı tercih edebiliriz. Ben Ado.Net Entity Framework kullanmayı tercih ettim ve kobay tablo olarak Northwind veritabanında yer alan Products nesnesini seçtim. Buna göre oluşan EDM diagramı aşağıdaki gibidir.

Tabiki bu işlemin ardından Web projesine, Domain Service Class öğesininde eklenmesi gerekmektedir. Bu şekilde .Net RIA Servisin, sunucu tarafındaki kısmı ve build işlemi sonrasındada istemci tarafındaki Domain Context parçası oluşturulmuş olacaktır. Ancak dikkat edilmesi gereken önemli bir nokta vardır.

Şekildende görüldüğü gibi Domain Service Class öğesinin eklenmesi sırasında Generate associated classes for metadata özelliği etkinleştirilmiştir. Bu sayede Web projesine, NorthwindService.metadata.cs isimli bir dosyanın daha eklendiği görülür.

Bu metadata dosyası içerisinde ise, Products isimli Entity sınıfı için üretilmiş partial bir tip daha olduğu görülür. Asıl önemli olan bu partial tip içerisinde, ProductsMetadata ({EnitiyName}Metadata) isimli internal erişim belirleyicisine sahip(yani sadece bulunduğu assembly içerisinde kullanılabilen) bir sınıf daha tanımlanmış olmasıdır. Bu sınıf aynı zamanda sealed bir tiptir. Bir başka deyişle kendisinden türetilme yapılamamaktadır.

Peki bu metadata sınıfını neden ürettirdik? Ne işe yaramaktadır?

Senaryomuzda dikkat edileceği üzere Ado.Net Entity Framework modeli kullanılmaktadır. Bu model, veritabanından seçilen nesnelerin karşılığı olan sınıfların otomatik üretilmesinide içermektedir. Dolayısıyla Products tablosunun karşılığı olan Entity tipi otomatik olarak üretilen bir sınıftır aslında. Aynı durumu LINQ to SQL tarafı içinde geçerlidir. Çok doğal olarak üretilen Entity tipi, modele has nitelikler ile süslenmiştir. Örneğin, özelliğin identity olup olmadığı, hangi entity ile ilişkili olduğu vb... Oysaki doğrulama gibi, ek nitelikler yardımıyla gerçekleştirmek isteyebileceğimiz işlemler, Entity' ye değil, .Net RIA Servis tarafına özeldir. Bu nedenle var olan Entity yapısının bozulması istenmediğinden ve hatta otomatik üretimler sonucu eski haline dönmesi ihtimalide bulunduğundan, ek metadata girişlerinin güvenli bir şekilde yapılması için .Net RIA Servis tarafında ayrı bir sınıf daha üretilmektedir. Yani Entity ile ilişkili yapmak istediğimiz ek işlemleri bu metadata sınıfı içerisinde toplayabiliriz. Dolayısıyla doğrulama işlemleri ile ilgili nitelik tanımlamalarının yapılacağı en uygun yerin, üretilen metadata sınıfları olduğunu söyleyebiliriz. Bizde üretilen bu metadata sınıfı üzerinde aşağıdaki değişiklikleri yapabiliriz.


namespace ValidationSystem.Web
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.Web.Ria;
    using System.Web.Ria.Data;
    using System.Web.DomainServices;
    using System.Data;

    [MetadataTypeAttribute(typeof(Products.ProductsMetadata))]
    public partial class Products
    {

#pragma warning disable 649

        internal sealed class ProductsMetadata
        {
            private ProductsMetadata()
            {
            }

            public int ProductID;

            [Required(ErrorMessage="Lütfen ürün adını giriniz")] // Mutlaka girilmeli(boş geçilemez)
            public string ProductName;

            [Required] // Mutlaka gerekli
            [RegularExpression("^\\d{2}")]  // İki hanel sayısal değer
            public Nullable<int> SupplierID;

            [Required]
            public Nullable<int> CategoryID;

            [StringLength(20,MinimumLength=3)] // minimum 3 karakter maksimum 20 karakter
            [Required]
            public string QuantityPerUnit;

            [Range(0,300)]
            [Required]
            public Nullable<Decimal> UnitPrice;

            [Range(0,1000)]
            [Required]
            public Nullable<short> UnitsInStock;

            public Nullable<short> UnitsOnOrder;

            public Nullable<short> ReorderLevel;

            public bool Discontinued;

            public EntityState EntityState;
        }

#pragma warning restore 649
    }
}

  • Required niteliği tahmin edileceği üzere, söz konusu alanın mutlaka girilmesi gerektiğini, bir başka deyişle boş geçilemeyeceğini belirtmektedir.
  • Range niteliği ile sayısal değer aralığı belirtilir.
  • StringLength niteliği ise, minimum ve maksimum karaketer aralığını belirtir.
  • RegularExpression niteliğinde ise, basit bir RegEx ifadesi kullanılarak, verinin hangi formatta olması gerektiği belirtilir.

Dikkat edileceği üzere bu niteliklerin benzer özellikleri vardır. Örneğin ErrorMessage özelliklerine atanan değerler ile, hata sonrası gösterilecek mesajın içeriği belirlenebilir. Yapılan bu ilaveler sırasında kullanılan niteliklerin(attributes), istemci tarafında üretilen otomatik cs dosyası içerisinde yer alan Products sınıfınada aktarıldığı gözlemlenecektir.

Artık testleri yapmak üzere XAML tarafındaki geliştirmelere başlayabiliriz. Baştada belirttiğimiz gibi DataForm isimli veri bağlı kontrolden yararlanıyor olacağız. Bu kontrol kendi içerisindeki Field' larda verinin nasıl gösterileceğini tanımlayabilmemizede olanak sağlamaktadır. Ayrıca, doğrulama işlemeleri sırasındaki hata mesajlarınıda güzel bir şekilde göstermektedir. İşte MainPage.xaml içeriğimiz;

<UserControl xmlns:dataControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm"  x:Class="ValidationSystem.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Width="550" Height="455">
    <StackPanel x:Name="LayoutRoot" Background="White" Orientation="Vertical">
        <dataControls:DataForm x:Name="dfProducts" Width="550" Height="425"></dataControls:DataForm>
        <Button x:Name="btnSaveChanges" Content="Save Changes" Width="100" HorizontalAlignment="Left" Height="25" Click="btnSaveChanges_Click"/>
    </StackPanel>
</UserControl>

Kod tarafını ise aşağıdaki gibi geliştirmemiz yeterlidir.

using System.Windows;
using System.Windows.Controls;
using ValidationSystem.Web;

namespace ValidationSystem
{
    public partial class MainPage : UserControl
    {
        // DomainContext nesnesi tanımlanır
        NorthwindContext context = null;

        public MainPage()
        {
            InitializeComponent();
            // DomainContext nesnesi örneklenir
            context = new NorthwindContext();
            // Veri kaynağı olarak Products özelliğinin işaret ettiği referans belirtilir
            dfProducts.ItemsSource = context.Products;
            // Ürünler yüklenir
            context.LoadProducts();
        }

        private void btnSaveChanges_Click(object sender, RoutedEventArgs e)
        {           
            if(dfProducts.Mode== DataFormMode.Display)  //Edit veya Inserted moddayken SubmitChanges metodunun verebileceği olası exception' lara karşı alınan geçici tedbirdir
                context.SubmitChanges();        // Değişiklikler sunucu tarafına gönderilir
        }
    }
}

Uygulamayı Start without debugging modda çalıştırarak testlerimize başlayalım. DataForm kontrolü verileri eğer aksi belirtilmediyse, tipe göre TextBox veya CheckBox gibi kontrollerde göstermektedir. Domain Service öğesi oluşturulurken Enable Editing seçeneği işaretlendiğinden, kontrolün sağ üst köşesinde ekleme, düzenleme ve silme işlemleri için gerekli düğmelerinde otomatik olarak üretildiği görülebilir. Bizim ağırlıklı olarak üzerinde duracağımız kısım doğrulam işlemleridir. Örneğin bir ürünü düzenleme moduna aldığımız varsayalım. Bu durumda, doğrulam işlemlerine tabi tutulan alanlara ait TextBlock kontrollerinin Bold olarak işaretlendiği fark edilecektir. Buda kullanıcıya hangi kontrollerin doğrulama denetimi altında olduğunu işaret eden bir ipucu olarak düşünülebilir. Örnek olarak seçilen ürünün adını boş geçmeye çalıştığımızı var sayalım. ProductName alanı ile ilişkili kontrolü terk ettiğimiz anda(başka bir kontrole geçerek), aşağıdaki şekilde görüldüğü gibi ekranın kırmızı fontlu yazılar ile dolduğunu görebiliriz.

Harika. Wink Burada dikkat edilmesi gereken noktalardan birisi hata mesajıdır. Dikkat edileceği üzere, ilgili niteliğin ErrorMessage özelliğine atanan değer gösterilmektedir. Peki ErrorMessage belirtmediklerimizde durum nasıldır? Örneğin UnitPrice değerini 1000 olarak girdiğimizi varsayalım. Range niteliğinde 0 ile 300 arasında bir değer olabileceğini belirtmiştik. Aşağıdaki durum ile karşılaşırız.

Görüldüğü gibi, built-in tasarlanmış standart bir hata mesajı üretilmektedir. Dolayısıyla doğrulama işlemlerini uyguladığımız senaryolarda ErrorMessage özelliğine bir değer atamamız, kullanıcıyı daha anlaşılır bir şekilde bilgilendirmek adına tercih edilmelidir. İyi güzel her şey hoş ama Asp.Net tarafından biliyoruzki, istersek standart doğrulama işlemleri dışındaki ihityaçlarda CustomValidator bileşeninden yararlanabilmekteyiz. Tabi bu durumda, istemci tarafı için gerekli Javascript içeriğini ve sunucu tarafındaki olay metodu kodlarınıda geliştirmemiz gerekmektedir.

Peki .Net RIA Servislerinin kullanıldığı senaryoda, özel doğrulama işlemleri nasıl ele alınabilir? Bu özel doğrulama işlemleri sınıf seviyesinde veya özellik seviyesinde nasıl uygulanabilir? Bir sonraki blog yazımda bu konulara değinmeye çalışıyor olacağım. Tekrardan görüşünceye dek hepinize mutlu bir yaz günü dilerim.

ValidationSystem.rar (1,85 mb)

.Net TV - .Net RIA Servisleri Hello World

Cumartesi, 23 Mayıs 2009 00:45 by bsenyurt

Merhaba arkadaşlar,

Bir süredir .Net RIA Servisleri ile ilişkili araştırmalar yapıyorum ve bildiklerimi sizinle paylaşıyorum. Yazıları desteklemesi açısından giriş seviyesinde bir görsel videoyuda az önce yayınladım. Şu adresten indirip, .Net RIA Servislerinin nasıl kullanıldığını izleyebilirsiniz. Faydalı olması dileğiyle iyi seyirler. 

.Net RIA Servisleri - DomainDataSource Kulanımı

Perşembe, 14 Mayıs 2009 21:30 by bsenyurt

Merhaba Arkadaşlar,

Her ne kadar şu günlerde güzel ülkemizin Ege kıyılarında kısa bir dinlenme molası vermiş olsamda, internetin sahil kıyılarındaki cafe' lere kadar girmiş olması, herşeyi değiştiriyor. Cool Artık bir yaşam tarzı haline gelen Yazılımdan, onun gizemli dünyasından uzak durmak bu nedenle, şu sıralar aşağıdaki şekilde görülen yerde tatilde bile olsam çok zor.

Bu kısa yazımda sizlere yine .Net RIA Servisleri ile ilişkili bilgilerimi aktarmaya gayret edeceğim. Bu seferki konumuz DomainDataSource isimli Silverlight kontrolü. Kontrolün adında yer alan DataSource son eki aslında olayı biraz olsun açıklamakta. .Net RIA Servislerinin kullanıldığı senaryolarda, sunucu tarafında mutlaka bir veri kaynağı yer almaktadır. Ağırlık olarak Ado.Net Entity Framework veya LINQ to SQL tabanlı sağlayıcılar ile eriştiğimiz bu veri kaynaklarını, istemci tarafında değiştirmek gibi işlemlerle uğraştığımızda bir gerçektir. Kısacası, istemci tarafına çekilen verinin sadece gösterilmesi dışında, düzenlenmesi, yenilerinin eklenmesi veya var olanların silinmesi gibi operasyonlar söz konusudur. Bunlara ek olarak, Silverlight tabanlı istemci tarafını düşündüğümüzde, verinin kullanıcı ile etkileşimde olan kontrollerde gösterilmeside bu işin önemli kısımlarından birisidir.

Tam bu noktada aklıma Asp.Net 2.0 ile birlikte gelen veri-bağlı kontrolleri(Data-Bound Controls) geliyor. SqlDataSource, ObjectDataSource, SiteMapDataSource vb...Bu kontrollerin en büyük amacı, sayfa üzerindeki sunucu kontrollerini, veri kaynağını bağlamaktır. SqlDataSource gibi kontroller sayesinde bu bağlama işlemleri ile birlikte, Insert, Update ve Delete operasyonlarına hizmet edecek kod parçalarının kolay bir şekilde geliştirilmesi ve ele alınmasıda mümkün olmaktadır. Şu anda bulunduğumuz noktayı düşündüğümüzde, .Net RIA Servislerini kullanan Silverlight istemcileri içinde benzer bir kolaylığın sağlanması önemlidir. Öyleyse bu kontrol nasıl kullanılır, tam olarak ne işe yarar hemen bir bakalım.

Örnek Silverlight Projemizde bu kez Northwind veri kaynağına Ado.Net Entity Framework öğesi yardımıyla bağlanıyor olacağız. Yine bir önceki blog yazımızda olduğu gibi Categories tablosunu ve ek olarak Products tablosunu kullanabiliriz. İstemci tarafında, Insert, Update ve Delete gibi işlemleride ele alma ihtimalimiz olduğundan(en azından sonraki blog yazılarımda), DomainService tipinin eklenmesi sırasında, her iki Entity tipi içinde Enable Editing özelliğinin işaretli olduğuna dikkat etmemiz gerekmektedir. Gelelim kullanacağımız DomainDataSource bileşenine. Bu bileşen varsayılan olarak Silverlight kontrol sekmesinde görünmemektedir. Dolayısıyla söz konusu kontrolün, .Net RIA Service sisteme yüklendikten sonra Visual Studio 2008 ortamında kullanılabilmesi için Toolbox' a Silverlight Components kısmından eklenmesi gerekir.

DataSource bileşenleri genellikle veri-bağlı kontroller ile kullanılırlar. DomainDataSource kontrolünü bu anlamda, DataGrid bileşeni ile etkileştirebiliriz. XAML içeriğimizin ilk halini aslında aşağıdaki gibi tasarladım.

<UserControl xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls" 
    xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" 
    x:Class="DomainDS.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:ds="clr-namespace:DomainDS.Web"
    Width="500" Height="300">   
    <Grid x:Name="LayoutRoot" Background="White">
        <riaControls:DomainDataSource x:Name="dsProducts" AutoLoad="True" LoadSize="10" LoadMethodName="LoadProducts">
            <riaControls:DomainDataSource.DomainContext>
                <ds:NorthwindContext/>
            </riaControls:DomainDataSource.DomainContext>
        </riaControls:DomainDataSource>
    <data:DataGrid x:Name="dgProducts" Height="Auto" Background="BlanchedAlmond" ItemsSource="{Binding Data, ElementName=dsProducts}"/>
    </Grid>
</UserControl>

Şimdi bu XAML içeriğinde, üzerinde durulması gereken önemli noktalar olduğu aşikardır. Öncelikli olarak DataGrid veya DomainDataSouce kontrollerini kullanabilmemiz için gerekli assembly veya isim alanları(Namespaces), ilgili bileşenleri XAML içerisine sürüklediğimizde, eğer gerekiyorsa projeye otomatik olarak dahil edileceklerdir. riaControls ön eki ile eklenen DomainDataSource elementi içerisinde kullanılan bazı nitelik verileri dikkate değerdir.

LoadSize niteliğine atanan değer, Silverlight uygulaması ilk yüklendiğinde çekilecek olan satır sayısını belirtmektedir. Gerçektende, uygulama bu haliyle çalıştırıldığında, SQL Server Profiler aracından yakalanan sorgu cümlesi aşağıdaki gibidir.

SELECT
[Limit1].[C1] AS [C1],
[Limit1].[ProductID] AS [ProductID],
[Limit1].[ProductName] AS [ProductName],
[Limit1].[SupplierID] AS [SupplierID],
[Limit1].[QuantityPerUnit] AS [QuantityPerUnit],
[Limit1].[UnitPrice] AS [UnitPrice],
[Limit1].[UnitsInStock] AS [UnitsInStock],
[Limit1].[UnitsOnOrder] AS [UnitsOnOrder],
[Limit1].[ReorderLevel] AS [ReorderLevel],
[Limit1].[Discontinued] AS [Discontinued],
[Limit1].[CategoryID] AS [CategoryID]
FROM ( SELECT TOP (10)
 [Extent1].[ProductID] AS [ProductID],
 [Extent1].[ProductName] AS [ProductName],
 [Extent1].[SupplierID] AS [SupplierID],
 [Extent1].[CategoryID] AS [CategoryID],
 [Extent1].[QuantityPerUnit] AS [QuantityPerUnit],
 [Extent1].[UnitPrice] AS [UnitPrice],
 [Extent1].[UnitsInStock] AS [UnitsInStock],
 [Extent1].[UnitsOnOrder] AS [UnitsOnOrder],
 [Extent1].[ReorderLevel] AS [ReorderLevel],
 [Extent1].[Discontinued] AS [Discontinued],
 1 AS [C1]
 FROM [dbo].[Products] AS [Extent1]
)  AS [Limit1]

Sanıyorumki SELECT TOP (10) ifadesi sizlerin dikkatinizden kaçmamıştır. Ancak LoadSize niteliği kaldırılırsa bu durumda SQL tarafında aşağıdaki sorgunun çalıştırıldığı görülecektir.

SELECT
1 AS [C1],
[Extent1].[ProductID] AS [ProductID],
[Extent1].[ProductName] AS [ProductName],
[Extent1].[SupplierID] AS [SupplierID],
[Extent1].[QuantityPerUnit] AS [QuantityPerUnit],
[Extent1].[UnitPrice] AS [UnitPrice],
[Extent1].[UnitsInStock] AS [UnitsInStock],
[Extent1].[UnitsOnOrder] AS [UnitsOnOrder],
[Extent1].[ReorderLevel] AS [ReorderLevel],
[Extent1].[Discontinued] AS [Discontinued],
[Extent1].[CategoryID] AS [CategoryID]
FROM [dbo].[Products] AS [Extent1]

Bu kez tüm Products tablosunun içeriği seçilmektedir. LoadSize özelliğini kullanarak, Silverlight uygulamasının ilk açılışı sırasındaki veri kümesinin yoğunluğunu kontrol altına alabilir ve performansı doğudan etkileyebiliriz. Burada dikkat çeken bir diğer önemli nitelik ise LoadMethodName niteliğine atanan değerdir. Bu değer, istemci tarafında kullanılan DomainContext tipinin içerisinde yer alan yükleme metodunun kendisidir. Örneğimizde bu metod LoadCategories isimli fonksiyondur. Ancak fonksiyon adı text tabanlı olarak yazılmaktadır. Peki, DomainDataSource bileşeni, hangi DataContext nesne örneği içerisindeki LoadCategories metodunu kullanacağını nasıl bilecektir? Undecided Bu sorunun cevabı, DomainDataSource.DomainContext elementi içerisinde verilmektedir. Bu kısımda, ds ön ekli namespace üzerinden NorthwindContext isimli DomainContext nesne referansının tanımlaması yapılmaktadır. Böylece, DomainDataSource bileşeninin kullanacağı DomainContext nesne örneği belirlenmiş olur.

DataGrid bileşeninin söz konusu DomainDataSource kontrolüne bağlanması içinse, ItemsSource niteliğine ilgili değerin atanması yeterlidir. (İtiraf etmeliyim ki, ItemsSource niteliğine atanan değerin yazım stilini, ne kadar dekleratif olsada halen ezbere yazamamaktayım Embarassed ) Uygulamayı bu haliyle çalıştırdığımızda aşağıdaki ekran görüntüsü ile karşılaşmamız son derece muhtemeldir.

Burada önemli olan noktalardan birisi, tanımlamalarım tamamen dekleratif olarak yapılmış olması ve geliştiricinin herhangibir kodlama yapmamış olmasıdır. Daha önceki blog yazılarımda yer alan örnekler göz önüne aldığımızda, CRUD operasyonları için istemci tarafında bazı kodlamalar yaptığımız ortadadır.  Şimdi XAML içeriğimizi biraz daha zengineştirmeye çalışalım. Örneğin, sıralama kriteri ekleyebiliriz. Bunun için SortDescriptor elementinin kullanılması gerekmektedir. Bu element, System.Windows.Ria.Controls.dll assembly' ı içerisinde yer aldığında, isim alanının XAML içeriğinde bildirilmesi gerekir. Bu şekilde başlayan düzenlemelerin son hali aşağıdaki gibidir.

<UserControl xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls"  xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" 
    x:Class="DomainDS.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:ds="clr-namespace:DomainDS.Web"
    xmlns:riaData="clr-namespace:System.Windows.Data;assembly=System.Windows.Ria.Controls"
    Width="500" Height="300">   
    <Grid x:Name="LayoutRoot" Background="White">
        <riaControls:DomainDataSource x:Name="dsProducts" AutoLoad="True" LoadSize="40" LoadMethodName="LoadProducts">
            <riaControls:DomainDataSource.DomainContext>
                <ds:NorthwindContext/>
            </riaControls:DomainDataSource.DomainContext>
            <riaControls:DomainDataSource.SortDescriptors>
                <riaData:SortDescriptor Direction="Descending" PropertyPath="UnitPrice"/>
            </riaControls:DomainDataSource.SortDescriptors>
        </riaControls:DomainDataSource>
    <data:DataGrid x:Name="dgProducts" Height="Auto" Background="BlanchedAlmond" ItemsSource="{Binding Data, ElementName=dsProducts}"/>
    </Grid>
</UserControl>

SortDescription elementi içerisinde yer alan Direction niteliğine atanan değer ile sıralamanın yönü belirtilmektedir. Diğer taraftan PropertyPath niteliğine atanan değer ilede hangi alana göre sıralama yapılacağına karar verilir. Bu ayarlamalara göre Products tablosundan ilk yüklemede 40 adet ürün bilgisi, UnitPrice değerlerine göre ters sırada çekilecektir. Nitekim SQL tarafında çalıştırılan sorguya bakıldığında aşağıdaki cümlenin çalıştırıldığı kolayca tespit edilebilir.

SELECT TOP (40)
[Project1].[C1] AS [C1],
[Project1].[ProductID] AS [ProductID],
[Project1].[ProductName] AS [ProductName],
[Project1].[SupplierID] AS [SupplierID],
[Project1].[QuantityPerUnit] AS [QuantityPerUnit],
[Project1].[UnitPrice] AS [UnitPrice],
[Project1].[UnitsInStock] AS [UnitsInStock],
[Project1].[UnitsOnOrder] AS [UnitsOnOrder],
[Project1].[ReorderLevel] AS [ReorderLevel],
[Project1].[Discontinued] AS [Discontinued],
[Project1].[CategoryID] AS [CategoryID]
FROM ( SELECT [Project1].[ProductID] AS [ProductID], [Project1].[ProductName] AS [ProductName], [Project1].[SupplierID] AS [SupplierID], [Project1].[CategoryID] AS [CategoryID], [Project1].[QuantityPerUnit] AS [QuantityPerUnit], [Project1].[UnitPrice] AS [UnitPrice], [Project1].[UnitsInStock] AS [UnitsInStock], [Project1].[UnitsOnOrder] AS [UnitsOnOrder], [Project1].[ReorderLevel] AS [ReorderLevel], [Project1].[Discontinued] AS [Discontinued], [Project1].[C1] AS [C1], row_number() OVER (ORDER BY [Project1].[UnitPrice] DESC) AS [row_number]
 FROM ( SELECT
  [Extent1].[ProductID] AS [ProductID],
  [Extent1].[ProductName] AS [ProductName],
  [Extent1].[SupplierID] AS [SupplierID],
  [Extent1].[CategoryID] AS [CategoryID],
  [Extent1].[QuantityPerUnit] AS [QuantityPerUnit],
  [Extent1].[UnitPrice] AS [UnitPrice],
  [Extent1].[UnitsInStock] AS [UnitsInStock],
  [Extent1].[UnitsOnOrder] AS [UnitsOnOrder],
  [Extent1].[ReorderLevel] AS [ReorderLevel],
  [Extent1].[Discontinued] AS [Discontinued],
  1 AS [C1]
  FROM [dbo].[Products] AS [Extent1]
 )  AS [Project1]
)  AS [Project1]
WHERE [Project1].[row_number] > 40
ORDER BY [Project1].[UnitPrice] DESC

Oldukça kolay gördüğünüz gibi. .Net RIA Servislerini sisteme yüklediğimizde gelen dökümantasyon içerisinde bu tip bir örnek yapılmaktadır. İlerleyen kısımlarında, verinin çekilmesi işlemi sırasında kullanılabilecek sayfalama(Paging) ve filtreleme(Filtering) seçenekleride örneğe dahil edilmektedir. Size tavsiyem söz konusu dökümantasyonda yer alan örneği incelemeniz olacaktır.

Ben yazımı sonlandırmadan önce sayfalama kriterinide XAML içeriğine dahil etmeye çalışacağım. Bu amaçla, DataPager isimli Silverlight bileşenini XAML içerisine sürüklememiz yeterli olacaktır. MainPage.xaml içeriğinin son hali aşağıdaki gibidir.

<UserControl xmlns:dataControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm"  xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls"  xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" 
    x:Class="DomainDS.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:ds="clr-namespace:DomainDS.Web"
    xmlns:riaData="clr-namespace:System.Windows.Data;assembly=System.Windows.Ria.Controls"
    Width="500" Height="350">   
    <StackPanel x:Name="LayoutRoot" Background="White" Orientation="Vertical">
        <riaControls:DomainDataSource x:Name="dsProducts" AutoLoad="True" LoadSize="40" LoadMethodName="LoadProducts">
            <riaControls:DomainDataSource.DomainContext>
                <ds:NorthwindContext/>
            </riaControls:DomainDataSource.DomainContext>
            <riaControls:DomainDataSource.SortDescriptors>
                <riaData:SortDescriptor Direction="Descending" PropertyPath="UnitPrice"/>
            </riaControls:DomainDataSource.SortDescriptors>
        </riaControls:DomainDataSource>
    <data:DataGrid x:Name="dgProducts" Height="330" Background="BlanchedAlmond" ItemsSource="{Binding Data, ElementName=dsProducts}"/>
    <dataControls:DataPager PageSize="20" Height="20" Source="{Binding Data,ElementName=dsProducts}"></dataControls:DataPager>
    </StackPanel>
</UserControl>

DataPager kontrolü doğal olarak kimi(yani hangi veri kaynağını) sayfalayacağını bilmek zorundadır. Bu nedenle Source niteliğine dsProducts isimli DomainDataSource bileşeni atanmıştır. Diğer taraftan PageSize niteliğine atanan değer ile her sayfada 20 adet satırın gösterileceği belirtilmektedir. Uygulamayı bu haliyle çalıştırdığımızda aşağıdakine benzer bir ekran görüntüsü ile karşılaşmamız muhtemeldir.

Burada dikkat çeken noktalardan biriside LoadSize ile ilk etapta 40 satırın yüklenmesine rağmen, sayfalama içerisinde en çok 80 (4X20) kaydın gösterilebilecek olmasıdır. Bu aslında kayda değer ve incelenmesi gereken bir durumdur. Nitekim SQL tarafında çalıştırılan sorgu cümelelerine dikkatlice bakmak gerekmektedir. İşte eğlence başlıyor. Tongue out

Sayfa ilk yüklendiğinde TOP 40 ile 40 satırlık bir veri bloğunun yüklenmesi sağlanır. PageSize değeri 20 olarak berlilendiğinden 1nci sayfadan 2nci sayfaya geçtiğimizde, SQL tarafında herhangibir sorgu çalıştırılmadığı gözlemlenir.(İyi bir gelişme Wink) Ancak 3ncü sayfaya geçmek istediğimizde, 40 satırlık yükleme boyutunu geçtiğimiz için sunucu tarafında yeni bir SQL sorgusu çalıştırılacak ve row_number değeri 40' ın üzerinde olanlar talep edilecektir. Aşağıdaki SQL cümlesinde görüldüğü gibi...

SELECT TOP (40)
[Project1].[C1] AS [C1],
[Project1].[ProductID] AS [ProductID],
[Project1].[ProductName] AS [ProductName],
[Project1].[SupplierID] AS [SupplierID],
[Project1].[QuantityPerUnit] AS [QuantityPerUnit],
[Project1].[UnitPrice] AS [UnitPrice],
[Project1].[UnitsInStock] AS [UnitsInStock],
[Project1].[UnitsOnOrder] AS [UnitsOnOrder],
[Project1].[ReorderLevel] AS [ReorderLevel],
[Project1].[Discontinued] AS [Discontinued],
[Project1].[CategoryID] AS [CategoryID]
FROM ( SELECT [Project1].[ProductID] AS [ProductID], [Project1].[ProductName] AS [ProductName], [Project1].[SupplierID] AS [SupplierID], [Project1].[CategoryID] AS [CategoryID], [Project1].[QuantityPerUnit] AS [QuantityPerUnit], [Project1].[UnitPrice] AS [UnitPrice], [Project1].[UnitsInStock] AS [UnitsInStock], [Project1].[UnitsOnOrder] AS [UnitsOnOrder], [Project1].[ReorderLevel] AS [ReorderLevel], [Project1].[Discontinued] AS [Discontinued], [Project1].[C1] AS [C1], row_number() OVER (ORDER BY [Project1].[UnitPrice] DESC) AS [row_number]
 FROM ( SELECT
  [Extent1].[ProductID] AS [ProductID],
  [Extent1].[ProductName] AS [ProductName],
  [Extent1].[SupplierID] AS [SupplierID],
  [Extent1].[CategoryID] AS [CategoryID],
  [Extent1].[QuantityPerUnit] AS [QuantityPerUnit],
  [Extent1].[UnitPrice] AS [UnitPrice],
  [Extent1].[UnitsInStock] AS [UnitsInStock],
  [Extent1].[UnitsOnOrder] AS [UnitsOnOrder],
  [Extent1].[ReorderLevel] AS [ReorderLevel],
  [Extent1].[Discontinued] AS [Discontinued],
  1 AS [C1]
  FROM [dbo].[Products] AS [Extent1]
 )  AS [Project1]
)  AS [Project1]
WHERE [Project1].[row_number] > 40
ORDER BY [Project1].[UnitPrice] DESC

Ne yazıkki 20 satır veri çekilmesi gerekmesine rağmen LoadSize özelliği nedeniyle Top 40 kullanımı söz konusudur. ( Undecided Bu açıkçası benim pek beklediğim bir durum değildi.) Peki 4ncü sayfaya geçmek istersek ne olacaktır? Bu durumda row_number değeri 60' ın (3X20 veya 3ncü sayfa X PageSize) üzerinde olan veriler çekilmeye çalışılacaktır.

SELECT TOP (40)
[Project1].[C1] AS [C1],
[Project1].[ProductID] AS [ProductID],
[Project1].[ProductName] AS [ProductName],
[Project1].[SupplierID] AS [SupplierID],
[Project1].[QuantityPerUnit] AS [QuantityPerUnit],
[Project1].[UnitPrice] AS [UnitPrice],
[Project1].[UnitsInStock] AS [UnitsInStock],
[Project1].[UnitsOnOrder] AS [UnitsOnOrder],
[Project1].[ReorderLevel] AS [ReorderLevel],
[Project1].[Discontinued] AS [Discontinued],
[Project1].[CategoryID] AS [CategoryID]
FROM ( SELECT [Project1].[ProductID] AS [ProductID], [Project1].[ProductName] AS [ProductName], [Project1].[SupplierID] AS [SupplierID], [Project1].[CategoryID] AS [CategoryID], [Project1].[QuantityPerUnit] AS [QuantityPerUnit], [Project1].[UnitPrice] AS [UnitPrice], [Project1].[UnitsInStock] AS [UnitsInStock], [Project1].[UnitsOnOrder] AS [UnitsOnOrder], [Project1].[ReorderLevel] AS [ReorderLevel], [Project1].[Discontinued] AS [Discontinued], [Project1].[C1] AS [C1], row_number() OVER (ORDER BY [Project1].[UnitPrice] DESC) AS [row_number]
 FROM ( SELECT
  [Extent1].[ProductID] AS [ProductID],
  [Extent1].[ProductName] AS [ProductName],
  [Extent1].[SupplierID] AS [SupplierID],
  [Extent1].[CategoryID] AS [CategoryID],
  [Extent1].[QuantityPerUnit] AS [QuantityPerUnit],
  [Extent1].[UnitPrice] AS [UnitPrice],
  [Extent1].[UnitsInStock] AS [UnitsInStock],
  [Extent1].[UnitsOnOrder] AS [UnitsOnOrder],
  [Extent1].[ReorderLevel] AS [ReorderLevel],
  [Extent1].[Discontinued] AS [Discontinued],
  1 AS [C1]
  FROM [dbo].[Products] AS [Extent1]
 )  AS [Project1]
)  AS [Project1]
WHERE [Project1].[row_number] > 60
ORDER BY [Project1].[UnitPrice] DESC

Yinede TOP 40 oluşumu söz konusudur. Ancak istediğimiz sonuç alınmıştır. Sayfalama işlemide başarılı bir şekilde gerçekleştirilmiştir.

Böylece geldik bir blog yazımızın daha sonuna. Şimdi müsadenizle biraz dinlenmeye çekileceğim. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

.Net RIA Servisleri - CRUD İşlemleri

Perşembe, 14 Mayıs 2009 07:50 by bsenyurt

Merhaba Arkadaşlar,

Bildiğiniz gibi bir süredir .Net RIA Servisleri ile ilişkili araştırmalarıma devam etmekteyim. Bu yazımızda, .Net RIA Servislerinde Insert, Update ve Delete işlemlerini nasıl yapabileceğimizi basit bir örnek üzerinden adım adım aktarmaya çalışacağım. Daha önceki Hello World örneğimizden farklı olarak, DAL(Data Access Layer) içerisinde LINQ to SQL modelini kullanıyor olacağız. İlk adımımız elbetteki bir Silverlight Application projesi oluşturmak olmalıdır. .Net RIA Servisini kullanacağımız için, projenin oluşturulması sırasında Link to ASP.NET Server Project seçeneğinin işaretli olmasına dikkat edelim. Sonrasında Web projesine bir adet LINQ to SQL öğesi eklemeli ve bağlanmak istediğimiz veri kaynağı üzerinden, kullanmak istediğimiz tablo veya stored procedure' leri diagram üzerine sürüklemeliyiz. Ben Insert, Update ve Delete işlemlerini çok basit bir şekilde ele almak istediğimdeN kullanabileceğim en kolay kobay tabloyu seçtim :) Northwind veritabanında yer alan Categories tablosu. Nitekim sadece CategoryName ve Description alanlarına veri eklemek bizim için yeterli olacaktır.

NOT : Ancak örneği biraz daha ileri seviyede geliştirmeye çalışmanızıda şiddetle tavsiye ederim. Söz gelimi, Categories tablosunda Image tipinden Picture isimli bir alan bulunmaktadır. Bu resim alanı binary tiptedir. Bir başka deyişle Silverlight istemcisinin, kategori resmini seçip sunucu tarafına binary formatta aktarmaya çalışmasıda iyi bir mücadele antrenmanı olarak göz önüne alınabilir. Hatta resmin istemci tarafında bir kontrol içerisinde gösterilmeside söz konusu olabilir.

LINQ to SQL diagramımızın içeriği aşağıdaki şekilde görüldüğü gibi olacaktır.

Bundan sonra yapmamız gereken, DomainService tipinin eklenmesidir. Bu seferki örneğimizde, tüm CRUD operasyonuna ihtiyacımız olacağından, Categories Entity' si için, Enable Editing özelliğinin etkinleştirilmiş olması şarttır.

Bu işlemlerin arkasından CategoryService isimli DomainService sınıfı içerisinde aşağıdaki fonksiyonelliklerin oluşturulduğu görülür.

namespace Editing.Web
{
    using System.Linq;
    using System.Web.DomainServices.LinqToSql;
    using System.Web.Ria;

    [EnableClientAccess()]
    public class CategoryService : LinqToSqlDomainService<NorthwindDataContext>
    {
        public IQueryable<Category> GetCategories()
        {
            return this.Context.Categories;
        }

        public void InsertCategory(Category category)
        {
            this.Context.Categories.InsertOnSubmit(category);
        }

        public void UpdateCategory(Category currentCategory, Category originalCategory)
        {
            this.Context.Categories.Attach(currentCategory, originalCategory);
        }

        public void DeleteCategory(Category category)
        {
            this.Context.Categories.Attach(category, category);
            this.Context.Categories.DeleteOnSubmit(category);
        }
    }
}

GetCategories metodu ile LINQ to SQL sağlayıcısı üzerinden kategorilerin çekilmesi sağlanmaktadır. Bir kategorinin eklenmesi sırasında InsertOnSubmit, silinmesi işleminde DeleteOnSubmit ve son olarak güncellenmesinde ise Attach isimli LINQ to SQL tarafından hazır olarak gelen fonksiyonların kullanıldığı görülmektedir. Tabiki projenin Build edilmesi sonrasında, istemci tarafındada uygun metodları içeren CategoryContext isimli DomainContext tipi ile sunucu tarafındaki Category entity sınıfının karşılığı hazırlanmış olacaktır. Artık tek yapmamız gereken istemci tarafını tasarlamak ve kodlamaktır. Ben tasarım konusunda özürlü olduğumdan, ancak aşağıdaki Silverlight UserControl bileşenini oluşturabilmiş bulunuyorum. Embarassed

MainPage.xaml

<UserControl x:Class="Editing.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Width="400" Height="300">
    <StackPanel x:Name="LayoutRoot" Background="White" Orientation="Vertical">
        <TextBlock Text="Categories" FontSize="12" FontStyle="Italic"/>
        <ComboBox x:Name="cmbCategories" Height="24" SelectionChanged="cmbCategories_SelectionChanged">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Vertical">
                        <TextBlock x:Name="id" Text="{Binding CategoryID}" Foreground="RoyalBlue"/>
                        <TextBlock x:Name="name" Text="{Binding CategoryName}" Foreground="SeaGreen"/>
                        <TextBlock x:Name="description" Text="{Binding Description}" Foreground="Salmon" FontSize="9" FontStyle="Italic"/>
                    </StackPanel>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
        <TextBlock Text="ID"  Foreground="BlueViolet"/>
        <TextBlock x:Name="txtCategoryID"/>
        <TextBlock Text="Name" Foreground="BlueViolet"/>
        <TextBox x:Name="txtCategoryName"/>
        <TextBlock Text="Description" Foreground="BlueViolet"/>
        <TextBox x:Name="txtCategoryDescription"/>
        <StackPanel Orientation="Horizontal" Margin="5">
            <Button x:Name="btnUpdate" Click="btnUpdate_Click" Content="Update" Width="100" Margin="2"/>
            <Button x:Name="btnDelete" Click="btnDelete_Click" Content="Delete" Width="100" Margin="2"/>
            <Button x:Name="btnInsert" Click="btnInsert_Click" Content="Insert" Width="100" Margin="2"/>
        </StackPanel>
    </StackPanel>
</UserControl>

Hemen bu sayfadanın tasarlanma amacını açıklıyayım. ComboBox kontrolümüz içerisinde, sayfanın oluşturulması sırasında yüklenen Category nesne örnekleri yer alacaktır. Bu nesne örneklerine ait CategoryID,CategoryName ve Description alanları DataTemplate şablonun içerisindeki TextBlock kontrollerinin Text özelliklerine bağlanmıştır(Binding). Kullanıcı isterse, sayfanın alt kısmında yer alan TextBox kontrollerini kullanarak yeni bir Category ekleyebilir. Tek yapması gereken, Insert başlıklı Button kontrolüne basmaktır. Diğer taraftan ComboBox kontrolünde bir Category seçildiğinde, buna ait CategoryName ve Description bilgileri ile CategoryID değeri, alt tarafta yer alan kontrollere, istemci tarafındaki DomainContext tipinin ilgili Categories özelliği üzerinden getirilmektedir. Kullanıcı bu işleyişi güncelleme sırasında değerlendirebilir. Bilgiler üzerinde gerekli değişiklikleri yaptıktan sonra Update düğmesini kullanması yeterlidir. Benzer şekilde silme işlemi içinde sadece ve sadece Delete düğmesini ele alabilir.

NOT: Tabi bu örnekte, silinmek istenen Category bilgisinin, sunucu tarafında başka birisi tarafından silinmiş olma durumu söz konusu olabilir. Yada var olan bir kaydı güncellemek isteyen kullanıcıdan önce başka birisi güncelleştirmiş olabilir ve o anki kullanıcı eski veriye bakıyor olabilir. Sanıyorumki nereye varmak istediğimi biraz anladınız. Eş zamanlı olarak birbirlerinden habersiz bir şekilde veriyi ektiliyen istemcilerin olduğu vaka. Bu tip bir vaka .Net RIA Servislerinin kullanıldığı bir ortamda son derece olasıdır. Önemli olan noktalardan birisi, SQL tarafında çalıştırılan sorgulardaki Where kriteridir. Where kriterinde varsayılan olarak nasıl bir yaklaşım sergilenmektedir? Bunu ilerleyen kısımlarda görmeye çalışacağız.

Gelelim kod tarafına...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Editing.Web;

namespace Editing
{
    public partial class MainPage : UserControl
    {
        CategoryContext context = new CategoryContext();

        public MainPage()
        {
            InitializeComponent();

            cmbCategories.ItemsSource = context.Categories;
            context.LoadCategories();
        }

        private void cmbCategories_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (e.AddedItems.Count>0
                && e.AddedItems[0]!=null)
            {
                Category selectedCategory = (Category)e.AddedItems[0];

                txtCategoryID.Text = selectedCategory.CategoryID.ToString();
                txtCategoryName.Text = selectedCategory.CategoryName;
                txtCategoryDescription.Text = selectedCategory.Description;
            }
        }


        private void btnUpdate_Click(object sender, RoutedEventArgs e)
        {
            // Güncellenmek istenen category nesne örneği, DataContext referansının Categories koleksiyonu üzerinden çekilir.
            if (!String.IsNullOrEmpty(txtCategoryID.Text))
            {
                Category category = context.Categories.Single<Category>(c => c.CategoryID == Convert.ToInt32(txtCategoryID.Text));

                // Güncellenmek istenen Category nesne örneğinin CategoryName ve Description alanlarına ilgili değerler aktarılır
                category.CategoryName = txtCategoryName.Text;
                category.Description = txtCategoryDescription.Text;
            }

            // Değişiklikler sunucu tarafına gönderilir.
            context.SubmitChanges();
        }

        private void btnDelete_Click(object sender, RoutedEventArgs e)
        {
            // Silinmek istenen Category tipi ComboBox içerisinden seçildikten sonra
            // ilk olarak DataContext tipi içerisindeki koleksiyondan çıkartılır.
            context.Categories.Remove((Category)cmbCategories.SelectedItem);

            // Değişiklikler sunucu tarafına gönderilir
            context.SubmitChanges();

            // Silme işleminden sonra sunucu tarafından Categories tablosunun son içeriği alınır
            context.LoadCategories();

            // Kontrollerin içeriği temizlenir
            txtCategoryDescription.Text = "";
            txtCategoryName.Text = "";
            txtCategoryID.Text = "";
        }

        private void btnInsert_Click(object sender, RoutedEventArgs e)
        {
            // Yeni Category tipi örneklenir
            Category newCategory = new Category {
                CategoryName = txtCategoryName.Text
                , Description = txtCategoryDescription.Text };

            // Örneklenen Category tipi, DataContext üzerindeki Categories koleksiyonuna eklenir.
            context.Categories.Add(newCategory);

            // Değişiklikler onaylanır ve sunucu tarafına aktarılır
            context.SubmitChanges();
        }
    }
}

Artık testlerimize başlayabiliriz. Burada Insert, Update ve Delete gibi işlemler söz konusu olduğundan ve sunucu tarafında SQL kullanıldığından, arka planda çalıştırılan sorgu cümlelerini eminimki sizde en az benim ettiğim kadar merak ediyorsunuzdur. Bu nedenle SQL Server Profiler aracımızda bir yandan açık duruyor olacak. Wink

İlk karşılaşacağımız ekran görüntüsü aşağıdakine benzer olacaktır.

Görüldüğü gibi ComboBox içerisinde, kategorilerin tamamı yer almaktadır. Eğer kullanıcı herhangibir öğeyi seçerse aşağıdaki ekran görüntüsünde olduğu gibi, o kategoriye ait bilgiler gelecektir.

Bu sırada ekrana gelen veri içeriğini değiştirdiğimizi ve sonrasında Update tuşuna bastığımızı düşünelim. Örnek olarak kategori adı ve açıklamalarının sonuna üç nokta koyduğumuzu varsayalım. Bu durumda SQL tarafında aşağıdaki sorgunun çalıştırıldığını görebiliriz.

exec sp_executesql N'UPDATE [dbo].[Categories]
SET [CategoryName] = @p2, [Description] = @p3
WHERE ([CategoryID] = @p0) AND ([CategoryName] = @p1)',N'@p0 int,@p1 nvarchar(10),@p2 nvarchar(13),@p3 ntext',@p0=2,@p1=N'Condiments',@p2=N'Condiments...',@p3=N'Sweet and savory sauces, relishes, spreads, and seasonings...'

Hemen dikkatimi çeken bir noktayı vurgulamak istiyorum. Sadece CategoryName ve Description alanlarını güncelleştirdik. Bu nedenle SQL tarafında yürütülen Update sorgusunda yanlızca bu alanlar yer almaktadır. Picture alanı için herhangibir ifade bulunmamaktadır. Bu neden önemlidir? n tane alandan oluşan bir tablonun kullanıldığı düşünüldüğünde, sadece bir alan için güncelleştirme yapılıyorsa, tüm alanların sorguya dahil edilmesi yerine sadece ilgili olanın eklenmesi söz konusudur...Mu acaba? Bunu test etmek son derece kolaydır aslında. Sadece CategoryName alanın değerini güncelleştirdiğinizi düşünelim. Bu durumda SQL profiler ile yaklanan sorguya bakarsak eğer, Description özelliğine, txtCategoryDescription kontrolünün değişmeyen içeriğini aktarmış olsak bile, sadece CategoryName alanının sorguya dahil edildiğini görebiliriz. Bu bizim için oldukça iyi bir haber aslında.

Yeni bir kategori eklenmek istendiğindeyse,

sunucu tarafında aşağıdaki SQL sorgusu çalıştırılacaktr.

exec sp_executesql N'INSERT INTO [dbo].[Categories]([CategoryName], [Description], [Picture])
VALUES (@p0, @p1, @p2)

SELECT CONVERT(Int,SCOPE_IDENTITY()) AS [value]',N'@p0 nvarchar(5),@p1 ntext,@p2 image',@p0=N'Kitap',@p1=N'Bisiklet nasıl sürülür, nasıl monte edilir :)',@p2=NULL

Sorgu cümlesinde standart bir Insert ifadesi olmasının dışında, eklenen kayıt için üretilen Identity değerinin, SCOPE_IDENTITY() fonksiyonundan yararlanılarak geriye döndürüldüğü gözden kaçırılmamalıdır. Öyleki, yeni eklenen satıra ait bilgiler ComboBox kontrolüne otomatik olarak bağlanırken, CategoryID değerininde sunucudan alındığı rahatlıkla gözlemlenebilir.

Silme işlemi için bir kategorinin seçilmesi gerekmektedir. Seçilen kategoriye ait Category nesne örneği bulunduktan sonra ise Remove metodu ile DomainContext içerisindeki koleksiyondan çıkartılır. Sonrasında ise değişikleri sunucu göndermek için yine SubmitChanges metodundan yararlanılır. Sonuç itibariyle SQL sunucusuna giden sorgu cümlesi aşağıdaki gibidir.

exec sp_executesql N'DELETE FROM [dbo].[Categories] WHERE ([CategoryID] = @p0) AND ([CategoryName] = @p1)',N'@p0 int,@p1 nvarchar(5)',@p0=12,@p1=N'Kitap'

Sorguda dikkat çeken en önemli nokta Where kriterine, Image tipinden olan Picture ile ntext tipinden olan Description dışındaki tüm alanların dahil edilmesidir. Bu bir anlamda eş zamanlı çakışmaların önüne geçilmesini sağlamaktadır ki aynı durum Update için çalıştırılan SQL sorgusunda da geçerlidir. Bilmem farketmiş miydiniz? Laughing

Böylece geldik bir yazımızın daha sonuna. Bu kısa yazıda, .Net RIA Servislerinde Insert,Update ve Delete işlemlerini basit bir biçimde ele almaya ve arka planda hareket eden SQL cümleciklerine bakıldığında gözümüze çarpan önemli noktaları vurgulamaya çalıştım. .Net RIA Servisleri ile ilişkiki araştırmalarıma devam ettikçe sizlerle paylaşıyor olacağım. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Editing.rar (1,14 mb)

.Net RIA Servisleri - Hello World

Çarşamba, 13 Mayıs 2009 22:29 by bsenyurt

Merhaba Arkadaşlar,

Hatırlayacağınız gibi bir önceki blog yazımda, .Net RIA Servisleri hakkında edindiğim kısa ve özet teorik bilgileri sizinle paylaşmaya çalışmıştım. Bu yazımda ise, teoriği pratiğe dökmeye gayret edeceğim. Geliştireceğimiz örnek, .Net RIA Servisini kullanan bir Silverlight uygulaması olacak. Geliştirmeyi Visual Studio 2008 üzerinde, Silverlight 3.0 ortamını kullanarak gerçekleştireceğim. Bu nedenle aşağıdaki şekilde görüldüğü gibi, klasik bir silverlight projesi oluşturarak işe başlayabiliriz.

Bu işlemin ardından ekrana gelen aşağıdaki pencerede,

LINQ to ASP.Net Server Project seçeneğinin işaretli olması önemlidir. Böylece, .NET RIA Servisi için gerekli ön hazırlığın yapılması sağlanmış olur. Elbetteki bunu seçmediğimiz takdirde elimiz kolumuz bağlı değildir. Daha sonradan istenirse, Silverlight uygulamasının özelliklerinden, .Net RIA Servisi destekleyecek şekilde değişiklikler yapılabilir. Yapmış olduğumuz bu işlemlerin sonrasında, HelloRIAServices isimli Silverlight uygulaması sunum mantığını(Presentation Logic) içeren istemci tarafını(client-tier) oluştururken, HelloRIAServices.Web isimli web uygulaması ise, iş mantığını(Business Logic) içeren orta katmanı(mid-tier) oluşturmaktadır. Bu sebepten, veriye erişimi sağlayacak olan LINQ to SQL(veya Ado.Net Entity Framework) öğeleri, Asp.Net Web uygulaması üzerinde yer alacaktır. Aynı şekilde DomainService sınıfıda, Asp.Net Web uygulaması üzerinde konuşlandırılacaktır. Tahmin edileceği üzere, servis için istemci tarafından gönderilecek çağrıları ele alacak olan içerik sınıfı ise(DataContext), Silverlight uygulaması tarafında yer almalıdır.

Bu işlemlerin ardından, DomainService' in erişip istemci tarafına sunacağı veri kümesini oluşturmamız gerekmektedir. Burada, veriye erişmek amacıyla(Data Access Layer tarafı olarak düşünebiliriz), Ado.Net Entity Framework öğesini veya LINQ to SQL sınıflarını kullanabileceğimizi belirtmiştik. Ben örneğimizde, Ado.Net Entity Framework' ü kullanarak, Northwind veritabanı üzerinden aşağıdaki şemaya sahip olan tabloları kullanmayı planlıyorum. Bu noktada şunu hatırlatmakta yarar var. Ado.Net Entity Framework veya LINQ to SQL kullanımı, .Net RIA Servisleri açısından bakıldığında bir zorunluluk yada şart değildir. Dolayısıyla farklı veri kaynaklarını kullanabilir(Örneğin XML tabanlı...) ve istemci tarafına bir DomainService üzerinden sunabiliriz.

Şunu hemen belirteyim; EDM içeriğini Asp.Net Web Project üzerinde oluşturmalıyız. EDM diagramından görüldüğü üzere Categories, Products ve Suppliers tabloları için gerekli Entity tipleri otomatik olarak üretilmiştir. Böylece, veriye erişimi sağlayacak olan katmanı bir nevi hazırlamış bulunuyoruz. Bu işlemin ardından proje bir kere derlendikten sonra, istemciye veriyi sunacak olan DomainService içeriğinin hazırlanmasına başlanabilir; ki buda son derece kolaydır Laughing Tek yapmamız gereken, yine web uygulaması projesi içerisine, aşağıdaki şekildende görüldüğü gibi bir DomainService öğesi eklemektir.

(Kullandığım sistemdeki kurulumdan kaynaklanan bir sorun olsa gerek, ikon ne yazıkki görünmüyor Undecided )

Bu seçimin ardından karşımıza aşağıdaki iletişim kutusu gelecektir.

Burada Categories ve Products tipleri işaretlenmiştir. Bu tiplerin hiç birisi için Insert, Update veya Delete operasyonu hazırlanmayacaktır. Ancak bu operasyonlarında hazırlanmasını istersek, Enable Editing özelliklerini işaretlememiz yeterlidir. Dikkat edileceği üzere, Available DataContexts/ObjectContexts kısmında az önce oluşturulan NorthwindEntities isimli Ado.Net Entity Framework tipi seçilidir. Taşlar yavaş yavaş yerine oturmaktadır. Artık, DomainService sınıfı hazırdır ve veriyi sunmak için, DAL içerisinde oluşturulan Entity içeriğine bağlanmıştır. Bu noktada biraz durup, oluşturulan tipleri incelemekte yarar olacağını düşünüyorum. Web uygulaması içerisindeki sınıf diagramını(Class Diagram) açtığımızda aşağıdaki şekilde yer alan tiplerin oluşturulduğunu görürüz.

Products, Suppliers, Categories isimli sınıflar, Northwind veritabanında seçtiğimiz aynı isimli tabloların karşılıkları olan Entity tipleridir. NorthwindEntities sınıf ise, söz konusu tiplere ait koleksiyonları içerisinde özellik bazında tutmakta ve ekleme gibi temel fonksiyonellikleri içermektedir. Buraya kadarki tipler, veri erişim mantığını içeren parçalar olarak düşünülebilir. NorthwinDomainService sınıfı ise asıl üzerinde odaklanmamız gereken tiptir. Şimdi bu tip ile ilişkili analizlerimizi değerlendirelim.

Herşeyden önce, LinqToEntitesDomainService<T> isimli generic ve abstract bir sınıftan türetildiğini görüyoruz. T tipi olarak örneğimizde, Ado.Net Entity Framework tarafında ürettiğimiz, NorthwindEntities tipi yer almakta. Buna göre, söz konusu DomainService sınıfının, hangi veri içeriğini(DataContenxt) ve üyelerini kullanacağı belirlenmiş oluyor. Burada dikkat çekici noktalardan biriside, DomainService sınıfının türediği tipe ait generic kısıtlamadır.

namespace System.Web.DomainServices.LinqToEntities
{
    public abstract class LinqToEntitiesDomainService<T>
      : LinqToEntitiesDomainService where T : System.Data.Objects.ObjectContext
    {
        protected LinqToEntitiesDomainService();

        protected T Context { get; }
    }
}

Koddanda görüleceği üzere T tipinin ObjectContext' ten türeme zorunluluğu bulunmaktadır. Buda geliştiricilere bir bağımsızlık getirmektedir. Yani, ObjectContext sınıfından türeteceğimiz özel tipler sayesinde farklı veri içeriklerinide DomainService içerisinde ele alabiliriz.

Bir diğer nokta, DomainService sınıfı içerisinde sadece GetProducts ve GetCategories isimli metodların yer almasıdır. Her iki metodda IQueryable<T> tipinden referans döndürmektedir. Kod içeriğine baktığımızda durum biraz daha netleşmektedir.

namespace HelloRIAServices.Web
{
    using System.Linq;
    using System.Web.DomainServices.LinqToEntities;
    using System.Web.Ria;
   
    [EnableClientAccess()]
    public class NorthwindDomainService : LinqToEntitiesDomainService<NorthwindEntities>
    {
        public IQueryable<Categories> GetCategories()
        {
            return this.Context.Categories;
        }

        public IQueryable<Products> GetProducts()
        {
            return this.Context.Products;
        }
    }
}

Her iki metodda basit olarak Context referansına gitmekte ve Categories ile Products koleksiyonlarının içeriklerini istemci tarafına döndürmektedir. Dönüş tipleri IQueryable olduğundan, istemci tarafında LINQ ifadeleri ile sorgulanmaya devam edilmeleri pekala mümkündür. Bunlara ek olarak çok daha önemli bir nokta vardır. Metodlara istenirse parametre verilebilir ve geriye döndürülecek içerik ile ilişkili bazı kısıtlamalar yaptırılabilir. Bir başka deyişle geliştirici, metodların parametrik yapısı ile oynayabileceği gibi, dönüş içeriğini IQueryable<T> olmasına(IEnumerable<T> da olabilir) dikkat edecek şekilde değiştirebilir. Söz gelimi, belkide istemcinin bulunduğu lokasyondaki tedarikçiye göre bir Products veya Categories içeriğinin döndürülmesi sağlanabilir. Yada çok basit anlamda, içeriklerin örneğin ürün adına veya kategori adına göre sıralanarak döndürülmesi sağlanabilir. Bu nedenle kod içeriğini aşağıdaki gibi değiştirmeye karar verdim.

namespace HelloRIAServices.Web
{
    using System.Linq;
    using System.Web.DomainServices.LinqToEntities;
    using System.Web.Ria;

    [EnableClientAccess()]
    public class NorthwindDomainService
 : LinqToEntitiesDomainService<NorthwindEntities>
    {
        public IQueryable<Categories> GetCategories()
        {
            // Lamda operatörü ve extension method yardımıyla
            return this.Context.Categories.OrderBy(c => c.CategoryName);
        }

        public IQueryable<Products> GetProducts()
        {
            // basit bir LINQ ifadesi yardımıyla
            return (from p in this.Context.Products
                    orderby p.ProductName descending
                    select p);                   
        }
    }
}

Tabiki bu değişiklikler ile sınırlı değiliz. İstersek, DomainService sınıfı içerisine farklı fonksiyonelliklerde ekleyebiliriz. Örneğin;

public IQueryable<Products> GetProductsByCategory(int categoryId)
        {
            return (from p in this.Context.Products
                    where p.Categories.CategoryID == categoryId
                    orderby p.ProductName
                    select p);
        }

gibi.

Son olarak DomainService sınıfı içerisinde dikkat çeken bir noktayı daha vurgulayalım. Sınıfın kendisine EnableClientAccess isimli bir nitelik(attribute) uygulanmıştır. Bu nitelik, söz konusu sınıfın istemci katmanından görünebileceği anlamına gelmektedir.

Bu adımların ardından Solution tamamıyla derlenirse ve Silverlight uygulamasının öğelerine Show All Files seçeneği ile bakılırsa, aşağıdaki şekilde görülen bir dosyanın üretildiği farkedilebilir.

Buradaki kod dosyası, DomainService sınıfı her değiştiğinde ve bu nedenle Web projesi her derlendiğinde otomatik olarak yeniden üretilmektedir. Söz konusu kod dosyası içerisinde, servisten sunulan her bir Entity tipi için karşılık olan bir sınıf bulunmaktadır.


Şekildende görüleceği gibi, Ado.Net Entity Framework kullanarak servis üzerinden sunulan Categories ve Products tipleri için, istemci tarafında birer sınıf üretilmiştir. Ayrıca, daha önceki yazımızda da bahsettiğimiz gibi, .NET RIA Servislerinin önemli iki parçasından birisi olan DomainContext türevli bir sınıfda(NorthwindDomainContext) oluşturulmuştur. Servis tarafında(DomainService içerisinde) yer alan GetProducts ve GetCategories metodlarına karşılık olarak, istemci tarafındaki DomainContext tipi içerisine LoadProducts ve LoadCategories fonksiyonları hazırlanmıştır. Yine özel olarak eklediğimiz GetProductsByCategory metoduna karşılık olarak, DataContext tarafında LoadProductsByCategory isimli fonksiyon üretilmiştir. Dolayısıyla, Silverlight uygulamasında, servis ile konuşulmasını sağlayacak olan proxy içeriği otomatik olarak üretilmiştir. Aslında orta katmanda(mid-tier) yer alan her bir DomainService tipi için, sunum katmanında bir DomainContext tipi var olacaktır. Yani, birden fazla veri kaynağına, farklı DAL öğeleri ile çıkan servisleri barındıran bir sunucu ile bunları ayrı ayrı kullanabilen bir istemci tasarlanması mümkündür. Artık tek yapmamız gereken, DomainContext tipinin ilgili fonksiyonlarını kullanarak istemci tarafını geliştirmektir. Bu amaçla Silverlight uygulamasının MainPage.xaml içeriğini aşağıdaki gibi geliştirdiğimizi düşünelim.

<UserControl x:Class="HelloRIAServices.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
    Width="500" Height="320">
    <StackPanel x:Name="LayoutRoot" Background="White" Orientation="Vertical">
        <ComboBox x:Name="cmbCategories" Height="50" VerticalAlignment="Top" SelectionChanged="cmbCategories_SelectionChanged">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Vertical">
                        <TextBlock x:Name="categoryId" Text="{Binding CategoryID}" FontSize="12" FontFamily="Calibri" Foreground="Blue"/>
                        <TextBlock x:Name="categoryName" Text="{Binding CategoryName}" FontSize="12" FontFamily="Calibri" Foreground="Black"/>
                        <TextBlock x:Name="categoryDescription" Text="{Binding Description}" FontStyle="Italic" FontSize="9" FontFamily="Calibri" Foreground="LimeGreen"/>
                    </StackPanel>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
        <data:DataGrid x:Name="grdProducts" Height="250" Background="Lavender" BorderBrush="CadetBlue"/>
    </StackPanel>
</UserControl>

UserControl içerisinde bir adet ComboBox ve DataGrid bileşeni bulunmaktadır. DataGrid bileşeninin kullanılabilmesi için Silverlight uygulamasına System.Windows.Controls.Data.dll assembly' ının referans edilmesi gerekmektedir. Ayrıca DataGrid kulanımı için, XAML içerisinde gerekli namespace tanımlamasıda yapılmalıdır. Sayfanın kullanımı son derece basit olacaktır. ComboBox içeriği, MainPage yapıcı metodu içerisinde, kategoriler ile doldurulacaktır. Kullanıcı, ComboBox içerisinden herhangibir kategoriyi seçtiğinde ise, buna bağlı ürün listeside DataGrid kontrolünde gösterilecektir. ComboBox kontrolüne ait veri içeriğinde, bir DataTemplate kullanılmaktadır ve dikkat edileceği üzere Categories isimli Entity tipinin CategoryID, CategoryName ve Description özellikleri kullanılarak bir şablon oluşturulmuştur. MainPage UserControl' üne ait kod içeriği ise aşağıdaki gibidir.

using System.Windows.Controls;
using HelloRIAServices.Web;

namespace HelloRIAServices
{
    public partial class MainPage : UserControl
    {
        // DomainContext nesnesi
        NorthwindDomainContext context = null;

        public MainPage()
        {
            InitializeComponent();
            // DomainContext nesnesi örneklenir
            context = new NorthwindDomainContext();
            // ComboBox kontrolüne veri kaynağı olarak, EntityList tipinden olan Categories özeliği bağlanır.
            cmbCategories.ItemsSource = context.Categories;
            // DataGrid kontrolü için veri kaynağı DomainContext nesne örneğindeki Products özelliği ile belirlenir
            grdProducts.ItemsSource = context.Products;

            // Categories listesi LoadCategories metodu ile yüklenir.
            context.LoadCategories();
        }

        private void cmbCategories_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            // Seçilen öğe Categories tipinden olduğu için CategoryID özelliğine aşağıdaki kod parçasında olduğu gibi ulaşılabilir.
            int selectedCategoryId=((Categories)e.AddedItems[0]).CategoryID;

            // Eğer aşağıdaki temizleme işlemini uygulamassak, Grid kontrolü içerisinde veriler arka arkaya eklenerek çoğalır.
            context.Entities.GetEntityList<Products>().Clear();

            // LoadProductsByCategory metoduna, seçili kategorinin CategoryID değeri gönderilerek, bağlı olan ürün listesinin yüklenmesi sağlanır.           
            context.LoadProductsByCategory(selectedCategoryId);           
        }
    }
}

Aslında kod içeriği son derece basittir. .Net RIA Servisleri açısından olaya baktığımızda iki önemli nokta göze çarpmaktadır. İlk olarak veri bağlı kontrolleri, Entity içeriklerine bağlamak için DomainContext nesne örneğine ait özelliklerden yararlanılmaktadır(Categories, Products gibi). Diğer taraftan veriyi doldurmak için, bu isteğin sunucu tarafındaki DomainService tipine ulaştırılması gerektiği de ortadadır. Bu sebepten LoadCategories ve LoadProductByCategory metodlarından yararlanılmaktadır. Sonuç olarak uygulama çalışma zamanında test edildiğinde aşağıdaki örnek çıktılar ile karşılaşılacaktır.

Uygulama ilk çalıştırıldığında kategoriler, ComboBox bileşeni içerisine yüklenecektir.

Herhangibir kategori seçildiğinde ise...

DataGrid kontrolü, bu kategoriye bağlı ürünler ile doldurulacaktır. İşte bu kadar. Görüldüğü gibi, Silverlight uygulamalarında .Net RIA Servislerini kullanılarak, çok katmanlı modelin(n-tier), basitçe iki katmana(2-tier) indergenmesi sağlanabilmektedir. Geliştirdiğimiz örnek göz önüne alındığında, aşağıdaki şekil durumu biraz daha açıklığa kavuşturmaktadır.

Böylece geldik bir yazımızın daha sonuna. .Net RIA Servisleri ile ilişkili araştırmalarıma devam ettikçe, öğrendiklerimi sizlerle paylaşmaya devam ediyor olacağım. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

HelloRIAServices.rar (1,60 mb)

.Net RIA Servisleri Nedir?

Cuma, 8 Mayıs 2009 22:41 by bsenyurt

Merhaba Arkadaşlar

Son yıllarda bildiğiniz üzere Servis Tabanlı Uygulamalar(Service Oriented Applications) hayatımızda oldukça fazla yer kaplamaya başladı. Microsoft cephesinden olaya baktığımızda, en büyük sıçramanın Windows Communication Foundation ile .Net Framework 3.0' da yaşandığını söyleyebiliriz. WCF' in getirdiği servis bazlı uygulama geliştirme yaklaşımı, .Net Framework 3.5 ile dahada zenginleşti. Eklenen Web programlama modeli(Web Programming Model) özellikleri sayesinde, REST(Representational State Transfer) bazlı servislerin geliştirilebilmesinin yolu açıldı. Sonrasında Workflow Foundation ile iç içe geçen WCF özellikleri sayesinde, iş akışlarının farklı domainler ile haberleşebilmesi veya servis gibi sunulabilmesi olanaklı hale geldi. Derken .Net Framework 3.5 Service Pack 1 ile hayatımıza başka bir kavram daha girdi. Ado.Net Data Services. Bu model ile, Ado.Net Entity Framework veya LINQ(Language INtegrated Query) bazlı sağlayıcılar üzerinden verinin REST tabanlı olarak sunulabilmesi mümkün hale geldi.Tabi bu geçişler sırasında Client Application Services ve Azure gibi kavramlar ile geliştiricinin hayatını kolaylaştıran REST Starter Kit gibi pek çok yeni fikir ve vizyon ile karşılaştık. Ama Microsoft cephesindeki yenilikler tüm hızıyla sürmeye devam etti, ediyor, edecek...Laughing Bir süredir .Net Framework 4.0 ve bu etapta WF 4.0&WCF 4.0 yeniliklerini incelemekteyim. Ancak arada kaçırdığım önemli bir konu var. .Net RIA(Rich Internet Application) Services ve Silverlight Embarassed Dolayısıyla bu yazımda sizlere, .Net RIA Servisleri ile ilişkili öğrendiklerimi ve bilgilerimi aktarmaya çalışıyor olacağım.

En nihayetinde, Silverlight sayesinde istemci tarafında çok zengin içeriklere sahip olabilecek ve tarayıcı tabanlı(ve hatta Silverlight 3.0 sonrası masaüstü...) uygulamaların geliştirilmesi mümkün. Ancak Silverlight gibi bir uygulama geliştirme modelinde, istemcinin sunucu üzerinde yer alan bazı veri kaynaklarına erişmesi için, servislerin kullanılmasıda kaçınılmaz bir gerçek. (Nedenini biraz sonra daha iyi anlatabileceğim.)

Özellikle Silverlight 3.0 ve .Net RIA Service çıkana kadar, geliştiricilerin sunucu verilerine erişmesi için biraz daha fazla kodlama yapması gerekmektedir. Aslında olaya sadece Silverlight değil, Asp.Net Ajax gibi istemciler açısından bakıldığında da, benzer kodlama süreçleri söz konusudur. Bu tip RIA uygulamalarını, n-tier tarzı mimariler ile geliştirmek istediğimizden, aslında sunum(Presentation) katmanının standart Asp.Net modelinden farklı olarak, tamamen istemci tarafına yıkıldığı oldukça önemli bir noktadır. Sanıyorum burada biraz kafaları karıştırdım. UndecidedGelin olayı standart n-tier modelin Asp.Net uygulamalarındaki genel kullanımı ile analiz etmeye başlayalım. Aşağıdaki şekilde bu model vurgulanmaya çalışılmaktadır.

Klasik olarak bir Asp.Net Web uygulamasında(çoğunlukla Asp.Net Ajax içinde benzer durum söz konusudur), katmanların tamamı sunucu üzerinde yer alır. Uygulama mantığı(Application Logic-Business Layer), veriye erişim katmanı(Data Access Layer) ve istemcinin göreceği HTML çıktının üretileceği sunum katmanı(Presentation Layer). Bunlara ek olarak web uygulaması içerisinde, veri erişim katmanından dış servisler yardımıyla farklı kaynaklara gidilebilir veya uygulamanın kendisinin farklı alanlardaki programlara sunacağı bir takım hizmetler/servisler olabilir. Oldukça basit ve kullanışlı. Ancak, günümüz uygulamalarında ve özellikle son yıllarda kullanıcı deneyimini(User Experience) zenginleştirecek şekilde yapılan bir çok atılım vardır. (Bu etkileşim özellikle web tabanlı mimarilerde kendini daha da ön plana çıkarmaktayken, geliştirme süreçlerinin standart masaüstü uygulamalara nazaran daha karmaşık ve zor olduğuda söylenebilir.) Bu nedenle tarayıcı uygulamalar üzerindeki kullanıcı deneyimini zenginleştirecek Silverlight gibi geliştirme ortamları söz konusudur. Hal böyle olunca yukarıdaki şekilde çizdiğimiz katmanlı model biraz daha değişim göstermektedir. Aşağıdaki şekilde olduğu gibi.

Zengin internet uygulamalarında, sunum katmanı/mantığı istemci tarafına yıkılmaktadır(Hatırlayalım, Silverlight uygulamalarının çalıştırılması için istemci tarafında minik bir framework, add-in tarzında yüklenmiş olmalıdır). Bu da kullanıcı etkileşimini dahada üst seviyeye çıkartmak anlamına gelmektedir. Ama doğal olarak n-tier modelde sunum katmanı ile uygulama mantığı arasına internet ağının girmesi gerekmektedir. Buna göre RIA' ları basit bir istemci uygulamadan ziyade, sunucu bileşenlerinide içeren birer Internet uygulaması olarak düşünmek gerekmektedir. Hal böyle olunca, sunucu tarafındaki veri kaynaklarının sunum tarafında kullanılabilmesinde servisler önemli bir rol üstlenmektedir. .Net RIA Servislerine kadarki zaman diliminde, geliştiricilerin bu anlamda düşünmesi gereken pek çok kıstas vardır. Herşeyden önce veriyi istemci tarafına taşıyacak servisin ve metodlarının yazılması gerekir. Ayrıca istemci tarafında, bu servisin kullanılabilmesi için gerekli proxy üretiminin yapılması şarttır. Silverlight tarafında kolay olan proxy üretimi, Asp.Net Ajax tarafı düşünüldüğünde ek javascript kütüphaneleri anlamına gelmektedir. Yinede, sunucu tarafında Ado.Net Entity Framework veya LINQ to SQL gibi modelleri kullanabileceğimizden bu zahmete girmeye değmektedir. Microsoft'un söz konusu servislerin, n-tier içerisindeki uyarlanışını daha da kolaylaştırmak adına .Net RIA Servislerini geliştirdiğini söyleyebiliriz. .Net RIA Servisleri kavramsal olarak iki ana parçadan oluşur.

DataService sınıfı aslında, temel CRUD(CreateRetrieveUpdateDelete) işlemlerini ve özel bir takım operasyonları içerebilir. Bunlara ek olarak doğrulama(Validation), yetkilendirme(Authorization) gibi kısıtlarıda ele alabilir. Bu nedenle DataService sınıfının, veri için ele alınacak iş mantığını içerdiğini söyleyebiliriz. DataService sınıfı genel olarak arka planda, hazır olan(built-in) veri modellerini kullanır. Yani Ado.Net Entity Framework veya LINQ to SQL burada göz önüne alınabilir. Elbetteki diğer veri kaynaklarıda gerek servisler, gerek özel kodlamalar yardımıyla kullanılabilir.İkinci bölümde yer alan DataContext sınıfı ise, servislerin istemciye sunduğu verilerin, tip bazındaki karşılıklarını içermektedir. Bu nedenle istemci tarafında, verilerin yüklenmesi, üzerinde yapılan değişikliklerin tekrardan sunucu tarafına gönderilmesi için gerekli kodlamaları ve metodlarıda hazır olarak içermektedir. Tahmin edeceğiniz üzere, .Net RIA Servislerinin Visual Studio 2008 ortamında geliştirilmesi son derece kolay ve basittir. Laughing

Son olarak .Net RIA Servisleri ile ilişkili olaraktan merak edilen sorulara cevap bulabileceğiniz ve gerekli yüklemeleri edinebileceğini bir internet adresini paylaşmak isterim.

Böylece geldik bir yazımızın daha sonuna. Bu yazımda sizlere .Net RIA Servislerini, anladığım kadarıyla anlatmaya çalıştım. Bir sonraki yazımızda basit bir örnek geliştirerek Merhaba .Net RIA Servisi diyeceğiz. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.