WCF RIA Servislerindeki en sevdiğim noktalardan bir tanesi, veri modelinde yer alan
Entity' lere ilişkin bazı işlerin Metadata sınıflarına yıkılabilmesidir.
İlk bakıldığında yalnızca bir takım basit validasyonları sağlayan Attribute’
ların burada yer alacağı sanılabilir; fakat üretilen Metadata sınıflarında
yapılabilecekler sadece bunlar değildir. Bu yazıda konuşacağımız bir başka konu
olan Shared kodlar ise, hem istemci hem de sunucu taraflarında, Entity’
lere, özelleştirilmiş iş mantıklarının kazandırılmalarına yönelik bir kullanım
sağlamaktadır. Dilerseniz konuya geçmeden önce, bu yazıda değineceğimiz başlıklara
kısaca bir göz atalım :
- Önceden tanımlı validasyon kuralları
- İstemci taraflı özelleştirilmiş validasyon
- Entity sınıfına özelleştirilmiş bir iş mantığı yüklemek
- Veri okumalarında servisten ilişkisel satırların çekilmesi
Tüm bu senaryoları incelemek üzere yeni bir Silverlight uygulaması oluşturarak işe
koyulalım
Örnek projede veri kaynağı olarak AdventureWorks’ün Product, ProductSubcategory
ve ProductCategory tablolarını kullanacağız. Bu üç tabloyu içeren Entity Data Model
ve hemen ardından Domain Service Class’ ı projeye dahil ettiken sonra
Domain Service ve projenin görüntüsü aşağıdaki gibi olmalıdır. Bu noktada
özellikle Generate associated classes for metadata seçeneğinin açık olduğuna
dikkat edilmelidir. Çünkü bu seçeneğin işaretli hale getirilmesiyle ,veri modelinde
bulunan Entity sınıfları için metadata sınıfları oluşturulacaktır.
Nedir bu metadata sınıfları?
Öncelikle AdventureWorksService.cs isimli dosyada, servis tarafında çalışacak metotların
yer aldığını hatırlatmak gerekir. Projede yer alan AdventureWorksService.metadata.cs
ismindeki dosya ise, bahsedilen metadata sınıflarını içermektedir.
Metadata sınıfı ne işe yarar?
WCF RIA Servislerinde metadata, yazının başında listelenen senaryoların birçoğunda
sıklıkla kullanılmaktadır. Ağırlıklı olarak validasyon kısmının bu sınıflarda yer
alacağı söylenilebilir.
Peki neden Product sınıfı varken, bu işlerin ProductMetadata sınıfında yapılması
gerekiyor?
Çok basit. Çünkü Product sınıfı, Entity Framework’ ün Code Generator’
ı tarafından oluşturulmaktadır. Dolayısıyla üzerinde değişiklik yapılırsa,
Code Generator’ ın kodları baştan oluşturması durumunda bu dğeişiklikler
kaybolacaktır. Bu yüzden benzer kod üretim durumlarının tümünde, kod üretimiyle
oluşan sınıflarda değişiklik yapmak yerine, Partial Class ve Partial Method
tekniklerinden faydanılmalıdır.
Tabi ki senaryoları test edebilmek için, Silverlight istemcisinde, ürün kayıtlarının
güncellenebileceği basit bir arayüze de ihtiyaç olacaktır. Örnekte kullanılan Silverlight
uygulamasının çalışma zamanındaki görüntüsü aşağıdaki gibidir.
A) Önceden tanımlı validasyon kuralları
Bu validasyonlar System.ComponentModel.DataAnnotations.ValidationAttribute
tipinden kalıtılarak oluşturulmuş Attribute sınıfları sayesinde gerçekleştirilmektedirler.
Bu sınıfların RIA Servisleri ve Silverlight’a özel olmadıkları da unutulmamalıdır.
Bahsi geçen sınıflar, validasyon amacıyla ASP.NET Dynamic Data
uygulamalarında da kullanılabilmektedirler. ValidationAttribute sınıfından
kalıtılmış bu Attribute’ lar aşağıda listelenmiştir.
- CustomValidationAttribute
- DataTypeAttribute
- RangeAttribute
- StringLengthAttribute
- RequiredAttribute
- RegularExpressionAttribute
Listelenen validasyon kurallarının Entity sınıflarına uygulanmaları metadata
sınıfllar üzerinden gerçekleştirilir. Örneğin, ürün adlarında alpha numeric karakterler
bulunamaması için ProductMetadata sınıfında, aşağıdaki gibi bir validasyon bildirimi
yapılabilir.
[RegularExpression(@"^[a-zA-Z0-9_\s]*$", ErrorMessage = "Ürün adı, alfanümerik
karakterler içeremez.")]
public string Name { get; set; }
Hatta bu bildirimler birlikte de kullanılabilirler.
[RegularExpression(@"^[a-zA-Z0-9_\s]*$", ErrorMessage = "Ürün adı, alfanümerik karakterler
içeremez.")]
[Required(ErrorMessage = "Ürün adı boş geçilemez.")]
public string Name { get; set; }
Uygulamanın çalışma anındaki görüntüsü :
Önceden tanımlı RangeAttribute , StringLengthAttribute gibi diğer
validasyon sınıfılarının kullanımları da benzer olduklarından bu kullanımlara ait
testleri size bırakıyorum.
Peki ben ProductMetadata isimli sınıfta, NVARCHAR(50) kolonuyla eşleşmiş
Name kolonuna [StringLength(
50)] niteliğini uygulamam da gerekir mi?
Her metinsel kolon için bunu tekrarlayacak mıyım?
Hayır, veri tabanındaki kolon bilgilerinden yola çıkılarak bu işlem biz farkında
olmadan otomatik olarak zaten yapılıyor. Nasıl olduğunu anlamak için Silverlight
projesinin Generated_Code klasörüne bakmak yeterli olacaktır
[DataMember()]
[RegularExpression("^[a-zA-Z0-9_\\s]*$", ErrorMessage="Ürün adı, alfanümerik karakterler
içeremez.")]
[Required(ErrorMessage="Ürün adı boş geçilemez.")]
[StringLength(50)]
public string Name
{
get
{
return this._name;
}
set
{
if ((this._name != value))
{
this.OnNameChanging(value);
this.RaiseDataMemberChanging("Name");
this.ValidateProperty("Name",
value);
this._name =
value;
this.RaiseDataMemberChanged("Name");
this.OnNameChanged();
}
}
}
Bu property istemcide oluşan Product sınıfına aittir. Yani servisteki ProductMetadata
sınıfında StringLength niteliği uygulanmamış olsa bile, kolonun tipi NVARCHAR(50)
olduğundan dolayı istemcide üretilen sınıfta ilgili işaretleme otomatik olarak gerçekleştirilmiştir.
Kaldı ki sadece StringLength değil, Key, RoundtripOriginal, Association
gibi nitelikler de gerekli olan property’ ler üzerine otomatik olarak
uygulanmaktadır.
B) İstemci taraflı özelleştirilmiş validasyon
Zaman zaman mevcut validasyon teknikleri ihtiyaçları karşılamayabilir. Bu durumda
kendi validasyon metotlarımızı yazarak, Entity bazında doğrulamayı Silverlight
uygulamamız içerisinde gerçekleştirmemiz gerekir. RIA Servisi içerisinde yer alan
bazı iş mantıklarının, istemci tarafında da yer alması istendiğinde, servisin bulunduğu
projeye, [DosyaAdi].shared.cs notasyonuyla bir dosya eklenmesi yeterlidir. Eğer Web
uygulaması içinde bu notasyona uyan bir dosya var ise, bilinmelidir ki, içerdiği
kodlar istemci tarafında, yani Silverlight uygulamasında da oluşturulacaktır.
Örneğin AdventureWorksService.shared.cs isminde bir dosya, Web uygulamasına
eklenerek proje derlendiğinde, aynı kod dosyasının istemcide de üretildiği rahatlıkla
görülebilir.
Derleme işlemi sonrasında istemci uygulamanın görüntüsü aşağıdaki gibi olmalıdır
(Eğer Generated_Code klasörü görünmüyorsa Solution Explorer’ da
Show All Files seçeneği açılmalıdır) .
Bingo! Madem ki, servis tarafında shared.cs uzantısına sahip bir dosya, istemcide
de aynen üretiliyorsa, o halde servise eklenilen validasyon metodu istemci uygulamaya
kolaylıkla paylaştırılabilir. O halde Web uygulamasına eklenilen AdventureWorksService.shared.cs
dosyasının validasyon metotlarını saklamak üzere kullanılması, bizi çözüme götürecektir.
Yalnız bu noktada devam etmeden önce çok önemli bir durumu hatırlatmakta fayda vardır.
Shared olarak işaretlenmiş dosyadaki kodlar, birebir olarak istemcide de
oluşacağı için, yazılan kodların istemci uygulamada çalışabilir nitelikte olması
gerekmektedir. Örneğin Web uygulamasına eklenmiş Shared dosyada şöyle bir
kod bulunduğunu düşünelim :
using System.Xml.Linq;
Elbette, Web uygulamasına eklenen bu using bloğu, eğer Web uygulamasının Assembly
referanslarında System.Xml.Linq.dll’i bulunuyorsa bir hataya sebep olmaz. Ancak
aynı kodlar birebir olarak istemcide de oluşacağı için, System.Xml.Linq.dll’inin
istemci Silverlight uygulamasını da referans edilmesi gerekir.
Tabi ki validasyon metodunun yazılmasında bazı kurallar da söz konusudur
:
- Metot public erişim belirleycisine sahip olmalıdır.
- Metot static olmalıdır.
- İkinci parametresi ValidationContext tipinden olmalıdır.
- Dönüş tipi ValidationResult olmalıdır.
- Metodun bulunduğu sınıf public erişim belirleyicisine sahip olmalıdır.
- Metodun bulunduğu sınıf static olmak zorunda değildir.
- Metodun ismi istenildiği gibi verilebilir.
- Metodun ilk parametresi, doğrulanan değeri taşımaktadır. Bu yüzden parametrenin
tipi, doğrulanan değerin tipiyle aynı yapılır.
Bu kurallar doğrultusunda, ProductNumber değerlerinin “0” ile başlamasını engelleyen
basit bir validasyon metodu aşağıdaki şekilde yazılabilir.
using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.DataAnnotations;
namespace RIA_EntityMetadata.Web
{
public class MyValidationRules
{
public static ValidationResult IsValidProductNumber(string
value, ValidationContext context)
{
if (value.StartsWith("0"))
return new ValidationResult("Ürün numarası 0 ile başlayamaz.", new[] { context.MemberName
});
return ValidationResult.Success;
}
}
}
Son olarak yazılan doğrulama metodunun, ProductMetadata sınıfında ProductNumber
özelliğine uygulanması gerekir. Bunun için, ilgili property’ ye, CustomValidation
niteliği uygulanmalıdır. Niteliğin uygulanması sırasında verilen ilk argüman,
validasyon metodunun bulunduğu sınıfın tipini, ikinci argüman ise metodun adını
belirtmektedir.
[CustomValidation(typeof(MyValidationRules), "IsValidProductNumber")]
public string ProductNumber { get; set; }
Uygulamanın çalışma anındaki görüntüsü :
C) Entity sınıfına özelleştirilmiş bir iş mantığı yüklemek
ORM araçlarının ürettikleri Entity sınıflarına zaman zaman, ilave bir takım
metotlar ya da property’’ ler eklemek gerekir. RIA Servisi kullanılan veri
odaklı uygulamalarda da tabi ki benzer ihtiyaçlar söz konusu olabilmektedir. Hemen
bu durumu da bir senaryo ile canlandırmaya çalışalım. Örneğin, ListPrice bilgisinin
yazıldığı TextBox’ ın yanına bir TextBlock ekleyerek, KDV dahil
fiyat bilgisi görüntülenmek istense nasıl bir yol izlenmelidir?
Akıllara ilk gelen çözüm, Product sınıfına ListPrice değerini 1.18 ile çarpıp geriye
dönen bir property tanımlamak olacaktır. Tabi ki, son derece doğru bir
yaklaşımdır. Ancak, bu üye, Product sınıfına nasıl eklenmelidir? Entity Framework’
ün oluşturduğu bu sınıfa yeni bir üye eklense bile, aynı üyenin istemci tarafında
oluşan Product sınıfına da kazandırılması nasıl sağlanacaktır? Tabi ki, Shared
işaretlenen bir kod dosyası ile!
O halde öncelikle Web uygulamasına, istemciyle paylaşılacak olan, Product.shared.cs
adındaki kod dosyasını ekleyelim.
Daga sonra eklenen kod dosyasında, Entity Framework tarafından üretilen
Product tipi için, partial sınıf tekniğiyle bir property tanımlayalım.
using System;
using System.Collections.Generic;
using System.Linq;
namespace RIA_EntityMetadata.Web
{
public partial class Product
{
public decimal KdvDahilFiyat
{
get { return
ListPrice * 1.18M; }
}
}
}
Artık istemci tarafındaki Product sınıfında da KdvDahilFiyat özelliği bulunmaktadır.
Dolayısıyla Silverlight istemcisinde fiyat bilgisinin yazıldığı TextBox’
ın yanına bir TextBlock ilave ederek, KdvDahilFiyat bilgisine Binding
yapılabilir.
Bu teknik ile, yukarıdaki gibi basit bir property yerine, istemcide kullanılmak
üzere daha karışık işler gerçekleştiren bir method, hatta event
gibi üyelerin de tanımlanabileceği unutulmamalıdır.
D) Veri okumalarında servisten ilişkisel satırların çekilmesi
Metadata sınıfının kullanıldığı durumlardan biri de, servisten dönen bir Entity’
nin ilişkisel Entity’ leriyle birlikte yüklenmesidir. Böylece, ürün
bilgileriyle birlikte, ürünün alt kategori bilgisi de istemciye tek seferde getirilmiş
olacak, alt kategori bilgisini elde etmek için istemcinin ikinci bir talep yapmasına
gerek kalmayacaktır. Söz konusu bu durumun, Eager Loading olarak da bilinen
bir Entity yükleme stratejisi olduğunu da hatırlatmakta fayda vardır.
Entity Framework’ de Eager Loading, Include isimli metot aracılığıyla
gerçekleştirilir. Örnek bir kullanımı şu şekilde olabilir :
Product[] products = context.Products.Include(“ProductSubcategory”).ToArray();
Görüldüğü üzere, Include metodu parametre olarak, Product nesnelerinin
hangi tablodaki ilişkisel satırları beraberinde yükleyeceğini istemektedir. Dolayısıyla,
Domain Service sınıfında Product’ların istemciye döndürülmesini sağlayan
GetProducts metodu düzenlenerek işe başlanmalıdır. Düzenleme sonrasında metodun
görüntüsü şu şekilde olmalıdır :
public IQueryable<Product> GetProducts()
{
return this.ObjectContext.Products. Include("ProductSubcategory");
}
GetProducts metodunda yapılan Include çağrısı sayesinde, Product tablosuyla ProductSubcategory
tablosunun JOIN edilerek ilişkisel satırların bir defada SQL sunucusundan
alınması sağlanmıştır. Aşağıda, Include m
SELECT
[Extent1].[ProductID] AS [ProductID],
[Extent1].[Name] AS [Name],
[Extent1].[ProductNumber] AS [ProductNumber], ...
FROM [Production].[Product] AS [Extent1]
LEFT OUTER JOIN [Production].[ProductSubcategory] AS [Extent2]
ON [Extent1].[ProductSubcategoryID] = [Extent2].[ProductSubcategoryID]
Fakat bu düzenleme tek başına yeterli değildir. Domain Service içinde de,
Product nesnelerinin, ilişkisel ProductSubcategory entity’ siyle birlikte
yüklenerek istemciye gönderilmesi için ekstra bir düzenleme gerekmektedir. Söz konusu
düzenleme için ProductMetadata sınıfına geçilerek, ilgili Navigation Property
bulunmalı ve Include niteliğiyle işaretlenmelidir.
[Include]
public ProductSubcategory ProductSubcategory { get; set; }
Eğer ki Include niteliğinin ilgili özelliğe uygulanması es geçilirse, SQL
sorgusu JOIN yapacak şekilde üretilecek ve SQL sunucusundan ürün bilgileri, ilişkisel
alt kategori satırlarıyla birlikte elde edilecek; fakat Silverlight istemcisiyle
RIA Servisi arasında gerekli konfigürasyon yapılmamış olduğundan, elde edilen verinin
istemciye gönderilmesi gerçekleşmeyecektir .
Her iki düzenleme de başarılı bir şekilde yapıldıktan sonra, istemci uygulamaya
bir TextBlock daha ekleyerek gerekli Binding işlemi gerçekleştirilebilir.
<TextBlock Grid.Column="2" Margin="5" VerticalAlignment="Center"
HorizontalAlignment="Left" FontSize="15" Foreground="Red" Text= "{Binding Path=ProductSubcategory.Name}"
/>
Gördüğünüz gibi, WCF RIA Servislerinde Metadata sınıfları ve Shared kod dosyaları
çok farklı amaçlara hizmet edebilmektedirler. Özellikle Shared dosyalarla ilgili
çok farklı ve keyifli kullanımlar yaratılabileceğini düşünmekteyim. Hatta yine benzer
şekilde deklarativ olarak bazı işlemleri gerçekleştirmek üzere kendimize özel Attribute
sınıfları tasarlayabileceğimiz ve bu kullanımları daha da öteye götürebileceğimizi
unutmamamız gerekir.
Faydalı olması dileğiyle…