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

WCF RIA Services - Alan Bazlı(Field Based) Rol Kontrolü [Beta 2]

Çarşamba, 27 Ocak 2010 11:33 by bsenyurt

Merhaba Arkadaşlar,

Hani bazen insanın aklına son derece zekice fikirler gelir ya... Wink Sene 1992. Lise öğrencisiyim. Bazı akşamlar yazlığımızdaki odamda üniversiteye hazırlanmaya çalışırdım. Güzel yaz gecelerinde, tertemiz ada ikliminde, mis gibi kokan iyotlu deniz suyunun çok yakınlarında konsantre olmak her ne kadar çok zor olsa da, buna mecburdum. Sealed Odamdaki flöresan ışığını çalışma ortamı için hiç uygun bulmazdım. Bunun yerine sarı ışığı tercih ederdim ve aynen yandaki şekilde görülene benzer bir gece lambam vardı. Aslında lambanın etrafında şık bir küre bulunmaktaydı fakat sakarlığıyla bilinen bendeniz onu bir ara kırmıştım. Tabi hal böyle olunca şöyle bir sorunla karşılaştım. Işık direkt olarak gözüme geliyor ve çok rahatsız ediyordu. Çözüm olarak nemi yaptım. Dahiyane bir fikirle Amerika' daki bir arkadaşımın hediye ettiği Newyork Nicks takımının logosunu taşıyan şapkayı, lambanın üstüne güzelce yerleştirdim. Özellikle ışığın gözüme direkt olarak girmesini engelleyen ama etrafı ve okuduklarımı görmemi sağlayan bir açıyı düşünüp, ölçüp biçerek, dikkatlice yerleştirdim. Kendimle gurur duyuyordum. Bu zeka ile NASA'ya bile gidebilirim diye düşünüyordum Tongue out 

Ancak gecenin ilerleyen saatlerinde değil NASA, sıradan bir bölümü bile kazanmamın zor olduğuna kanaat getirdim. Nitekim ampülün zaman içerisinde çevreye yaydığı aşırı ısıyı tahmin edememiştim. Ancak odanın içerisine bir yanık kokusu yayıldığında bir şeylerin ters gittiğinin farkına varabilmiştim. En nihayetinde güzelim şapkanın ortasında kocaman bir yanık izi ve erimiş kumaş parçaları ile kala kaldım. Neredeyse koca bir delik açılmıştı. Laughing 

İşte geçen gün yine böyle dahiyane bir fikir gelir mi aklıma diye düşünürken, WCF RIA Servis operasyonlarından dönen Entity nesnelerinin alanlarını rol bazlı olarak ele alabilir miyiz diye sorgulamaya başladım. Peki neden böyle bir ihtiyacımız  olsun? Çok basit bir sebep öne sürebiliriz. Sunucu tarafındaki servisten dönen Entity örnekleri içerisindeki alanlarının bazılarının, Login olan kullanıcı tarafından görülmemesi veya kullanılamaması istenebilir. Malum DomainService tipi içerisinde yer alan operasyonlarda Login olan kullanıcının içerisinde bulunduğu rol elde edilebilmektedir. Buna göre sorgunun üreteceği çıktı içerisine dahil edilecek alanların yetkiye göre oluşturulması sağlanabilir...mi acaba? Durumu örnek bir senaryo üzerinden incelersek çok daha anlaşılır olacaktır. Öncelikli olarak AdventureWorks veritabanı içerisinde yer alan SalesOrderDetail isimli tabloyu kullanmak istediğimizi düşünelim.

Örnek senaryomuzda çok doğal olarak Authentication alt yapısınında tesis edilmiş olması gerekmektedir. WCF RIA Service' lerinde Authentication Domain Service kullanımından daha önceki yazılarımızda bol bol bahsettiğimizden bu detayları atlıyoruz. Ancak ASP.NET Membership tabanlı olarak kurulan Authentication alt yapısı içerisinde testler için iki rol olduğunu söyleyebiliriz. AuthorizedSalesPerson ve JuniorSalesPerson. Senaryomuza göre güya AuthorizedSalesPerson rolünden gelen kullanıcılar UnitPriceDiscount alanının değerini görebilirken, JuniorSalesPerson rolündeki kullanıcılar göremeyecektir. Başlangıçta AdventureDomainService isimli Domain Service sınıfını aşağıdaki gibi düzenlediğimizi düşünelim.

namespace RoleBasedFields.Web
{
    using System.Linq;
    using System.Web.DomainServices;
    using System.Web.DomainServices.Providers;
    using System.Web.Ria;

    [RequiresAuthentication]
    [EnableClientAccess()]
    public class AdventureDomainService
        : LinqToEntitiesDomainService<AdventureWorksEntities>
    {
        public IQueryable<SalesOrderDetail> GetSalesOrderDetails()
        {
            return (from sod in ObjectContext.SalesOrderDetails
                    select sod).Take(50);
        }
    }
}

Kişisel Not: 121317(Yüz yirmi bir bin üçyüz on yedi)...SalesOrderDetail tablosunda bu kadar satır bulunmaktadır. Bu satırların tamamını istemci tarafına göndermek performans açısında tercih edilmemelidir. Zaten WCF RIA Service' lerin kullanımı ile ilişkili best practices tiyolarında, üretilen standart sorguların filtreler ile düzenlenmesi önerilmektedir. En azından dönen veri içeriğinin bir gözden geçirilerek gerektiğinde performans için filtrelenmesi düşünülmelidir. Bu sebepten örneğimizde Take genişletme metodundan(Extension Method) yararlanılarak ilk 50 satırın alınması sağlanmıştır.

İlk etapta sunucu tarafındaki operasyonda herhangibir rol kontrolü yapılmamaktadır. Buna göre Login olan bir kullanıcı için ekran çıktısı aşağıdaki gibi olacaktır.

Ancak örnek senaryomuza göre JuniorSalesPerson rolünde olan bill isimli kullanıcının UnitPriceDiscount alanını görmemesi veya anlamlandıramaması gerekmektedir.(Anlamlandıramamasının ne kadar zor olduğunu biraz sonra anlayacağız) Buna göre sunucu tarafında yer alan operasyonun özelleştirilmesi gerekmektedir. Aslında yazımızın ulaşmak istediği tek nokta budur. Peki ama nasıl? Wink 

Sonuçta istemci ve sunucu tarafında eş olan SalesOrderDetail Entity sınıf bilgisini çalışma zamanında değiştirmemiz şu etapta pek mümkün değildir. Akla ilk gelen yöntem result set çekilirken role göre gösterilmesi istenmeyen alana örneğin null değer atanmasını sağlamak olabilir. (Bu konu ile ilişkili olaraktan yaptığım araştırmalarda, blog girdisini hazırladığım tarih itibariyle Brad Abrams' ın ilgili yazısında bu tip bir teknik uygulandığını gördüm) Tabi örneğimizdeki alan null değer almamaktadır. Buna göre belki -1 değer atanması sağlanabilir. Ama bu durumdada alan yine görülebilir olacaktır. Undecided Aşağıdaki kod parçasını göz önüne alalım.

public IQueryable<SalesOrderDetail> GetSalesOrderDetails()
        {
            var resultSet = (from sod in ObjectContext.SalesOrderDetails
                             select sod).Take(50);

            foreach (var result in resultSet)
            {
                if (ServiceContext.User.IsInRole("JuniorSalesPerson"))
                    result.UnitPriceDiscount = -1;
            }

            return resultSet;
        }

Bu kod parçasında görüldüğü gibi resultSet gönderilmeden önce her bir satırı taranmakta ve ServiceContext üzerinden elde edilen güncel kullanıcının rolüne bakılarak UnitPriceDiscount alanına -1 değer atanması sağlanmaktadır. Buna göre çalışma zamanı çıktısı bill isimli kullanıcı için aşağıdaki gibi olacaktır.

Peki istediğimiz bu muydu?

Kesinlikle değil. Bizim hayalimiz ilgili alanın istemci tarafından görülmemesini sunucudaki operasyon üzerinden sağlamaktı. Oysaki öğrenebildiğimiz sadece şu oldu; servis tarafındaki operasyondon dönen resultSet içeriğindeki veriyi istersek Login olan kullanıcının rolüne göre değiştirebiliriz. İşte şu anda lambaya koyduğumuz şapkanın delindiğini görmekteyiz. Benim NASA hayalleri yine yalan oldu anlayacağınız. Sealed

Peki ya çözüm?

En ideal çözüm istemci tarafında kullanıcının rolüne göre ilgili alanının gizlenmesi olarak düşünülebilir. Ancak buda optimal bir çözüm olmayacaktır. Nitekim sunucu tarafında ele alınması gereken güvenlik konulu bir iş mantığını istemeden istemci tarafına taşımak zorunda kalmış oluruz. Tabi en büyük sıkıntılardan birisi şudur. Sunucu tarafında bu rol kontrolünü başarabilsek dahi, istemci tarafında gönderilecek entity örneklerinin dinamik olarak değişebiliyor olması gerekecektir. Oldukça zor bir işlem aslında...

Anlaşılan bu konuda WCF RIA Services tarafında bir eksiklik var. Bende en ideal çözüm için araştırmalarıma devam ediyorum. Bakalım lambanın üzerine koyduğumuz şapkadaki deliği kapatabilecek miyiz? Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

WCF RIA Services - Custom Authorization [Beta 2]

Salı, 26 Ocak 2010 10:52 by bsenyurt

Merhababa Arkadaşlar,

Geçtiğimiz günlerde uzun süredir yemediğim şu meşhur Dunkin & Donuts' tan bir iki kurabiye almak istedim. Wink Ansızın gelen bu dayanılmaz istek üzerine oturduğumuz semte en yakın dükkanına gidip hem kendim hemde eşim için bir kaç tane aldım. Sonrası malum...Yanında güzel bir kahve ve harika bir tat...Tattıları afiyetle mideye indirdikten sonra evde sessiz ve sakin bir ortamın olduğunu farkettim. Bizim azman ufaklık uyumuş ve gıkı bile çıkmıyorken, yorduğu eşim divanda mışıl mışıl sızmıştı. Herkesin böyle huzurlu bir ortamı hakkettiğini düşünürken, yağmur damlalarının cama vuruşunu izliyordum. Derken ansızın bir ilham geldi ve bloğuma bir şeyler yazmamın iyi olacağı kanısına vardım. Nede olsa gerekli glikoz yüklemesi fazlasıyla yapılmıştı. Günlüğüme yazılacaklar listeme baktığımda,  sıradaki konunun WCF RIA Service' lerinde özel yetkilendirme niteliklerinin(Custom Authorization Attributes) nasıl yazılacağının anlatılması olduğunu farkettim. Neyseki hafta içi bu konu ile ilişkili olaraktan internetteki az sayıda kaynaktan bilgi edinmiştim. Tabiki en güncel ve geçerli kaynak her zamanki gibi MSDN' di.

Hatırlayacağınız gibi bir önceki yazımızda niteliklerden yararlanarak yetkilendirme işlemlerinin nasıl yapılabileceğini basit bir örnek üzerinden incelemeye çalışmıştık. Buna göre önemli olan nokta, nitelik(Attribute) yardımıyla çalışma zamanının nasıl davranacağının belirlenebilmesidir. Ancak bazen, yetkilendirme(Authorization) kontrolü için özel durumların ele alınması da gerekebilir. Nitekim sadece istemcinin içinde bulunduğu role göre karar vermenin dışında yapılması gereken yetki kontrolleri söz konusu olabilir. Böyle bir durumda çalışma zamanının anlayacağı kendi niteliklerimizi yazmamız gerekmektedir. Çok doğal olarak bu geliştirme işleminde yazılacak olan nitelik tipinin, çalışma zamanının anlayacağı ve kullanacağı bir veya daha fazla operasyonu uygulaması şarttır. Burada tipik olarak, çalışma zamanının değerlendireceği fonksiyonellikleri barındıran bir ata nitelik sınıfından yapılacak olan türetme(Inherit) işleminden bahsettiğimizi ifade edebiliriz. Dilerseniz konuyu daha iyi anlamak için bu yazımızda geliştireceğimiz örnek senaryomuzdan kısaca bahsedelim.

Senaryomuza göre Domain Service içerisinde tanımlanmış olan herhangibir operasyonun gerçekleştirilmesi sırasında, talepte bulunan kullanıcının içerisinde bulunduğu role değil, adının özel olarak tutulan yasaklı bir listede bulunup bulunmadığına bakılması durumu ele alınmaktadır. Bu yasaklı listenin ASP.NET Membership tarafından hazır olarak tutulan veritabanına extend edilmiş bir tablo içerisinde tutulması mümkündür. Bunun dışında dosya tabanlı olarak XML veya basit Text formatında dahi tutulabilir. Hatta olayı biraz abartıp Windows Registry ayarlarında dahi tutulması ve benzer saklama alanları düşünülebilir. Hatta bu listenin bir uzak sunucuda duruyor ve ancak servis bazlı bir operasyon yardımıyla kontrollerin yapılabiliyor olması da söz konusu olabillir. Tabiki dosya tabanlı olan saklama şekli bu seçenekler arasında en az güvenli olanıdır. Nitekim dosyanın güvenliğini sağlamak, veritabanı içerisindeki bir tabloya göre çok daha zor olabilir. Ancak amacımız yetkilendirme işlemi için özel nitelik yazılması ve kullanılması olduğundan bebek adımlarıyla ilerleyeceğiz. Bu nedenle geliştireceğimiz örnekte basit olarak yasaklı listenin bir Text dosyada düzenli olarak tutulduğunu varsayıyor olacağız. Buna göre System.Web.DomainServices.AuthorizationAttribute niteliğinden türettiğimiz tipin içerisinde yer alan Authorize metodu içerisinde gelen kullanıcı adının, yasaklı listede olup olmadığını kontrol etmemiz yeterli olacaktır. İşte sunucu tarafında yer alan CheckBannedListAttribute sınıfı içeriğimiz.

using System.IO;
using System.Linq;
using System.Web;
using System.Web.DomainServices;

namespace ChinookCustomAuthorization.Web
{
    public class CheckBannedListAttribute
        :AuthorizationAttribute
    {
        public override bool Authorize(System.Security.Principal.IPrincipal principal)
        {
            string filePath = HttpContext.Current.Server.MapPath("~\\BannedList.txt");
            string[] bannedList=File.ReadAllLines(filePath);
            return !bannedList.Contains(principal.Identity.Name);
        }
    }
}

CheckBannedListAttribute sınıfı AuthorizationAttribute tipinden türemektedir. Buna göre sınıf diagramı görüntüsünden de fark edileceği üzere, abstract olan Authorize metodunu ezmek zorundadır. Authorize metodu, niteliğin uygulanacağı metodlar için gerekli yetki kontrolü operasyonunu üstlenmektedir. Dikkat edileceği üzere Authorize metodu parametre olarak tanıdık bir arayüzü almaktadır; IPrincipal. Bu arayüze gelen çalışma zamanı referansından yararlanarak sisteme giriş yapan kullanıcının adını, hangi rolde yer aldığını, doğrulanıp doğrulanmadığını öğrenebiliriz. Biz örnek senaryomuza göre Login olan kullanıcı adının yasaklı listenin tutulduğu text tabanlı dosya içerisinde olup olmadığını incelemeyi hedefliyoruz. Bu sebepten Authorize metodu içerisinde BannedList isimli ve sunucu proje içerisinde tutulan text tabanlı dosyanın tüm satırlarının yüklenmesi ve elde edilen dizi içerisinde olup olmadığına göre bool tipte bir sonucun döndürülmesi söz konusu. Yazmış olduğumuz özel nitelik tipini sunucu tarafında yer alan ChinookDomainService sınıfı içerisinde uygulamamız ise son derece kolay.

Kişisel Not :Örneğimizin kalan kısmı daha önceki yazımızda geliştirdiğimiz ile benzer. Bu nedenle detaya girerek konudan uzaklaşmamayı tercih etmekteyim.

namespace ChinookCustomAuthorization.Web
{
    using System.Linq;
    using System.Web.DomainServices;
    using System.Web.DomainServices.Providers;
    using System.Web.Ria;


    [RequiresAuthentication]
    [EnableClientAccess()]
    public class ChinookDomainService
        : LinqToEntitiesDomainService<ChinookEntities>
    {
        [CheckBannedList]
        public IQueryable<Album> GetAlbums()
        {
            return this.ObjectContext.Albums;
        }
    }
}

Görüldüğü gibi CheckBannedList niteliği, yasaklı liste yetki kontrolü yapılmak istenen operasyonun üzerinde uygulanmaktadır. Buna göre çalışma zamanında Login olan bir kullanıcının söz konusu GetAlbums operasyonunu talep etmesi halinde devreye girecektir. Söz gelimi text dosyamız içerisinde aşağıdaki isimlerin yer aldığını varsayalım.

Buna göre örneğin buraks isimli kullanıcı ile Login olup albüm listesinin yüklendiği operasyonu talep ettiğimizde, tarayıcı uygulama üzerinde aşağıdaki script hatasını aldığımızı görürüz.

Görüldüğü üzere Access Denied kelimeleri hata mesajı içerisinde yer almaktadır. Çok doğal olarak yasaklı liste içerisinde yer almayan bir kullanıcı ile sistem girdiğimizde(örneğin senaryomuza göre bill olabilir) albüm listesinin başarılı bir şekilde elde edildiği görülecektir. Aynen aşağıdaki ekran görüntüsünde olduğu gibi.

Sonuç olarak eklediğimiz özel authorization niteliği yardımıyla, Domain Service üzerinden çağırılacak bir operasyon için, sisteme giriş yapan kullanıcının rolüne bakmatan farklı bir yetkilendirme kontrolü gerçekleştirebildiğimizi görmüş olduk. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

CustomAuthorization.rar (1,29 mb)

WCF RIA Services - Authentication Domain Service - Attribute Bazlı Yetkilendirme [Beta 2]

Cuma, 22 Ocak 2010 09:45 by bsenyurt

Merhaba Arkadaşlar,

Bildiğiniz üzere bir süredir WCF RIA Service' lerinde doğrulama(Authentication), yetkilendirme(Authorization), Role ve Profile yönetimi konularına değinmekteyiz. WCF RIA Service' lerinin temel amaçlarından birisininde RIA tipindeki uygulamalar için Ado.Net Entity Framework gibi kaynaklar üzerinden CRUD(CreateReadUpdateDelete) operasyonlarını sağlanması olduğu düşünüldüğünde, servis fonksiyonelliklerinin yetkilendirilmeside güvenlik açısından önem arz eden konuların başında gelmektedir. Bu konu, WCF RIA Service' lerinde nitelikler(Attributes) yardımıyla ele alınabilmektedir.

Bu noktada iki önemli niteliğin olduğunu söyleyebiliriz. Bunlardan birisi Domain Service sınıfının Authentication işlemleri çerçevesinde değerlendirilmesi gerektiğini çalışma zamanına söyleyen RequiresAuthentication niteliğidir. Bu niteliği Domain Service tipine uygulamak yeterlidir. Diğer taraftan, Domain Service içerisinde tanımlanmış olan operasyonların hangi yetkiler altında çalıştırılabileceğini belirtmek için RequiresRole isimli nitelikten yararlanılmaktadır. RequiresRole niteliği string tipinde birden fazla parametre alabilmektedir. Bu parametre değerleri tahmin edileceği üzere rolleri ifade etmektedir. Bu teoriye göre bir Domain Service operasyonunun rol bazlı olaraktan yetki altında çalıştırılmasının sağlanması mümkündür.

Tabi teoride bahsettiğimiz bu kavramları pratiğe dökmemiz bizim için önemlidir. Bu nedenle yazımızın bundan sonraki kısmında basit olarak Chinook veritabanındaki kobay tablolarımızdan olan Album içeriğine ulaşmak için kullanılan bir operasyon üzerinde, rol bazlı yetkilendirme işlemlerini nasıl uygulayabileceğimizi incelemeye çalışacağız.

Kişisel Not : Başlamadan önce Silverlight uygulamasında daha önceki yazılarda sıklıkla anlattığımız şekilde Form bazlı doğrulama(Form-Based Authentication) için gerekli ayarları yapmamız gerektiğini hatırlatalım. Özellikle role yönetimini etkinleştirmemiz gerektiğini ve hem sunucu hemde istemci tarafında gerekli konfigurasyon ve kod ayarlarını uygulamamız gerektiğini unutmayalım. Örneğimizde yine buraks ve bill isimli kullanıcıları değerlendiriyor olacağız. Bunlardan birisi Employee rolünde iken diğeri Finance rolündedir. Amaçlanan sadece Finance rolündekilerin, albüm listesini çekebilmesini sağlamaktır. Tabiki Chinook isimli veritabanını Silverlight uygulamasında kullanabilmek için gerekli Domain Service(ChinookDomainService) öğesinide eklememiz gerektiğini hatırlatmak isterim. Pek çok hatırlatmada bulundum ama önceki yazıları takip edip uygulayan arkadaşlarımız için örneği bu aşamaya getirmek son derece kolay olacaktır düşüncesindeyim Wink

Gelelim örneğimize. İlk olarak durumu kuş bakışı değerlendirmeye çalışalım. Buna göre aşağıdaki şekli göz önüne alabiliriz.

Şekildende görüleceği üzere sunucu uygulama tarafında Chinook veritabanına ulaşılmasını ve üzerinde CRUD işlemleri yapılabilmesini sağlayan(ki bu örnekte sadece veri çekiyor olacağız) ChinookDomainService isimli Domain Service tipi bulunmaktadır. Bu tip arada Ado.Net Entity Framework modelini kullanmaktadır. Diğer taraftan doğrulama işlemleri için ASP.NET Membership alt yapısı kullanılmakta olup istemci tarafının bu hizmeti değerlendirebilmesi için birde Authentication Domain Service(ChinookDomainService) öğesi yer almaktadır. İstemci tarafı veri ve güvenlik işlemleri için bu iki servisten yararlanacaktır. Önemli olan noktalardan biriside hangi operasyonu nasıl yetkilendireceğimizi bilmektir. Bu noktada ChinookDomainService sınıfını aşağıdaki şekilde oluşturduğumuzu göz önüne alalım.

namespace SilverlightApplication8.Web
{
    using System.Linq;
    using System.Web.DomainServices;
    using System.Web.DomainServices.Providers;
    using System.Web.Ria;

    [RequiresAuthentication] // Servis operasyonlarında Authentication uygulanacağını belirtiyoruz.
    [EnableClientAccess()]
    public class ChinookDomainService : LinqToEntitiesDomainService<ChinookEntities>
    {
        [RequiresRole("Finance")] // Sadece Finance rolündekilerin aşağıdaki operasyonu kullanabileceğini belirtmekteyiz.
        public IQueryable<Album> GetAlbums(int artistId)
        {
            // Örnek olarak ArtistId bilgisine göre albüm listesini Title bilgisine göre A...Z sırasında döndürüyoruz
            return from a in ObjectContext.Albums
                   where a.ArtistId == artistId
                   orderby a.Title
                   select a;
        }
    }
}

Dikkat edileceği üzere ChinookDomainService tipine RequiresAuthentication niteliği uygulanmıştır. Bununla birlikte sadece Finance rolündekilerin kullanımına sunulan GetAlbums isimli bir operasyon yer almaktadır. Yazımızın başında da belirttiğimiz üzere RequiresRole niteliğine parametre olarak birden fazla rol adı verilebilir. Sunucu tarafında rol bazlı yetkilendirme için yapmamız gerekenler sadece bu kadardır. Gelelim istemci tarafına. Bu amaçla MainPage.xaml içeriğini ve kod kısmını aşağıdaki gibi geliştirdiğimizi düşünelim.

MainPage.xaml;

<UserControl x:Class="SilverlightApplication8.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:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" xmlns:dataInput="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input">

    <Grid x:Name="LayoutRoot" Background="White">
        <Button Content="Load Albums" Height="23" HorizontalAlignment="Left" Margin="305,18,0,0" Name="btnLoadAlbums" VerticalAlignment="Top" Width="83" Click="btnLoadAlbums_Click" />
        <data:DataGrid AutoGenerateColumns="True" Height="105" HorizontalAlignment="Left" Margin="10,87,0,0" Name="grdAlbums" VerticalAlignment="Top" Width="378" />
        <dataInput:Label Height="80" HorizontalAlignment="Left" Margin="10,208,0,0" Name="lblStatus" VerticalAlignment="Top" Width="378" Content="Olası hata mesajı..." />
        <dataInput:Label Height="28" HorizontalAlignment="Left" Margin="12,18,0,0" Name="label1" VerticalAlignment="Top" Width="59" Content="Username" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="77,18,0,0" Name="txtUsername" VerticalAlignment="Top" Width="120" />
        <dataInput:Label Height="28" HorizontalAlignment="Left" Margin="14,54,0,0" Name="label2" VerticalAlignment="Top" Width="57" Content="Password" />
        <PasswordBox Height="23" HorizontalAlignment="Left" Margin="77,54,0,0" Name="txtPassword" VerticalAlignment="Top" Width="120" />
        <Button Content="Login" Height="23" HorizontalAlignment="Left" Margin="203,18,0,0" Name="btnLogin" VerticalAlignment="Top" Width="82" Click="btnLogin_Click" />
        <Button Content="Logout" Height="23" HorizontalAlignment="Left" Margin="203,54,0,0" Name="btnLogout" VerticalAlignment="Top" Width="82" Click="btnLogout_Click" />
    </Grid>
</UserControl>

MainPage.xaml.cs;

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ria;
using System.Windows.Ria.ApplicationServices;
using SilverlightApplication8.Web;

namespace SilverlightApplication8
{
    public partial class MainPage : UserControl
    {
        // DomainContext ve AuthenticationService kullanımı için gerekli örnekler
        ChinookDomainContext context;
        AuthenticationService authSrv;

        public MainPage()
        {
            InitializeComponent();

            btnLoadAlbums.IsEnabled = false;
            btnLogout.IsEnabled = false;
           
            context = new ChinookDomainContext();
            authSrv = WebContext.Current.Authentication;

            // Login işlemi olduğunda devreye girecek olay metodu yüklenir
            authSrv.LoggedIn += new EventHandler<AuthenticationEventArgs>(authSrv_LoggedIn);
            // Logout işlemi tamamlandığında devreye girecek olay metodu yüklenir
            authSrv.LoggedOut += new EventHandler<AuthenticationEventArgs>(authSrv_LoggedOut);
        }

        private void btnLogin_Click(object sender, RoutedEventArgs e)
        {
            // Login işlemi yapılır
            LoginOperation logOp = authSrv.Login(new LoginParameters(txtUsername.Text, txtPassword.Password));
        }

        private void btnLogout_Click(object sender, RoutedEventArgs e)
        {
            // Logout işlemi yapılır. Hata var ise exception fırlatılması sağlanır
            authSrv.Logout(true);
        }

        void authSrv_LoggedIn(object sender, AuthenticationEventArgs e)
        {
            // Login olan kullanıcı için güncel User bilgileri alınır
            Web.User currentUser = WebContext.Current.User;
            // Kullanıcının dahil olduğu roller ve adı bilgilendirme amaçlı öğrenilir
            string roles=String.Empty;           
            foreach (var role in currentUser.Roles)
            {
                roles += String.Format("{0}|", role);
            }
            lblStatus.Content =String.Format("Kullanıcı {0} Rolleri : {1}",currentUser.Name,roles);
           
            btnLoadAlbums.IsEnabled = true;
            btnLogout.IsEnabled = true;
            btnLogin.IsEnabled = false;
        }

        void authSrv_LoggedOut(object sender, AuthenticationEventArgs e)
        {
            btnLogout.IsEnabled = false;
            btnLoadAlbums.IsEnabled = false;
            btnLogin.IsEnabled = true;
        }

        private void btnLoadAlbums_Click(object sender, RoutedEventArgs e)
        {  
            // Login olunduktan sonra Album listesinin yüklenmesi aşamasına geçilir.
            // DomainService üzerinde yer alan GetAlbums operasyonundaki role gerekliliği nedeni ile sadece Finance rolü için yükleme yapıldığı görülür. Aksi durumda ise bir çalışma zamanı hatası oluşacak ve script error olarak tarayıcı üzerinde görülecektir.
            LoadOperation<Album> op = context.Load<Album>(context.GetAlbumsQuery(1));
            grdAlbums.ItemsSource = op.Entities;
        }
    }
}

Aslında istemci tarafında yapılan tek şey kullanıcının Login ve Logout olmasını sağlayacak operasyonlar ile örnek olması açısından 1 numaralı şarkıcıya ait albüm listesinin çekilmesini sağlamaktır. Dikkat edileceği üzere yetkilendirme kontrolü istemci tarafında yapılamamaktadır. Bu kontrol sunucu tarafında çalışma zamanı tarafından ele alınan Domain Service sınıfı üzerinden ele alınmaktadır. Buna göre Login olan geçerli kullanıcının Finance rolünde olmaması halinde albüm bilgilerini getirememesi gerekmektedir ki bu gerçektende böyledir. İşte yetkisiz bir kullanıcının albümleri yüklemek istemesi halinde çalışma zamanında oluşacak durum;

Hata mesajında yer alan Access Denied kelimeleri olayı tüm çıplaklığıyla özetlemektedir. Diğer yandan senaryomuza göre Finance rolünde yer alan bill isimli kullanıcı ile Login olunup albüm listesi çekilmek istendiğinde, bilgilerin başarılı bir şekilde DataGrid kontrolüne çekildiği gözlemlenir. Aynen aşağıdaki şekilde görüldüğü gibi.

Tabi bu yazımızda yetkilendirme nedeniyle oluşan istisna durumu ele kontrol altına alınmamıştır. Uygulama bu istisna ile karşılaştığında sonlandırılmaktadır ve hata mesajı script error olarak tarayıcı uygulama üzerinden yakalanmaktadır. Ancak en basit haliyle attribute bazlı olaraktan yetkilendirme işlemi sunucu tarafında yer alan servisler kanalıyla gerçekleştirilebilmiştir. Bu noktada vurgulanması gereken durumlardan biriside kendi Authorization niteliklerimizi(Attribute) yazabileceğimizdir. Söz gelimi role göre değil ama başka bir kritere göre yetkilendirme yapmak isteyebiliriz. Bu durumu ilerleyen yazılarımızda ele almaya çalışıyor olacağım. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

WCF RIA Services - Authentication Sample.rar (1,31 mb) [Dosya boyutunun küçük olması için ASPNETDB.mdf içeriği çıkartılmıştır]

WCF RIA Services - Authentication Domain Service - Profile ve Role

Perşembe, 21 Ocak 2010 01:10 by bsenyurt

Merhaba Arkadaşlar,

Yandaki resmin bir renk cümbüşü oluşturup sizlere çok güzel göründüğüne eminim. Hatta bu resmin biraz sonra anlatacağımız konu ile olan ilgisini merak ediyor olabilirsiniz. Ne yazıkki yok. Sadece renk cümbüşünün benide etkilediğini ve yazının hoş görünmesi için eklediğimi itiraf edebilirim. Gelelim asıl mevzumuza.

Bir önceki yazımızda Authentication Domain Service konusunu incelemeye başlamış ve RIA(Rich Internet Application) çeşitlerinden olan Silverlight uygulamalarında Form tabanlı doğrulamanın standart ASP.NET Membership kaynakları üzerinden nasıl sağlanabileceğini görmüştük. RIA uygulamaları ile ilişkili konulardan bir diğeride rol ve profil yönetimidir. WCF RIA Service' lerde kullanılan Authentication Domain Service' lerden yararlanarak Role ve Profile yönetimide yapılabilir. Çok doğal olarak WCF RIA Service' leri, Asp.Net mimarisinin rol ve profil alt yapısını kullanmaktadır.

Doğrulanan bir kullanıcının(Authenticated User), sistem içerisinde yapabileceklerini belirlerken rolüne bakılarak karar verilmesi tercih edilen yöntemlerdendir. Örneğin Administrator rolündeki bir kullanıcı ile Guest rolündeki bir kullanıcının sistem içerisinde yapılabilecekleri kuvvetle muhtemel farklıdır. Burada açık bir şekilde role göre yetkinin mertebesinin belirlendiğini düşünebiliriz. Diğer yandan sistem içerisinde yer alan tüm kullanıcılar için ortak tanımlanabilecek özellikler, çalışma zamanında farklı(bazende benzer, hatta aynı) değerler alarak, her kullanıcının sistem için bir profilinin oluşmasında kullanılabilirler. Çok doğal olarak bu profil özellikleri sistemden sisteme farklı şekillerde tanımlanabilir ve kullanılabilirler. Söz gelimi RIA uygulamasına dahil olan kullanıcıların ünvanları, doğum tarihleri, göz renkleri, cep telefonlarının gsm operatörleri, son giriş zamanları her kullanıcı için birer profil özelliği olarak değerlendirilebilir.

Bu yazımızda bir önceki örneğimizi devam ettirerek rol ve profil özelliklerinin nasıl değerlendirilebileceğini ele almaya çalışıyor olacağız. Temel amacımız rol ve profil bilgilerinin özellikle istemci tarafında nasıl kullanılabileceğini görmek olduğundan çok işe yarar bir örnek geliştirmeyeceğimizi şimdiden belirtmek isterim Wink Öncelikli olarak sunucu uygulama tarafında rol ve profil yönetimi için gerekli ayarları yapmamız gerekiyor. Bir önceki örneğimizde kullandığımız buraks ve bill isimli kullanıcıları sırasıyla Developer ve Administrator isimli rollere atadığımızı düşünerek devam edeceğiz. Hatta testlerimizi daha iyi yapabilmek adına bill isimli kullanıcının her iki rol atlında da bulunması sağladığımızı düşünelim. Bu şekilde istemci tarafına birden fazla rol bilgisinin nasıl aktarıldığını değerlendirme fırsatımız olacaktır. Bildiğiniz üzere rol atama işlemleri için ASP.NET Configuration aracını kullanabiliriz. Rol atama işlemlerinin ardından örneğimizde kullanacağımız bir kaç profil özelliğini tanımlayarak devam edebiliriz. Bu amaçla Web.config dosyamızın içeriğinde aşağıdaki değişiklikleri yaptığımızı düşünelim.

<configuration>
  <system.web>
    <httpModules>
      <add name="DomainServiceModule" type="System.Web.Ria.Services.DomainServiceHttpModule, System.Web.Ria, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    </httpModules>
    <authentication mode="Forms" />
    <roleManager enabled="true" />
    <profile enabled="true">
      <properties>
        <add name="Title"/>
        <add name="LastAccessTime" type="System.DateTime"/>
      </properties>
    </profile>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
...

roleManager ve profile elementleri içerisinde enabled niteliklerine true değer atandığına dikkat edelim. Bu sayede RIA uygulamamızda role ve profile kullanımını etkinleştirmiş oluyoruz. profile elementi içerisinde yer alan properties alt elementi içerisinde ise Title ve LastAccessTime isimli özellik tanımlamalarının yapıldığını görmekteyiz. Özellikle LastAccessTime niteliği için DateTime bildiriminin de yapıldığına dikkat edelim. Nitekim profil özellikleri varsayılan olarak string tipindendir. Bu sebepten string harici tipleri bildirmemiz gerekmektedir.

Web.config dosyasında yapılan bu bildirimler ASP.NET tarafındaki role ve profile alt yapıları için gereklidir. Ne varki RIA uygulaması tarafında da ilgili profil özelliklerinin kullanımı için gerekli bildirimlerin yapılması gerekmektedir. Üstelik kullanıcının rol bilgilerinin istemci tarafında yer alan kod kısmında nasıl ele alınabileceği de şu anda soru işaretidir. Panik ve heyecan yapmadan sakin bir şekilde adım adım ilerleyelim. Öncelikli olarak OurAuthenticationService ismiyle oluşturduğumuz Authentication Domain Service dosyasını açalım ve User isimli tipin içeriğini aşağıdaki gibi düzenleyelim.

using System;
using System.Web.Ria;
using System.Web.Ria.ApplicationServices;

namespace SilverlightApplication7.Web
{
    [EnableClientAccess]
    public class OurAuthenticationService
        : AuthenticationBase<User>
    {
    }

    public class User
        : UserBase
    {
        public string Title { get; set; }
        public DateTime LastAccessTime{ get; set; }
    }
}

Dikkat edileceği üzer profile elementi altında tanımlanan özellikler, User isimli tip içerisinde de property olarak bildirilmiştir. Buraya kadar yaptığımız işlemlerin ardından uygulamayı build ettiğimizde, istemci tarafında otomatik olarak üretilen sınıf içerisinde yer alan User tipininde aşağıdaki şekilde görüldüğü gibi oluşturulduğunu fark edebiliriz.

Dikkat edileceği üzere sunucu tarafında tanımladığımız Title, LastAccessTime isimli özellikler için istemci tarafındaki User sınıfı içerisinde de gerekli bildirimler yapılmıştır. Üstelik rol işlemleri içinde IEnumerable<string> tipinden bir özellik(Roles) olduğu görülmektedir. Buna göre birden fazla rolün kod tarafında değerlendirilmesi mümkündür. Hatta LINQ ifadeleri ile Roles özelliği üzerinden sorgular atılabilir. Artık istemci tarafında rol ve profil işlemlerini değerlendirebilecek alt yapı hazırlıklarını tamamlamış bulunuyoruz. Şimdi MainPage.xaml içeriğini aşağıdaki gibi güncelleyerek yolumuza devam edebiliriz.

MainPage.xaml içeriği;

<UserControl x:Class="SilverlightApplication7.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="325" d:DesignWidth="420" xmlns:dataInput="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input" xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation">
    <Grid x:Name="LayoutRoot" Background="White" Height="325">
        <dataInput:Label Height="28" HorizontalAlignment="Left" Margin="8,14,0,0" Name="label1" VerticalAlignment="Top" Width="120" Content="Username" />
        <dataInput:Label Height="28" HorizontalAlignment="Left" Margin="10,48,0,0" Name="label2" VerticalAlignment="Top" Width="120" Content="Password" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="134,14,0,0" Name="txtUsername" VerticalAlignment="Top" Width="197" />
        <PasswordBox Height="23" HorizontalAlignment="Left" Margin="134,50,0,0" Name="txtPassword" VerticalAlignment="Top" Width="197"/>           
        <Button Content="Login" Height="23" HorizontalAlignment="Left" Margin="337,14,0,0" Name="btnLogin" VerticalAlignment="Top" Width="75" Click="btnLogin_Click" />
        <dataInput:Label Height="28" HorizontalAlignment="Left" Margin="12,82,0,0" Name="lblLoginStatus" VerticalAlignment="Top" Width="196" />
        <Button Content="Logout" Height="23" HorizontalAlignment="Left" Margin="337,53,0,0" Name="btnLogout" VerticalAlignment="Top" Width="75" Click="btnLogout_Click" />
        <dataInput:Label Height="28" HorizontalAlignment="Left" Margin="214,82,0,0" Name="lblProcess" VerticalAlignment="Top" Width="198" />
        <Border BorderBrush="#FFFF3B00" BorderThickness="3" Height="185" HorizontalAlignment="Left" Margin="10,124,0,0" Name="brdProfile" VerticalAlignment="Top" Width="395" CornerRadius="10" Background="{x:Null}">
            <Canvas Height="170" Name="canvas1" Width="380">
                <dataInput:Label Canvas.Left="6" Canvas.Top="6" Height="13" Name="label3" Width="43" Content="Title" />
                <dataInput:Label Canvas.Left="8" Canvas.Top="40" Height="15" Name="label4" Width="101" Content="Last Access Time" />
                <TextBox Canvas.Left="56" Canvas.Top="7" Height="23" Name="txtUserTitle" Width="309" />
                <dataInput:Label Canvas.Left="116" Canvas.Top="42" Height="28" Name="lblUserLastAccessTime" Width="249" />
                <dataInput:Label Canvas.Left="10" Canvas.Top="83" Height="15" Name="label5" Width="39" Content="Roles" />
                <dataInput:Label Canvas.Left="60" Canvas.Top="83" Height="28" Name="lblRoles" Width="305" />
                <Button Canvas.Left="290" Canvas.Top="130" Content="Save Profile" Height="23" Name="btnSaveProfile" Width="75" Click="btnSaveProfile_Click" />
            </Canvas>
        </Border>
    </Grid>
</UserControl>

Tasarımın biraz fakir olmasına aldırmadan ilerlemeye devam edelim. Undecided Senaryomuzu şu şekilde işletiyor olacağız; Kullanıcı Login işlemini başarılı bir şekilde gerçekleştirdiyse eğer, WebContext.Current.User özelliğinden elde edeceğimiz kullanıcı bilgilerini Border alanı içerisindeki kontrollerde göstereceğiz. Sembolik olarak Title ve LastAccessTime değerlerini ve dahil olduğu rollerin adlarını değerlendireceğiz. Sonrasında kullanıcı isterse Save Profile başlıklı düğmeye basaraktan yeni Title ve LastAccessTime bilgilerini kaydedebilecek. Bu basit senaryo için bir önceki yazımızda geliştirdiğimiz MainPage.xaml.cs içeriğini aşağıdaki gibi güncellememiz yeterli olacaktır.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ria.ApplicationServices;

namespace SilverlightApplication7
{
    public partial class MainPage : UserControl
    {
        // Kullanıcının doğrulanması, profil özelliklerinin yüklenmesi ve kaydedilmesi için gerekli fonksiyonellikleri sunan tiptir.
        AuthenticationService authSrv = WebContext.Current.Authentication;
      
        public MainPage()
        {
            InitializeComponent();

            btnLogout.IsEnabled = false;
            brdProfile.Visibility = Visibility.Collapsed;

            authSrv.LoggedIn += new System.EventHandler<AuthenticationEventArgs>(authSrv_LoggedIn);
            authSrv.LoggedOut += new System.EventHandler<AuthenticationEventArgs>(authSrv_LoggedOut);
        }

        private void btnLogin_Click(object sender, RoutedEventArgs e)
        {
            lblLoginStatus.Content = String.Empty;
            lblProcess.Content = String.Empty;

            if (!authSrv.IsLoggingIn) // Eğer asenkron olarak devam eden bir Login operasyonu yoksa
            {
                // Login işlemi için AuthenticationService tipinin Login metodu çağırılır.
                // ilk parametre ile kullanıcı adı ve şifre bilgisi gönderilir. Bu metodun aşırı yüklenmiş versiyonları mevcuttur.
                // ikinci parametrede Login metodunun işleyişini tamamlaması sonrası devreye giren metodun işaret edilmesi sağlanmaktadır(Action<LoginOperation> temsilci tipi ile işaretleme yapılır). Bu metod içerisinde işlemin iptali, exception üretmesi gibi durumlarda ele alınmaktadır.
                authSrv.Login(
                    new LoginParameters(txtUsername.Text, txtPassword.Password),
                    opt =>
                    {                       
                        if (opt.IsCanceled)
                            lblProcess.Content = "Login işleminde iptal";
                        else if (opt.Error != null)
                            lblProcess.Content = opt.Error.Message;
                        else if (opt.IsComplete)
                            lblProcess.Content = "Login işlemi tamamlandı";
                    }
                    , null
                    );
            }
        }

        private void btnLogout_Click(object sender, RoutedEventArgs e)
        {
            lblLoginStatus.Content = String.Empty;
            lblProcess.Content = String.Empty;

            if (!authSrv.IsLoggingOut) // Eğer asenkron olarak devam eden bir Logout operasyonu yoksa
            {
                // Logout işlemi kullanılan metod çağrısı
                // işlem tamamlandığında devreye girecek olan metod Action<LogoutOperation> temsilcisi ile işaret edilir.
                authSrv.Logout(
                   opt =>
                   {
                       if (opt.IsCanceled)
                           lblProcess.Content = "Logout işleminde iptal";
                       else if (opt.Error != null)
                           lblProcess.Content = opt.Error.Message;
                       else if (opt.IsComplete)
                           lblProcess.Content = "Logout işlemi tamamlandı";
                   }
                , null);
            }
        }

        // Kullanıcı başarılı bir şekilde Logout olduğunda tetiklenir
        void authSrv_LoggedOut(object sender, AuthenticationEventArgs e)
        {
            lblLoginStatus.Content = String.Format("{0} {1} zamanında çıkış yaptı", e.User.Identity.Name, DateTime.Now.ToLongTimeString());

            btnLogin.IsEnabled = true;
            btnLogout.IsEnabled = false;
            brdProfile.Visibility = Visibility.Collapsed;
        }

        //AuthenticationService nesnesine ait Login metodunun çalıştırılması sonrasında kullanıcı başarılı bir şekilde doğrulandıysa çalışır
        void authSrv_LoggedIn(object sender, AuthenticationEventArgs e)
        {
            lblLoginStatus.Content = String.Format("{0} {1} zamanında giriş yaptı", e.User.Identity.Name, DateTime.Now.ToLongTimeString());

            btnLogin.IsEnabled = false;
            btnLogout.IsEnabled = true;
            brdProfile.Visibility = Visibility.Visible;
            lblRoles.Content = String.Empty;
            txtUserTitle.Text = String.Empty;

            // Login işlemi tamamlandıktan sonra WebContext.Current üzerinden giriş yapan User bilgileri alınır
            Web.User currentUser = WebContext.Current.User;
            // Profil özelliklerinin değerleri ilgili kontrol özelliklerine atanır
            lblUserLastAccessTime.Content = currentUser.LastAccessTime.ToLongTimeString();
            txtUserTitle.Text = currentUser.Title;

            // Kullanıcının dahil olduğu tüm roller Label kontrolü içerisinde ardışıl olarak yazdırılır
            foreach (var role in currentUser.Roles)
            {
                lblRoles.Content += role + "|";
            }
        }

        private void btnSaveProfile_Click(object sender, RoutedEventArgs e)
        {
            // Save işleminde yine WebContext.Current üzerinden elde edilen User tipinin özelliklerinden yararlanılır
            Web.User currentUser = WebContext.Current.User;
           
            // Bu kez profile özelliklerine, kontroller üzerindeki değerler atanır
            currentUser.Title = txtUserTitle.Text;
            currentUser.LastAccessTime = DateTime.Now;

            // Kaydetme operasyonu için AuthenticationService nesne örneğinin SaveUser metodu çağırılır. Bu metodun çalıştırılması sırasında bir istisna olduğunda bunun ortama fırlatılması için parametre olarak true değeri verilmiştir.
            SaveUserOperation operation=authSrv.SaveUser(true);
            // Kaydetme operasyonu tamamlandığında SaveUserOperation tipinin Completed olay metodu devreye girer.
            operation.Completed +=
                (snd, arg) =>
                {
                    lblProcess.Content = "Profil Save is OK";
                };
        }
    }
}

(Eklediğimiz önemli kısımlar bold olarak işaretlenmiştir)

Hemen şu noktayıda vurgulayalım. İstenirse profil özelliklerinin kontrollere bağlanması sırasında Binding imkanlarından da yararlanılabilir. Bu durumda Login işlemini takiben otomatik olarak User bilgilerinin ilgili kontrollere bağlanması söz konusudur. Biz örneğimizde profil bilgilerinin gösterilmesi işlemlerini kod tarafında değerlendirmeye çalıştık. Ancak Binding işlemi ile aynı fonksiyonelliklerin uygulamaya nasıl kazandırılabileceğini incelemenizi öneririm. Dilerseniz uygulamamızın çalışma zamanını test ederek ilerleyelim. Ben bill isimli kullanıcı için daha önceden bir profil bilgisini test amacıyla kaydetmiştim. Bu durumda login işleminden sonra aşağıdaki görüntüye benzer sonuçlarla karşılaştım.

Şimdi profil bilgilerini değiştirip kaydettiğimizi düşünelim. Bu durumda Save Profile işleminin başarılı bir şekilde gerçekleştirildiğini görebiliriz.

Bu işlemin ardından tekrar Logout olup yeniden Login işlemini gerçekleştirirsek(ki uygulamayı kapatıp yeniden başlatmakta söz konusu olabilir) bill isimli kullanıcı için az önce kaydedilen profil bilgilerinin getirildiğini görebiliriz. Buda çalışmanın başarılı olduğunu bir ispatı olarak düşünülebilir.

Böylece geldik bir yazımızın daha sonuna. Bu yazımızda WCF RIA Service - Authentication Domain Service hizmetini kullanaraktan Silverlight uygulamalarında Role ve Profile alt yapılarının nasıl değerlendirilebileceğini incelemeye çalıştık. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

SilverlightApplication7RoleAndProfile.rar (780,46 kb) [Dosya boyutunun küçük olması için ASPNETDB.mdf içeriği çıkartılmıştır]

Webiner - WCF RIA Services

Pazar, 20 Aralık 2009 00:10 by bsenyurt

Merhaba Arkadaşlar,

Bildiğiniz üzere geçtiğimiz hafta içerisinde kişisel bilgisayarımdaki teknik bir aksaklık nedeniyle NedirTv?com sitesi aracılığıyla yayınladığımız WCF RIA Services webinerimiz yarım kalmıştı. 18.12.2009 Cuma gecesi 21:00 - 22:00 saatleri arasında ise herhangibir sorun olmadan Webinerimizi tamamlamayı başardık. Ayrıca Webiner kaydı NedirTv?com sitesi üzerinden yayınlandı.

Bu Webinerimizde kısaca WCF RIA Service kavramını tanımaya çalıştık. Bildiğiniz üzere .Net RIA Services ismi WCF RIA Services olarak değiştirildi. Bu WCF Eco System yaklaşımının bir sonucu. WCF RIA Service' leri, zengin içerikli internet uygulamalarının(Rich Internet Applications), çok katmanlı mimariyi(N-Tier) uygulamaları halinde sunucu üzerindeki kaynaklara ulaşmalarında büyük önem taşımaktadır. Özellikle sunum katmanı(Presentation Layer) ile orta katman(Middler Tier) arasındaki uygulama mantığının(Application Logic) paylaşılmasında önemli bir rol üstlenmektedirler. Bu anlamda sunucu kaynaklarının kullanımına ilişkin olarak, uygulama mantığının etkili ve kolay bir şekilde ele alınması mümkün hale gelmektedir. Üstelik geliştiricilerin servis alt yapısı, iletişim şekli gibi bir takım detayları düşünmesine gerek yoktur. Nitekim, Visual Studio ortamına entegre gelen Domain Service şablonu yardımıyla sadece uygulama mantığı üzerine odaklanılması yeterlidir. Elbette veri kaynağına olan erişiminde kullanılabilen Entity Data Model' in yetenekleride yadsınamaz.

Tabi konu RIA uygulamaları olunca akla ilk gelen Silverlight nesneleridir. Bizde Webinerimizde Silverlight 4.0 odaklı olarak geliştirilen bir uygulamada WCF RIA Service kullanımını incelemeye çalıştık. Keyifli seyirler dilerim...

Süre -> 54:52

WCF RIA Services - Authentication Domain Service

Perşembe, 10 Aralık 2009 13:40 by bsenyurt

Merhaba Arkadaşlar,

Bazen insanın yapmak zorunda olduğu bazı şeyler gözünde büyür. Örneğin havalimanlarında kontrollerden geçerek uçağa ulaşmak o daracı rahatsız koltuklara sığmak için çabalamak. Önce ilk kapıda bir güvenlik kontrollünden geçilir, ardından Check-In işlemi için kimlikle birlikte bir kontrolden daha geçilir(hatta bagajımız var ise tartılır ve gerekiyorsa ekstra para ödenir), ardından uçağa bineceğimiz kapılara gitmek için bir kontrolden daha geçilir, ardından uçağa binerken bilet ve kimlik ile kontrolden bir kere daha geçilir. Keşke insanoğlu daha barışçıl olsaymış dedirten aramalar ile karşılaşılır zaman içerisinde. Güvenlik kontrollerinin temel amaçlarından birisi de, gerçekten sizin kimliğinizde söylenen kişi olduğunuzu görmek ve hatta elinizdeki biletiniz ile söz konusu uçağa binebilecek yetkiye sahip olduğunu öğrenmektir. Tabi arada sıra Administrator seviyesinde pek çok insan güvenlik kontrollerine takılmadan protokol kapısından giriş yaparak yollarına devam edebilirler. Üstelik yürüdükleri hat üzerinde kırmızı halılarda bulunabilir. Wink Özellikle bu son durumda, giriş yapan kişilerin rollerininde büyük önemi vardır.

Sözün özü bir noktaya güvenlik kontrolünden geçerek girmemiz gerektiğinde doğrulanma, yetki kontrolü, rol gibi faktörlerle karşılaşırız. Zaten yazılım dünyasında da üyelik tabanlı(Membership Based) olarak çalışan sistemlerde, kullanıcıların bazı şeyleri yapabilmesi için önce doğrulanmaları(Authenticate) gerekir. Buna ilaveten, doğrulanan kullanıcıların yetkilerine bakaraktan bir takım işlemleri yapıp yapmamalarına izin verilmesi(Authorization), hatta bu sırada rollerinin de değerlendirilmesi söz konusudur. Dahası doğrulanan ve yetkisi dahilinde bir yere ulaşan bireyin kendine has profilini tanımlayan özellikleride bulunabilir.

Web tabanlı uygulamalarda sıkça karşılaştığımız doğrulama(Authentication) işlemlerinin genellikle Form-Based veya Windows-Based olarak yapılabildiğini görürüz.(Hatta Microsoft Passport hizmetininde kullanılması mümkündür) Asp.Net 2.0 ile birlikte getirilen Membership alt yapısı sayesinde SQL üzerinde tutulabilen hazır üyelik sistemlerinden kolaylıkla yararlanabiliriz. Hatta ilk Asp.Net sürümünden bu yana, Active Directory sayesinde intranet tabanlı web uygulamalarında Windows Domain kullanıcılarını ve rollerini değerlendirebiliriz. Temel amaç aslında son derece açıktır; Gerçekten tanınan(Authenticated User) kullanıcıların belirli işlemleri yapabilmesi ve neler yapabileceklerine karar verilirken yetkilerine(Authorization) yada rollerine bakılabilmesi.

Sözü fazla uzatmadan WCF RIA Services tarafındaki duruma bakalım. Sonuç itibariyle RIA(Rich Internet Application) için sıklıkla değerlendirdiğimiz Silverlight uygulamalarınında, Asp.Net Membership veya Windows Domain yapılarından yararlanarak doğrulama, yetkilendirme ve rollendirme işlemlerini yapılabilmesi mümkündür. Bu amaçla WCF RIA Services' ler ile birlikte gelen Authentication Domain Service tipinden yararlanılmaktadır. Bu yazımızdaki amacımız da, çok basit anlamda bir RIA uygulamasında doğrulama işlemlerinin nasıl ele alınabileceğini incelemektir.

İşe ilk olarak Silverlight 4.0 ve WCR RIA Services destekli bir Sliverlight uygulaması oluşturarak başlamalıyız. Bilindiği üzere Silverlight çözümlerinde Web tabanlı bir sunucu ve Silverlight kontrollerini barındıran bir uygulama söz konusudur. Silverlight tarafında geliştirilen kullanıcı kontrollerinin host ediliği Web uygulaması Asp.Net tabanlı olduğundan pekala bir Membership sistemine sahip olabilir. Bu nedenle sunucu uygulama üzerinde Membership API' sini kullanarak işe başlamakta ve Form-Based Authentication' ı kullanacağımızı belirtmekte yarar vardır. Bildiğiniz üzere Membership işlemleri için, Web projesinin Asp.Net Configuration aracı kullanılabilir. Ben örneğimizde SQL Express Edition üzerinde konuşlandırılan Membership veritabanını kullanmayı tercih ettim ve şifreleri 123456. olan iki kullanıcı oluşturdum(buraks ve bill). Form-Based Authentication kullandığımız için web.config dosyasında yer alan authentication elementinin içeriğinin aşağıdaki gibi oluşturulması gerekmektedir(Tabi Asp.Net Configuration aracını kullanarak From Internet seçeneğini işaretlediğimizde bu ayar otomatik olarak web.config içerisine işlenecektir)

...
<system.web>
        <httpModules>
            <add name="DomainServiceModule" type="System.Web.Ria.Services.DomainServiceHttpModule, System.Web.Ria, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        </httpModules>
        <!-- Form tabanlı doğrulama(Form-Based Authentication) kullanılacağı belirtilir.-->
        <authentication mode="Forms" />         
        <compilation debug="true" targetFramework="4.0" />
    </system.web>
...

Bu işlemin ardından yine sunucu uygulamaya Authentication Domain Service tipinden bir sınıf eklenmesi gerekmektedir.

Örnekte OurAuthenticationService isimiyle eklediğimiz dosyanın içeriği otomatik olarak oluşturulacak ve aşağıdaki kod parçasında görüldüğü gibi olacaktır.

using System.Web.Ria;
using System.Web.Ria.ApplicationServices;

namespace SilverlightApplication7.Web
{
    [EnableClientAccess]
    public class OurAuthenticationService : AuthenticationBase<User>
    {
        // To enable Forms/Windows Authentication for the Web Application,
        // edit the appropriate section of web.config file.
    }

    public class User : UserBase
    {
        // NOTE: Profile properties can be added here
        // To enable profiles, edit the appropriate section of web.config file.

        // public string MyProfileProperty { get; set; }
    }
}

Burada yer alan AuthenticationBase<User> türevli sınıf doğrulama işlemleri sırasında istemci tarafından kullanılacak tiptir. Diğer yandan User isimli tip üyelere has profil özelliklerinin uygulanabilmesi için kullanılmaktadır. Söz gelimi, Membership API üzerinde tanımlanmış ve yetkisi olan bir kullanıcının Silverlight uygulamasını kullanmaya başlaması ile birlikte Title, Birthdate gibi geliştirici tanımlı bir takım bilgilerinin profil özelliği olarak kullanılabilmesi mümkündür. Tabi User tipi içerisinde profil özelliklerinin tanımlanması yeterli değildir. Web.config dosyasında da aynı profil özelliklerinin bildirilmesi ve profil yönetiminin etkinleştirilmesi gerekmektedir. (Bu yazımızda geliştireceğimiz örnekte doğrulama işlemlerini çok basit bir seviyede anlamak istediğimizden profil özellikleri ile uğraşılmayacaktır). Yapılan ekleme işleminden sonra projenin Build edilmesi halinde istemci tarafında WebContext, User ve OurAuthenticationContext isimli tiplerin oluşturulduğu gözlemlenir. User sınıfına ait nesne örneği ile tahmin edileceği üzere sunucu tarafında tanımlanan User tipine erişilebilmesi mümkündür. Diğer taraftan WebContext tipi, istemci tarafından Authentication Domain Service' e erişilebilmesini ve User bilgisinin alınabilmesini sağlamaktadır. Buraya kadar ki kısımda sunucu tarafı için gerekli aşağıdaki işlemleri yaptığımızı söyleyebiliriz;

  • Form Tabanlı doğrulama için Membership veritabanının oluşturulması ve buna bağlı olarak web.config dosyasında gerekli ayarların yapılması,
  • Gerekiyorsa role seçeneğinin etkinleştirilmesi,
  • Authentication Domain Service tipinin sunucu projeye eklenmesi,
  • Gerekiyorsa User tipi için profil özelliklerinin kod ve web.config bazında oluşturulması

Ancak istemci tarafında, sunucu üzerindeki Authentication Domain Service hizmetinin kullanılabilmesi için bir takım işlemler yapılmalıdır. Öncelikli olarak App.Xaml içeriğinin aşağıdaki hale getirilerek Web.Context ve Form tabanlı doğrulamanın kullanılacağının belirtilmesi gerekir(Buradaki kodlamaların aslında ilerleyen sürümlerde otomatik hale getirileceğini ümit etmekteyim Wink )

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:app="clr-namespace:SilverlightApplication7"
             xmlns:appSrv="clr-namespace:System.Windows.Ria.ApplicationServices;assembly=System.Windows.Ria"
             x:Class="SilverlightApplication7.App"
             >
    <Application.Resources>
       
    </Application.Resources>
    <!-- Aşağıdaki kısım Form-Based authentication kullanımı için eklenmelidir-->
    <Application.ApplicationLifetimeObjects>
        <app:WebContext>
            <app:WebContext.Authentication>
                <appSrv:FormsAuthentication />
            </app:WebContext.Authentication>
        </app:WebContext>
    </Application.ApplicationLifetimeObjects>
   
</Application>

App.Xaml içerisinde yer alan tanımlamalar da yeterli değildir. WebContext' in XAML içerisinde kullanılabilir olması için App.Xaml.cs kod tarafında aşağıdaki ilavenin yapılması gerekmektedir.

private void Application_Startup(object sender, StartupEventArgs e)
        {
            // XAML içerisinde WebContext nesnesinin kullanılabilmesi için Resources koleksiyonuna ilgili WebContext nesne örneğinin eklenmesi gerekir.
            this.Resources.Add("WebContext", WebContext.Current);
            this.RootVisual = new MainPage();
        }

Artık Silverlight kontrolünün içeriğinin tasarlanmasına başlanabilir. Örneğimizde Login ve Logout işlemlerinin yapılması irdelenecektir. Buna göre aşağıdaki tasarım görüntüsü ve XAML içeriğine sahip olacak şekilde MainPage.xaml dosyasını düzenleyerek ilerlediğimizi düşünelim.

XAML İçeriği;

<UserControl x:Class="SilverlightApplication7.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="202" d:DesignWidth="316" xmlns:dataInput="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input">

    <Grid x:Name="LayoutRoot" Background="White">
        <dataInput:Label Height="28" HorizontalAlignment="Left" Margin="8,14,0,0" Name="label1" VerticalAlignment="Top" Width="120" Content="Username" />
        <dataInput:Label Height="28" HorizontalAlignment="Left" Margin="10,48,0,0" Name="label2" VerticalAlignment="Top" Width="120" Content="Password" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="134,14,0,0" Name="txtUsername" VerticalAlignment="Top" Width="170" />
        <PasswordBox Height="23" HorizontalAlignment="Left" Margin="134,50,0,0" Name="txtPassword" VerticalAlignment="Top" Width="170"/>           
        <Button Content="Login" Height="23" HorizontalAlignment="Left" Margin="12,92,0,0" Name="btnLogin" VerticalAlignment="Top" Width="75" Click="btnLogin_Click" />
        <dataInput:Label Height="28" HorizontalAlignment="Left" Margin="10,134,0,0" Name="lblLoginStatus" VerticalAlignment="Top" Width="294" />
        <Button Content="Logout" Height="23" HorizontalAlignment="Left" Margin="229,92,0,0" Name="btnLogout" VerticalAlignment="Top" Width="75" Click="btnLogout_Click" />
        <dataInput:Label Height="28" HorizontalAlignment="Left" Margin="10,170,0,0" Name="lblProcess" VerticalAlignment="Top" Width="294" />
    </Grid>
</UserControl>

Gelelim kod kısmına;

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ria.ApplicationServices;

namespace SilverlightApplication7
{
    public partial class MainPage : UserControl
    {
        // Kullanıcının doğrulanması, profil özelliklerinin yüklenmesi ve kaydedilmesi için gerekli fonksiyonellikleri sunan tiptir.
        AuthenticationService authSrv = WebContext.Current.Authentication;

        public MainPage()
        {
            InitializeComponent();

            btnLogout.IsEnabled = false;
           
            authSrv.LoggedIn += new System.EventHandler<AuthenticationEventArgs>(authSrv_LoggedIn);
            authSrv.LoggedOut += new System.EventHandler<AuthenticationEventArgs>(authSrv_LoggedOut);
        }

        private void btnLogin_Click(object sender, RoutedEventArgs e)
        {
            lblLoginStatus.Content = String.Empty;
            lblProcess.Content = String.Empty;

            if (!authSrv.IsLoggingIn) // Eğer asenkron olarak devam eden bir Login operasyonu yoksa
            {
                // Login işlemi için AuthenticationService tipinin Login metodu çağırılır.
                // ilk parametre ile kullanıcı adı ve şifre bilgisi gönderilir. Bu metodun aşırı yüklenmiş versiyonları mevcuttur.
                // ikinci parametrede Login metodunun işleyişini tamamlaması sonrası devreye giren metodun işaret edilmesi sağlanmaktadır(Action<LoginOperation> temsilci tipi ile işaretleme yapılır). Bu metod içerisinde işlemin iptali, exception üretmesi gibi durumlarda ele alınmaktadır.
                authSrv.Login(
                    new LoginParameters(txtUsername.Text, txtPassword.Password),
                    opt =>
                    {
                        if (opt.IsCanceled)
                            lblProcess.Content = "Login işleminde iptal";
                        else if (opt.Error != null)
                            lblProcess.Content = opt.Error.Message;
                        else if (opt.IsComplete)
                            lblProcess.Content = "Login işlemi tamamlandı";
                    }
                    , null
                    );
            }
        }

        private void btnLogout_Click(object sender, RoutedEventArgs e)
        {
            lblLoginStatus.Content = String.Empty;
            lblProcess.Content = String.Empty;

            if (!authSrv.IsLoggingOut) // Eğer asenkron olarak devam eden bir Logout operasyonu yoksa
            {
                // Logout işlemi kullanılan metod çağrısı
                // işlem tamamlandığında devreye girecek olan metod Action<LogoutOperation> temsilcisi ile işaret edilir.
                authSrv.Logout(
                   opt =>
                   {
                       if (opt.IsCanceled)
                           lblProcess.Content = "Logout işleminde iptal";
                       else if (opt.Error != null)
                           lblProcess.Content = opt.Error.Message;
                       else if (opt.IsComplete)
                           lblProcess.Content = "Logout işlemi tamamlandı";
                   }
                , null);
            }

        }

        // Kullanıcı başarılı bir şekilde Logout olduğunda tetiklenir
        void authSrv_LoggedOut(object sender, AuthenticationEventArgs e)
        {
            lblLoginStatus.Content = String.Format("{0} {1} zamanında çıkış yaptı", e.User.Identity.Name, DateTime.Now.ToLongTimeString());

            btnLogin.IsEnabled = true;
            btnLogout.IsEnabled = false;
        }

        //AuthenticationService nesnesine ait Login metodunun çalıştırılması sonrasında kullanıcı başarılı bir şekilde doğrulandıysa çalışır
        void authSrv_LoggedIn(object sender, AuthenticationEventArgs e)
        {
            lblLoginStatus.Content = String.Format("{0} {1} zamanında giriş yaptı", e.User.Identity.Name, DateTime.Now.ToLongTimeString());

            btnLogin.IsEnabled = false;
            btnLogout.IsEnabled = true;
        }
    }
}

Aslında bütün iş yükünü AuthenticationService nesne örneği almaktadır. Login ve Logout operasyonları için kullanılan bu nesne örneği üzerinden profil ve rol yönetimi için gerekli pek çok operasyonda gerçekleştirilebilir. Örneğin doğrulanan kullanıcı bilgilerine ulaşılabilir(User), kullanıcı bilgilerinin(özellikle profil bilgileri) değiştirilmesi, kayıt edilmesi veya yüklenmesi sağlanabilir. Aşağıdaki şekilde kullanılabilecek özellik ve üye metodlar görülmektedir.

Uygulama açıldıktan sonra geçerli bir kullanıcı adı ve şifre ile Login işlemini yapmak istediğimizde aşağıdaki sonuçları elde ettiğimizi görebiliriz.

Dikkat edileceği üzere kullanıcı başarılı bir şekilde tanınmıştır. Login olan kullanıcının Logout olması için gerekli işlemi yaptığımızda ise aşağıdaki sonuç ile karşılaşırız.

Çok doğal olarak geçersiz bir kullanıcı adı veya şifre girişinde Login işleminin gerçekleştirilemediği görülecektir. Elbette bu yazımızdaki senaryomuzda sembolik olarak Login ve Logout operasyonlarının işlevsellikleri ön plana çıkartılmıştır. Dolayısıyla Login olunduktan sonra asıl işlemlerin yapılacağı sayfaya yönlenilmesi ve hatta rol kontrolüne göre ilerlenmesi işlemleri göz ardı edilmiştir. Oysaki gerçek hayat projelerinden bu işlemlerin ele alınması şarttır. Bu tip konuları belki bir görsel ders üzerinden incelemeye çalışabiliriz. Buraya kadar yaptıklarımıza baktığımzıda, istemci tarafında aşağıdaki temel işlemleri yaptığımızı özetleyebiliriz;

  • App.Xaml ve App.Xaml.cs içerisinden Form tabanlı doğrulama servisinin kullanılması için gerekli ayarlar,
  • Kod tarafında, AuthenticationService nesne örneğinin WebContext.Current ile elde edilmesi ve gerekli kodlamalar,

Görüldüğü üzere WCF RIA Service' ler ile birlikte gelen Authentication Domain Service tipini kullanarak, Silverlight formatlı RIA uygulamalarının, Asp.Net Web uygulamalarında sıklıkla ele alınan doğrulama(Authentication), yetkilendirme(Authorization), rol(Role) ve profil(Profile) yönetimi kabiliyetlerine sahip olması sağlanabilmektedir. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

SilverlightApplication7.rar (626,55 kb) [Dosya Boyutunun küçük olması amacıyla, Memberhip için kullanılan ASPNET_DB veritabanı silinmiştir]

WCF RIA Services - Gerçekten WCF

Perşembe, 26 Kasım 2009 08:19 by bsenyurt

Merhaba Arkadaşlar,

Uzun ve yorucu bir geceydi...Dün gece WCF RIA Service' leri ile ilişkili görsel bir dersin hazırlıklarını yaparken sevgili Mehmet Cengiz arkadaşımın hediyesi olan tablet üzerinde aşağıdaki şekli çizdiğimi farkettim. Bu şekilde WCF RIA Service' i kullanan basit bir Silverlight uygulamasının anlaşılır hali yer almaktadır. Web uygulamamız, içerisinde Silverlight nesnesi barındıran test sayfası ve Domain Service sınıfı, dışarıda veya aynı alanda duran Ado.Net Entity Data Model içeriği ve onun kullandığı veritabanı. Soru işareti içeren ok ve Winforms kutucuğunu ise sonradan eklemeye karar verdim ve araştırmam da işte bu şekilde başladı Laughing

Gerçektende Silverlight uygulamlarını RIA Service' lerini kullanacak şekilde oluşturduğumuzda, genellikle Silverlight nesnesini host eden bir Web uygulaması olması gerekmektedir. Bu web uygulamasının içerisinde yer alan ve Sliverlight tarafına veri hizmeti sunan RIA Service' leri, harici bir Class Library içerisinde bulunabilen bir Entity Data Model' i(yada aynı web domain içerisindekini) kullanabilir. Bu şekli çizmeye çalışırken aklıma takılan ve soru işareti bırakan husus ise şudur; Web uygulaması dışarısında yer alan bir diğer uygulamanın, Web alanı içerisinde yer alan bir RIA Service' ini kullanması mümkün olabilir mi?

Sonuç itibariyle WCF alt yapısı üzerine oturan RIA Service' leri web tabanlı olarak host edilmekte ve hizmet sunarken WCF çalışma zamanı motoru tarafından yürütülmektedirler. Bu nedenle belkide dışarıya WSDL içeriği sunabilir ve Silverlight gibi Rich Internet Application' lar dışındaki uygulamalar tarafından da kullanılabilirler. İşte bu sorunun cevabını ararken Brad Abrams' ın blog yazısı ile karşılaştım. Brad Abrams blog yazısında Silverlight Business Application tipinden geliştirdiği bir proje üzerinden ilgili konuyu irdeleyerek RIA Service' lerin aslında birer WCF Service olduğunu ispat etmektedir. Konuyu bende bu haliyle incelemek istediğimden aynı örneğin benzerini bu kez Silverlight Application tipinden olan bir projede yapmayı denedim. Ancak çok küçük bir özelliği kullanmamam nedeni ile hata ile karşılaştım. Bu hatanın sebebini ve çözümünü bulmam biraz zamanımı aldı. Dolayısıyla Brad Abrams' ın kullandığı proje şablonu yerine normal bir Silverlight Application üzerinden benzer bir senaryoyu uygulamaya karar verdim. İşte bu tecrübenin hikayesi;

İlk olarak aşağıdaki şekilde görülen tipte bir Solution içeriği oluşturarak işe başlamaya karar verdim.

Silverlight Application şablonunda bir Solution açılmasına rağmen aslında ChinookMusicSotreApp isimli proje hiç kullanılmayacaktır.Undecided  ChinookMusicStoreApp.Web isimli web projesi, ChinookEDM isimli sınıf kütüphanesini referans etmektedir. Bu şekilde Albums ve Artist isimli Chinook tablolarına ait Entity karşılıklarını içermekte olan Ado.Net Entity Data Model öğesini kullanabilmektedir. Diğer yandan Web tarafına eklenen Domain Service sınıfı içeriğinde Album tipi üzerinden Insert, Update, Delete işlemleri yapılmasına izin verecek şekilde geliştirmeler yapılmıştır. Bu adımları tamamladıktan sonra ChinookDomainService sınıfının içeriği biraz değiştirerek aşağıdaki hale getirdim. Aslında tek yaptığım GetAlbumsByFirstLetter isimli metodu eklemek oldu.


namespace ChinookMusicStoreApp.Web
{
    using System.Data;
    using System.Linq;
    using System.Web.DomainServices.Providers;
    using System.Web.Ria;
    using ChinookEDM;

    [EnableClientAccess()]
    public class ChinookDomainService
        : LinqToEntitiesDomainService<ChinookEntities>
    {
        // firstLetter parametresi ile başlayan Album' lerin getirilmesini sağlar
        public IQueryable<Album> GetAlbumsByFirstLetter(string firstLetter)
        {
            return from album in ObjectContext.Albums
                   where album.Title.StartsWith(firstLetter)
                   orderby album.Title
                   select album;
        }


        // Tüm Artist' lerin getirilmesini sağlar
        public IQueryable<Artist> GetArtists()
        {
            return this.ObjectContext.Artists;
        }

        // Yeni bir Album eklenmesi için kullanılır
        public void InsertAlbum(Album album)
        {
            if ((album.EntityState != EntityState.Added))
            {
                if ((album.EntityState != EntityState.Detached))
                {
                    this.ObjectContext.ObjectStateManager.ChangeObjectState(album, EntityState.Added);
                }
                else
                {
                    this.ObjectContext.AddToAlbums(album);
                }
            }
        }

        // Bir Album' ü güncelleştirmek için kullanılır
        public void UpdateAlbum(Album currentAlbum)
        {
            if ((currentAlbum.EntityState == EntityState.Detached))
            {
                this.ObjectContext.AttachAsModified(currentAlbum, this.ChangeSet.GetOriginal(currentAlbum));
            }
        }

        // Bir Albumu silmek için kullanılır
        public void DeleteAlbum(Album album)
        {
            if ((album.EntityState == EntityState.Detached))
            {
                this.ObjectContext.Attach(album);
            }
            this.ObjectContext.DeleteObject(album);
        }
    }
}

Domain Service sınıfı bu şekilde hazırlandıktan sonra artık asıl işimize odaklanabiliriz. İlk hedefimiz ChinookDomainService isimli sınıfın aslında çalışma zamanı için bir WCF Service olduğunu göstermektir. Buna göre Web üzerinden svc uzantılı olarak erişilebiliyor olması gerekmektedir. Peki WCF çalışma zamanı için herhangibir yerde bir tanımlama yer almakta mıdır? Aslında web.config dosyası içeriğine bakıldığında system.ServiceModel elementinin aşağıdaki gibi eklenmiş olduğu görülecektir.

Önemli Not : Örneğimizde EDM' yi Web uygulamamıza referans ettiğimiz bir Class Library içerisinde tuttuğumuz için, bu kütüphanenin App.config dosyası içerisine yazılan connectionString bilgisinin, Web uygulamasının web.config dosyasına eklenmesi gerekmektedir. Aksi takdirde çalışma zamanında hata alınacaktır.

Hımmm...O halde Asp.net development server' ın çalıştırılmasını sağlayıp herhangibir tarayıcıdan örneğimize göre http://localhost:4977/ChinookMusicStoreApp-Web-ChinookDomainService.svc adresini talep ettiğimde bir Service ekranı ile karşılaşmam gerekmektedir. Oysaki bu denemenin ardından ben aşağıdaki ekran görüntüsü ile karşılaştım. Frown

Oysaki Brad Abrams örneğinde svc uzantısından sonra servis içeriği görüntülenmiş hatta WSDL çıktısı bile elde edilebilmiştir. Acaba sorun nerededir?

Yaptığım araştırmalar sonucunda bu servise erişmek için bir Authentication ayarlamasının yapılması gerektiğini öğrendim. Bu çok doğaldı çünkü RIA Service' in kullanılmak istendiği Web Domain' i dışarısına açılması gibi bir durum söz konusuydu. Dolayısıyla web.config dosyası içerisinde system.Web elementi altında authentication için gerekli tanımlamaların yapılması gerekmekteydi. Bu örnekte herhangibir doğrulama kullanmadığımdan(Forms, Passport vb...) mode değerini None olarak bırakmayı tercih ettim.

<?xml version="1.0"?>
<configuration>

    <system.web>
        <httpModules>
            <add name="DomainServiceModule" type="System.Web.Ria.Services.DomainServiceHttpModule, System.Web.Ria, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        </httpModules>
        <compilation debug="true" targetFramework="4.0" />
      <authentication mode="None"/>
    </system.web>
.
.
.

Bu işlemin ardından http://localhost:4977/ChinookMusicStoreApp-Web-ChinookDomainService.svc adresine tekrardan talepte bulunduğumda aşağıdaki görüntü ile karşılaştım.

Hatta WSDL talebi sonrası söz konusu servisin Description içeriğinin de geldiğini gördüm.

Demekki Domain Service sınıfı yada geliştirdiğimiz RIA Service gerçektende bir WCF Service' miş. Wink 

Ancak ispatı tamamlamak için söz konusu servisi örnek bir istemcide kullanabiliyor olmam da gerekmekteydi. Bu nedenle Solution içerisinde yer alan Win Forms uygulamasına Add Service Reference ile http://localhost:4977/ChinookMusicStoreApp-Web-ChinookDomainService.svc adresinden gerekli Proxy üretiminin gerçekleştirilmesi yeterli olacaktı. Ben bu şekilde yoluma devam ettim ve aşağıdaki ekran görüntüsünde olduğu ilgili servis içeriğini izin verilen operasyonları ile(SubmitChanges metodunun da olduğuna dikkat edelim) üretilebildiğini gördüm.

İlgili ekleme işlemi sonrasında app.config dosyası içeriğinin aşağıdaki gibi üretildiğini farkettim.

Dikkat edileceği üzere iki adet EndPoint tanımlaması yapıldığı görülmektedir. Ben yazının bundan sonraki kısmında aşağıdaki ekran görüntüsüne sahip bir Form geliştirerek ilerlemeyi tercih etmekteyim.

Aslında bir önceki yazımızda yaptığımız gibi A' dan Z'ye Button bileşenlerimiz bulunmaktadır ve herhangibirine basıldığında bu harf ile başlayan albümler listelenmektedir. Buna ek olarak yeni bir Album ekleme özelliğide bulunmaktadır. Yeni bir Album nesnesi örneklendiğinde Title ve ArtistId değerlerinin mutlaka girilmesi gerekmektedir. ArtistId içinse kullanıcının var olan Artist' lerden herhangibirini seçmesi sağlanmaktadır. İşte Form uygulamamıza ait kodlarımız.

using System;
using System.Windows.Forms;
using ChinookClient.ChinookRef;


namespace ChinookClient
{
    public partial class Form1
        : Form
    {
        ChinookDomainServiceClient proxy;

        public Form1()
        {
            InitializeComponent();

            proxy = new ChinookDomainServiceClient("BasicHttpBinding_ChinookDomainService");
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            for (int i = 65; i < 91; i++)
            {
                Button btn = new Button();
                btn.Width = 24;
                btn.Height = 24;
                btn.Text = ((char)i).ToString();
                btn.Click += (snd, ea) =>
                    {
                        grdAlbums.DataSource=proxy.GetAlbumsByFirstLetter(btn.Text).RootResults;
                    };
                pnlButtons.Controls.Add(btn);
            }

            cmbArtists.DataSource=proxy.GetArtists().RootResults;
        }

        private void btnAddNewAlbum_Click(object sender, EventArgs e)
        {
            if (!String.IsNullOrEmpty(txtTitle.Text))
            {
                Album albm = new Album
                {
                    Title = txtTitle.Text,
                     ArtistId=((Artist)cmbArtists.SelectedItem).ArtistId
                };

                ChangeSetEntry[] entry = new ChangeSetEntry[]{
                    new ChangeSetEntry {
                      Entity=albm,
                       Operation= DomainOperation.Insert
                }
                };

                proxy.SubmitChanges(entry);
            }
        }
    }
}

Album' lerin alfabetik olarak elde edilmesi veya Artist listesinin getirilmesi bir yana, en önemli noktalardan biriside yeni bir Album nesnesinin nasıl eklendiğidir. Dikkat edileceği üzere bu işlem için ChangeSetEntry tipinden bir dizi kullanılmıştır. Aslında Insert, Update ve Delete işlemlerinde bu tipten yararlanılmakta ve toplu olarak değişikliklerin bildirilmesi sağlanabilmektedir. Örnekte bir Album nesnesinin eklenmesi istendiğinden ChangeSetEntry oluşturulurken Operation özelliğine DomainOperation.Insert sabit değeri atanmıştır. Buna göre albm isimli Album nesne örneği için bir Insert işlemi yapılacağı vurgulanmaktadır. ChangeSetEntry dizisi içerisinde yer alan insert, update ve delete işlemlerinin servis tarafına gönderilmesi içinse SubmitChanges metodunun kullanılması yeterlidir.

Önemli Not : Artist bilgilerinin ComboBox içerisinde görülebilmesi için istemci tarafına atılan Artist sıfınında ToString metodu override edilmiştir.

 

Bilindiği üzere Windows Forms uygulamalarında List kontrollerinin içeriklerine Object türünden herhangibir referans atanabilmektedir. Örneğimizde de Artist nesne örneklerinin atanması söz konusudur. Ancak ComboBox içerisindeki öğelerde hangi bilgilerin görüneceği ToString metodunun ezilmesi ile sağlanabilir. Tabi bu durumda bir sorunda ortaya çıkmaktadır. Service' te yapılacak değişiklikler sonrasında istemci tarafındaki referans güncellenirse Entity sınıfları tekrardan oluşturulacağından ezilen(Override) ToString metodunun uçacağı görülecektir. Bu duruma dikkat etmek gerekir.

İşte çalışma zamanına ait örnek bir görüntü.

Test olarak Van Halen isimli grub için Benim Şarkılarım isimli yeni bir albüm eklenmeye çalışılmıştır. Bu işlemin ardınan SQL tarafına bakıldığında Album isimli tabloya da ilgili satırın eklendiği gözlemlenebilir.

İşte bu kadar. WCF RIA Service' ler ile ilişkili araştırmalarıma devam etmekteyim. Yeni bilgiler edindikçe sizlerle paylaşıyor olacağım. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

ChinookMusicStoreApp.rar (590,82 kb)

WCF RIA Services - Kendi Sorgularımızı Kullanmak

Çarşamba, 25 Kasım 2009 13:30 by bsenyurt

Merhaba Arkadaşlar,

Bir önceki yazımızda WCF RIA Service' lerine kısa bir giriş yapmış ve ilk Hello World uygulamamızı geliştirmiştik. Bu yazımızda yine Chinook veritabanında yer alan albümlerin alfabetik olarak elde edilebildiği ve bunlara bağlı parçalarında gösterilebildiği bir Silverlight uygulaması yazmaya çalışacağız. Bu örnekteki temel amacımız ise, kendi sorgulama metodlarımızı ilgili DomainService sınıfı içerisinde nasıl geliştirebileceğimizi görmek ve Silverlight uygulamasında göze daha hoş gelecek(Her ne kadar buna kendimde inanamasam da Sealed ) bir arayüzü tasarlayabilmek olacak. İlk etapta hedefimizin aşağıdaki ekran görüntüsünde yer alan uygulama arayüzü ve fonksiyonelliğine ulaşmak olduğunu ifade etmek isterim.

Dikkat edileceği üzere A...Z' ye kadar sıralanmış bir Button kümesi görülmektedir. Bu düğmelerden herhangibirisine basıldığında, o harf ile başlayan albümlerin isimleri ComboBox bileşenine doldurulmaktadır. Kullanıcı eğer ComboBox bileşeninden bir albümü seçerse, bu albüm içerisinde yer alan şarkı listeside alt tarafta yer alan ve arka planında harikulade Cool bir manzaraya sahip olan DataGrid kontrolü içerisine doldurulmaktadır. Bu örnekte baş harfine göre albüm' lerin getirilebilmesi ve seçilen albüme ait olan şarkıların çekilmesi için WCF RIA Service içerisinde gerekli sorgu metodlarının yazılmış olması gerekmektedir. Bildiğiniz gibi Ado.Net Entity Data Model nesnesi içeriğinden seçilen Table, View yada Stored Procedure' lere göre Domain Service sınıfının içeriğinde hazır metodlar oluşmaktadır. Ancak bu metodlar her zaman için yeterli olmayabilir. Özellikle çok büyük boyutta veri kümelerinin döndürülmesi yerine performans açısından filtrelenmiş içeriklerin tedarik edilmesi tercih edilmelidir. Bu açıdan bakıldığında Domain Service sınıfı içerisine kendi operasyonlarımı eklemek veya var olanları uygun bir şekilde güncelleştirmek kaçınılmazıdır. Bu bilgilerden yola çıkarsak, geliştireceğimiz örnekte ilk hedefimiz üretilen Domain Service sınıfının metodlarını kendi istediğimiz şekilde geliştirmek olacaktır.

Kişisel Not: Entity Data Model ve DomainService' in nasıl hazırlanması gerektiğini bir önceki yazımızda incelediğimizden burada tekrar edilmeyecektir. Ancak Entity Data Model içerisinde Album ve Track tablolarının karşılıklarının kullanıldığını belirtmek isterim.

İlk olarak, ChinookDomainService adı ile oluşturacağımız Domain Service sınıfının içeriğindeki tüm operasyonları silip aşağıdaki hale getirdiğimizi düşünelim.


namespace SilverlightApplication5.Web
{
    using System.Linq;
    using System.Web.DomainServices.Providers;
    using System.Web.Ria;

    [EnableClientAccess()]
    public class ChinookDomainService : LinqToEntitiesDomainService<ChinookEntities>
    {
        public IQueryable<Album> GetAlbumsByFirstLetter(string firstLetter)
        {
            return from albm in ObjectContext.Album
                   where albm.Title.StartsWith(firstLetter)
                   orderby albm.Title
                   select albm;
        }

        public IQueryable<Track> GetTracks(int albumId)
        {
            return from track in ObjectContext.Track
                   where track.AlbumId == albumId
                   orderby track.Name
                   select track;
        }
    }
}

GetAlbumsByFirstLetter ve GetTracks isimli metodlar IQueryable<T> tipinden referanslar döndürmektedir. GetAlbumsByFirstLetter metodu parametre olarak string bir bilgi almakta ve Title bilgisi bu içerik ile başlayanların listesini geriye döndürmektedir(Örneğin baş harfi A olan albümlerin elde edilmesi). Diğer yandan GetTracks metodu, albumId isimli parametre sayesinde, bir albüme bağlı olan şarkıların listesini Name alanına göre alfabetik sırada döndürmektedir. Böylece çok basit olsalardan kendi operasyonlarımızı tanımlamış bulunmaktayız. Solution' u bu haliyle derlediğimizde Silverlight uygulaması içerisinde yer alan ChinookDomainContext sınıfında uygun metod çağrılarının oluşturulduğunu açık bir şekilde görebiliriz. 

DomainService sınıfını bu şekilde düzenledikten sonra sıra Silverlight uygulamasını geliştirmeye geldi. Bu amaçla MainPage.XAML içeriğini aşağıdaki gibi geliştirdiğimizi düşünelim.

<UserControl x:Class="SilverlightApplication5.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="401" d:DesignWidth="640" xmlns:dataInput="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input" xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data">

    <Grid x:Name="LayoutRoot" Background="White">
        <ComboBox Height="23" HorizontalAlignment="Left" Margin="11,62,0,0" Name="cmbAlbums" VerticalAlignment="Top" Width="449" SelectionChanged="cmbAlbums_SelectionChanged">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <TextBlock Text="{Binding Title}"/>
                    </StackPanel>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
        <dataInput:Label Height="28" HorizontalAlignment="Left" Margin="12,46,0,0" Name="label1" VerticalAlignment="Top" Width="120" Content="Albümler" />
        <StackPanel ScrollViewer.HorizontalScrollBarVisibility="Auto" Height="26" HorizontalAlignment="Left" Margin="11,14,0,0" Name="pnlButtons" VerticalAlignment="Top" Width="617" Background="#FFEBEBB2" Orientation="Horizontal" UseLayoutRounding="True"></StackPanel>
        <data:DataGrid AutoGenerateColumns="True" Height="285" HorizontalAlignment="Left" Margin="15,104,0,0" Name="grdTracks" VerticalAlignment="Top" Width="613" Visibility="Visible">
            <data:DataGrid.Background>
                <ImageBrush ImageSource="/SilverlightApplication5;component/Images/1243016_70835687.jpg" />
            </data:DataGrid.Background>
        </data:DataGrid>
    </Grid>
</UserControl>

Belkide dikkat çeken ilk nokta ComboBox kontrolü içerisinde bir DataTemplate kullanılmasıdır. Nitekim DataTemplate kullanarak Title alanını göstermek istediğimizi belirtmediğimiz durumda, AlbumId alanının getirildiğini görürüz. Bu bilgi son kullanıcı açısından çok anlamlı değildir. Ama tabiki DataTemplate içerisinde yer alan StackPanel elementinde istenilen kontroller kullanılarak daha fazla Album bilgisinin ComboBox' ın her bir öğesinde gösterilmeside sağlanabilir.

Gelelim MainPage için kod tarafına;

using System.Windows.Controls;
using System.Windows.Ria;
using SilverlightApplication5.Web;

namespace SilverlightApplication5
{
    public partial class MainPage
        : UserControl
    {
        ChinookDomainContext context= new ChinookDomainContext();

        void LoadButtons()
        {
            for (int i = 65; i < 91; i++)
            {
                Button btn = new Button();
                btn.Width = 20;
                btn.Height = 20;
                btn.Name = "Button_" + i.ToString();
                btn.Content = ((char)i).ToString();
                pnlButtons.Children.Add(btn);

                btn.Click += (o,e) =>
                    {
                        grdTracks.Visibility = System.Windows.Visibility.Collapsed;
                        LoadOperation<Album> albumLoadOpt = context.Load<Album>(context.GetAlbumsByFirstLetterQuery(btn.Content.ToString()));
                        cmbAlbums.ItemsSource = albumLoadOpt.Entities;
                    };
            }
        }

        public MainPage()
        {
            InitializeComponent();

            LoadButtons();
            grdTracks.Visibility = System.Windows.Visibility.Collapsed;
        }

        private void cmbAlbums_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {           
            if (e.AddedItems.Count > 0)
            {
                int albumId = ((Album)e.AddedItems[0]).AlbumId;
                LoadOperation<Track> tracks = context.Load<Track>(
                    context.GetTracksQuery(albumId),
                    (load) =>
                    {
                        grdTracks.Visibility = System.Windows.Visibility.Visible;
                    },
                    null
                    );
                grdTracks.ItemsSource = tracks.Entities;
            }
        }
    }
}

MainPage yapıcı metodu içerisinde Button bileşenlerinin oluşturulması işlemi gerçekleştirilmektedir. Bu işlem sırasında her Button bileşeni için Click olayının yüklenmesi sağlanmaktadır. Dikkat edileceği üzere Click olay metodu içerisinde, o anda basılan düğmenin Content bilgisinden yararlanılarak bir LoadOperation oluşturulur. Ayrıca, ChinookDomainContext nesne örneği üzerinden yapılan GetAlbumsByFirstLetterQuery metodu ile gerekli sorgunun elde edilmesi sağlanır. Bundan sonra ise ComboBox kontrolünün ItemsSource özelliğine gerekli veri bağlama işlemi yapılır. ComboBox kontrolünde bir öğenin seçilmesi halinde devreye giren SelectionChanged metodunda ise bu kez GetTracks servis metodunun çalıştırılması için gerekli işlemler yapılmaktadır. Yanlız bu seferki kullanımda ChinookDomainContext referansına ait Load metodunun ikinci parametresine dikkat edilmelidir. Bu parametre söz konusu Load işlemi tamamlandıktan sonra devreye girecek bir metodu işaret edecek Action<T> tipinden bir temsilcidir(delegate). Burada Load işlemi tamamlandığında görünür olmayan DataGrid kontrolünün görünür hale getirilmesi için sembolik bir işlem yapıldığını belirtebiliriz. Ancak ana fikir, Load operasyonunun tamamlanması ile kontrolü ele alabileceğimiz bir metodun işaret edilebiliyor olmasıdır. Bu kodlamanın ardından DataGrid kontrolünün ItemsSource özelliğine gerekli veri bağlama işleminin yapılması yeterlidir. Uygulamayı bu noktadan sonra teste çıkartabiliriz. Sonuç olarak yazımızın başında belirttiğimiz ekran görüntüsüne benzer sonuçları elde ebiliyor olmamız gerekmektedir.

Peki neler öğrendik?

  • WCF RIA Service' lerinde sihirbaz yardımıyla Entity Data Model' den otomatik olarak üretilen Domain Service sınıf metodları yerine kendi sorgulama metodlarımızı kullanabileceğimizi, var olanları istersek güncelleştirebileceğimizi,
  • İstemci tarafında, Domain Service sınıfı içerisindeki operasyonlara yapılacak olan çağrılarda Callback metodlarının değerlendirilerek yükleme tamamlandıktan sonrasını anlayıp bazı işlemler yaptırabileceğimizi,
  • Silverlight uygulaması içerisindeki kontrollerde DataTemplate kullanarak, servis tarafından çekilen Entiy içeriklerinin sadece istediğimiz alanlarının kullanılabileceğini,
  • Silverlight tarafında dinamik olarak kontrollerin nasıl üretilip ilgili elementlere eklenebileceğini,

öğrendik.

hatta Background özelliğine Picture ekleyebileceğimizi ve bu sayede daha hoş bir görüntü sunabileceğimizi farkettik demek istesemde, bu önemsenecek bir mevzu değildir. Wink 

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

SilverlightApplication5.rar (5,28 mb)

WCF RIA Services - Bir Merhaba Diyelim

Salı, 24 Kasım 2009 10:56 by bsenyurt

Merhaba Arkadaşlar,

Yağmurlu günler ve kış geldikçe bir blog yazarının ilham gelmesini bekleyerek zaman geçirmesine hiç mi hiç gerek yoktur? (Tabi yandaki resimde görülen köprüye o açıdan uzun uzun baktığınızda bloğunuza yazacak çok güzel fikirler edinebilirsiniz) Aslında bu gün itibariyle blog yazısına konsantre olmak için gerekli şartlar zaten mevcuttur. Bir adet bilgisayar, internet bağlantısı, gerekli referans kitaplar(eğer konu ile ilişkili bulunabilirse), kapalı bir hava, güzel bir müzik ve onu kulaktan beyin hücrelerine kaliteli bir şekilde aktaracak kulaklıklar ile yağmurlu bir gün. Tabi insan bazen bloğuna yazarken bir solukta işe yarayacak eserlerde çıkartmak isteyebilir. İşte ben bu yüzden Hello World uygulamalarını çok severim. Zaten Microsoft pek çok eğitim materyalinde detaya girmeden önce, basit bir Hello World uygulaması ile "aslında biz bu konsept ile neyi yapabiliyoruz" sorusuna cevap vererek başlamayı tercih etmiştir,etmektedir. Örneğin çok çok eski Xml Web Services eğitiminde böyle bir giriş bulunmaktadır. Önce yazılmış olan bir Xml Web Service' inin kullanıdırıması öğretilir. Dikkat edin eğitimin amacı Xml Web Service' lerini geliştirmek. Öyleyse bu gün menümüzde ne var bir bakalım.

Takip eden arkadaşlarımız bir süre önce Microsoft PDC 2009 konferansının gerçekeştiğini ve pek çok yeniliğin tanıtıldığını bilirler. Benim açımdan önemli olan gelişmelerden biriside pek çok teknolojinin adının değişmesi olmuştur. Özellikle WCF tabanlı olarak geliştirilen pek çok yardımcı servis modelinin adı, benimde istediğim ve beklediğim gibi tekilleştirildi. Buna göre Ado.Net Data Services' ler WCF Data Services ve Rich Internet Application' lar için n-tier sorununu servis bazlı olarak kolayca aşmamızı sağlayan .Net RIA Services' da WCF RIA Services olarak isim değiştirmiştir. Aslında bu bilgilerden yararlanıldığında bir WCF eko sisteminin oluşturulduğunu ve içerisinde Workflow Services, Data Services, RIA Services, Web HTTP Services ve Core Services gibi kavramların yer aldığını ifade edebiliriz. Şimdilik bu eko sistemin içerisine çok fazla girmeyeceğiz. Bu yazımızdaki amacımız Beta' sı yayımlanan WCF RIA Services' e Bir Merhaba diyebilmek.

Bildiğiniz üzere RIA Service' leri ile Silverlight gibi Rich Internet Application istemcilerinin n-tier modeline göre geliştirilebilmesi oldukça kolaylaştırılmaktadır. Özellikle Silverlight 4.0 ile Visual Studio 2010 tarafına getirilen yeni özellikler(örneğin Windows uygulamalarındaki gibi Data Sources kısmının kullanılabilmesi ve bu sayede sürükle-bırak desteği) ve WCF RIA Services bir araya geldiğinde oldukça güzel sonuçlar ortaya çıktığını söyleyebiliriz. Aslında söz konusu kolaylıkları görsel derslerimizde incelemeye çalışcağımız şimdiden belirtmek isterim. Ancak hemen öncesinde çok basit bir Hello World uygulaması yaparak, Silverlight uygulaması içerisinden WCF RIA Service' lerin nasıl kullanılabileceğini görelim. Örneğimizi mümkün olduğunda basit bir şekilde gerçekleştireceğiz. WCF RIA Service' imiz arka planda Entity Data Model' i kullanıyor olacak. Bu amaçla kaynak bir veritabanını SQL üzerinde ele alacağız. Ancak bu sefer AdventureWorks değil Smile Yihaaaa!!! Bu kez örnek olarak Chinook isimli Codeplex üzerinden yayınlanan ve pek çok MVP, Microsoft çalışanı ve profesyonlin konu anlatımlarında kullandığı açık kaynak veritabanını ele alacağız. Haydi bakalım parmakları sıvayalım. Wink 

İlk olarak örneğimizi Visual Studio 2010 Ultimate Beta 2 üzerinde geliştirdiğimizi ifade etmek isterim. Diğer yandan Silverlight 4.0 Beta ve WCF RIA Services kurulumlarının da yapılmış olması gerektiğini hatırlatalım. Eğer bu kurulumlar tamamlandıysa işe basit bir Silverlight Application projesi oluşturarak başlayabiliriz. Oluşturma işlemi sırasında, Enable .NET RIA Services seçeneğini etkinleştirmemiz gerekmektedir ki WCF RIA Service' leri kullanabilelim(Tabi isim değişse de IDE üzerinde henüz değişmemiş olduğu gözden kaçmamalıdır. Belkide isim değişmez. Immm...Bilemiyorum. Değişirse iyi olur tabi)

Projemizin bu şekilde oluşturulması sonrasında Silverlight uygulamasının host edileceği ayrı bir Web uygulamasının da oluşturulduğunu görebiliriz. Söz konusu Web uygulaması hem Entity Data Model' i hemde DomainService sınıfını içerecektir. Veri modeli için ChinookModel isimli yeni bir Ado.Net Entity Data Model öğesini Web projesine ekleyerek devam edelim. Başlangıç için aşağıdaki şekilde görülen Entity tiplerinin oluşturulmasını sağlayabiliriz.

Web uygulaması tarafında artık bir veri modelimiz bulunmaktadır. Silverlight uygulamasına veri modelinden hizmet sunabilmek için(CRUD-CreateReadUpdateDelete işlemleri), yine Web projesine bir DomainService sınıfı ekleyerek devam etmemiz gerekmektedir. Bunun için Web sekmesinden Domain Service Class öğesini seçmemiz yeterlidir.

Karşımıza çıkacak olan Wizard adımlarında, kullanacağımız veri modelini ve ilgili Entity tiplerini seçerek ilerliyor olacağız. Burada hemen bir ipucunu vermek isterim; Entity Data Model oluşturulduktan sonra projeyi derlemezsek, Available Data Contexts/ObjectContexts ComboBox' ında herhangibir içeriğin çıkmadığı görülecektir. Derleme işleminden sonra ise aşağıdaki şekilde görüldüğü üzere sadece Album Entity tipini seçerek ilerleyelim.

Album Entity' si üzerinden örneğimizde veri ekleme, çıkartma ve silme işlemlerini bu yazımızda yapmıyor olacağız ancak arka planda oluşturulan servis sınıfı içerisinde ne gibi kodlamalar yapıldığını görmekte yarar var. Bu adımı geçtikten sonra Web projesi tarafında üretilen ChinookDomainService sınıfını kısaca incelemenizi tavsiye ederim. İlk etapta sınıf diagramına baktığımızda aşağıdaki içeriğe sahip olduğunu görebiliriz.

Dikkat edileceğiz üzere LinqToEntitiesDomainService<ChinookEntities> tipinden türeyen sınıf içerisinde CRUD işlemleri için üretilmiş dört metod bulunmaktadır. GetAlbums isimli metod IQueryable<Album> tipinden bir referans döndürmektedir. Buda istemci tarafından LINQ ifadeleri ile sorgulanabilir bir içeriğin yakalanabileceğini göstermektedir. Ayrıca Delete, Insert ve Update işlemlerinde parametre olarak Album tipinden nesne örneklerine ihtiyaç duyulmaktadır ki bunların tamamı Silverlight uygulaması tarafından da kullanılabilir. Nitekim Silverlight uygulamasındaki gizli dosya içeriklerine bakıldığında gerekli Entity tipinin bu tarafa da taşındığı görülebilir.

Web tarafında yer alan DomainService sınıfı içerisinde şimdilik GetAlbums metodunu kullanıyor olacağız. Nitekim ilk amacımız Album listesini Silverlight tarafındaki bir DataGrid kontrolüne çekmek olacak.

[EnableClientAccess()]
    public class ChinookDomainService
        : LinqToEntitiesDomainService<ChinookEntities>
    {
        public IQueryable<Album> GetAlbums()
        {
            return this.ObjectContext.Albums;
        }
// KOD DEVAM EDİYOR...

Burada önemli olan noktalardan birisi sınıfın başında istemci erişimine izin verildiğini belirten EnableClientAccess isimli niteliğin(Attribute) kullanılmış olmasıdır. Diğer yandan GetAlbums metodu aslında ObjectContext içerisinde Albums özelliği üzerinden album içeriğini döndürmektedir. Dolayısıyla LINQ sorgusu ile Where gibi operatorler kullanılabilir ve metod istendiğinde parametrik olarak çalıştırılabilir(Bu gibi pek çok farklı konuyu görsel derslerimde ele almayı planladığım için burada fazla detaya girmeyeceğiz). Artık Silverlight uygulamamızı tasarlamaya başlayabiliriz. Bu amaçla MainPage.xaml içeriğini aşağıdaki gibi geliştirdiğimizi düşünelim.

<UserControl x:Class="SilverlightApplication3.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="600" xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data">

    <Grid x:Name="LayoutRoot" Background="White">
        <data:DataGrid AutoGenerateColumns="True"
                       Height="250"
                       HorizontalAlignment="Left"
                       Name="grdAlbums"
                       VerticalAlignment="Top"
                       Width="600" />
        <Button Content="Get Albums"
                Height="23"
                HorizontalAlignment="Left"
                Margin="12,265,0,0"
                Name="btnGetAlbums"
                VerticalAlignment="Top"
                Width="75"
                Click="btnGetAlbums_Click" />
    </Grid>
</UserControl>

Burada küçük bir ipucu daha vermek isterim; başlangıçta DataGrid bileşeninin AutoGenerateColumns özelliği False değerine sahiptir. Bu nedenle kodlama doğru bir şekilde yapılsa dahi içeriğin DataGrid kontrolüne aktarılmadı görülecektir. Dolayısıyla ilgili niteliğin değerini bu örnek için True yapmayı unutmamalıyız. btnGetAlbums isimli düğmeye basıldığında tüm Album listesinin bir DataGrid içerisine doldurulmasını sağlamak için, kod tarafını aşağıdaki gibi geliştirmemiz yeterlidir.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Ria;
using SilverlightApplication3.Web;

namespace SilverlightApplication3
{
    public partial class MainPage
        : UserControl
    {
        ChinookDomainContext context;

        public MainPage()
        {
            InitializeComponent();
            // DomainContext nesnesi örneklenir
            context = new ChinookDomainContext();
        }

        private void btnGetAlbums_Click(object sender, RoutedEventArgs e)
        {
            // Asenkron Load operasyonunu gerçekleştirecek tip tanımlanır
            // Tip parametre olarak Album' leri çekmek için gerekli sorguyu üreten GetAlbumsQuery metodunu kullanmaktadır.
            LoadOperation<Album> loader = context.Load<Album>(context.GetAlbumsQuery());
            grdAlbums.ItemsSource = loader.Entities; // Load operasyonu tarafından elde edilen Entite yüklenir
        }
    }
}

Ve çalışma zamanındaki sonuç; 

Tabi daha ne kolaylıklar bar Visual Studio tarafında bir bilseniz. Wink İştahınızı kabartmış olabilirim ama devamı için görsel videolarımı bekleminizi öneririm. Umarım vakit bulup çekeceğim. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

SilverlightApplication3.rar (1,28 mb)