14 Ocak 2011 Cuma

Genişletilebilir Uygulama Yazma

Uygulamaların hem fonksiyonel hem de ara yüz olarak genişletilebilir olması bir ihtiyaçtır. Fakat genelde uygulamalar tek parça halinde yazılırlar ve tak-çalıştır mantığına sahip bir genişletilebilme yeteneği sunmazlar. System.ComponentModel.Composition isim uzayında bu genişletilebilirlik problemi için isteyen her uygulamanın kullanabileceği basit bir genişletilebilir uygulama çözüm alt yapısı sunulmaktadır. Alt yapı çalışma anında uygun genişleletme parçalarını bulur ve yükler. Ayrıca alt yapı yüklenen parçaların yaşam döngülerini de takip eder.

System.ComponentModel.Composition ile birlikte uygulamalar için Katalog (Catalog) ve taşıyıcı (Container) kavramları gelmektedir.

Katalog sadece uygulama genelinde ki dağıtım(Export) tanımlarını saklamaktadır. Yani uygulamanın ihtiyaç duyduğu bir nesnenin nereden ve nasıl oluşturulacağının cevabını katalog vermektedir. Katalog çalışma zamanında kendisini güncelleyen bir yapıdadır. Belirtilen dizinde ki dll’ler içinden dağıtım tanımlarını toplayabilmektedir. Dizin içinde ki dll’ler değiştikçe katalogda kendisini güncellemektedir.

Taşıyıcı ise parçaları(Part) bir araya getiren kısımdır. Her bir parça kodlanırken uygulama geneline ithal etmek istediği (Export) tanımları ve uygulamadan almak istediği (Import) ihraç tanımları söylemektedir. Taşıyıcı çalışma anında parçaları oluşturur ve kullanımda olduğu sürece saklar.


Taşıyıcı ve katalog arasında çalışan fakat bizim görmediğimiz ExportProvider sınıfı vardır. ExportProvider sınıfı parçaları oluşturan sınıftır.Taşıyıcı ve katalog ile konuşarak kendisinden istenen parçayı üretmektedir.

Ortamı Hazırlama

Katalog Oluşturma

Uygulama ilk olarak tip tanımlarını saklayacağı bir tip katalog oluşturmalıdır. En sık kullanılan kataloglardan ilk AssemblyCatalog’dur. AssemblyCatalog verdiğiniz assembly dosyasını taramakta ve parça kontratlarını saklamaktadır.

[cs]

var  assemblyCatalog = new  AssemblyCatalog (Assembly.GetExecutingAssembly ());

Başka bir katalog türü ise DirectoryCatalog’dur. DirectoryCatalog ise verilen dizinde ki tüm assembly dosyalarını taramakta ve bulduğu tüm parça taşıma kontratlarını saklamaktadır.

[cs]

var  directoryCatalog = new  DirectoryCatalog (Path .Combine (".." , "Plugin" ));

Birden çok katalogu AggregateCatalog ile bir arada kullanarak daha geniş bir parça taşıma kontratı havuzuna sahip olabilirsiniz.

[cs]

var  aggregateCatalog = new  AggregateCatalog (assemblyCatalog, directoryCatalog);

Bu üç katalog dışında daha az kullanılan SilverLight’a özel DeploymentCatalog ve sadece parametre olarak verilen nesneler için tip katalogu oluşturan TypeCatalog sınıfları da mevcuttur.

Taşıyıcı Oluşturmak

Parçalarımızın program içinde nasıl hareket edeceğini gösteren kontratlarımızı barındıran katalogumuzu oluşturduk. Şimdi sıra katalogu kullanacak olan taşıyıcıyı oluşturmaktadır. Taşıyıcıyı iki şekilde oluşturabilirsiniz. Eğer taşıyıcıya hiçbir katalog vermeden oluşturursanız taşıyıcı varsayılan olarak katalogsuz olarak oluşturacaktır. Fakat katalog olmadığı için parçaları oluştururken gerekli tüm parçaları doğrudan taşıyıcıya eklemeniz gerekecektir. Burada dikkat edilmesi gereken husus taşıyıcı [Export] [Import] niteliklerini sahip parçalar ile çalışabilmektedir.

[cs]

var  compositionContainer = new  CompositionContainer ();

Katalogsuz taşıyıcı kullanma durumu hemen hemen hiç kullanılmamaktadır. Genelde katalogsuz olarak taşıyıcı oluşturulmamaktadır. Eğer kendinize ait bir katalog oluşturdunuzsa taşıyıcının bu katalog ile çalışmasını isteyebilirsiniz.

[cs]

var  aggregateCatalog = new  AggregateCatalog (assemblyCatalog, directoryCatalog);
 var  compositionContainer = new  CompositionContainer (aggregateCatalog);

Parçalar ve Kontratlar

Katalog ve taşıyıcıyı oluşturduktan sonra sıra uygulama parçalarını oluşturmaya geldi. [Export] ve [Import] niteliğine sahip tüm sınıflar, ara yüzler, değişkenler, metotlar program parçası sayılmaktadır. [Export] ve [Import] nitelikleri ile parçalar taşıyıcıya uygulama içinde nasıl hareket edeceğini yani taşınma kontratlarını belirtmektedir.

Dışa Aktarım

Her hangi bir parçaya sadece [Export] niteliği ekleyerek dışa aktarılabilir olmasını sağlayabilirsiniz.

[cs]

// 1- Basit dışa aktarım
 [Export ]
 public  class  SimpleContractExport

Sınıfınızı üst sınıfın bir örneği olarak da dışa aktarabilirsiniz.

[cs]

// 2- üst sınıfı ile dışa aktarım kontratı 
 [Export (typeof (SimpleContractExport ))]
 public  class  SimpleDrivenTypeExport  : SimpleContractExport

Sınıfınızı özel bir etiket belirterek de dışarı aktarabilirsiniz. Bu durumda aynı etiket ile çağrıldığında bu dışa aktarım kontratı çalışacaktır.

[cs]

// 3- etiket ile dışa aktır kontratı
 [Export ("SimpleTagedTest" )]
 public  class  SimpleTagedTypeExport

Dışarı aktarmak istediğiniz bir ara yüzün uygulaması da olabilmektedir.

[cs]

// 4- bir arayüz uygulması olarak dışa aktarma
 [Export (typeof (ISimpleInterface ))]
 public  class  InterfaceContractExport  : ISimpleInterface

Her zaman bir sınıfı aktarmak zorunda değilsiniz. Diğer program bileşenleri de dışarı aktarılabilmektedir. Her hangi bir property dışarı aktarılabilmektedir.

[cs]

// 5- bir property olarak dışa aktarım kontratı
 [Export ("NextString" )]
 public  string  NextStringProperty

Yukarıdaki örnekte bir etiket verilmiştir çünkü bir string tipi dışarı aktarılmaktadır. Birçok string tipi arasında bulabilmek için bir etiket verilmiştir. Genelde property olarak aktarımlar Factory Pattern uygulamak için kullanılmaktadır.

[cs]

// 5- bir property olarak dışa aktarım kontratı
 [Export ]
 public  ISimpleInterface  SimpleInterfaceFactory 
 {
     get 
     {
         return  new  OtherInterfaceContractExport ();
     }
 }

Tam bu noktada nesnelere police enjekte etmek için ara bir çözüm bulunmaktadır. Eğer uygulamanızda ki sadece birkaç nesne üzerinde “Policy Injection” kullanıcaksanız property olarak aktarım işinize yarayacaktır.

[cs]

// 5- bir property olarak dışarı aktarım  
 // ve özel bir sınıfta policy injection kullanımı
 [Export ]
 public  ISimpleInterface  SimpleInterfaceFactoryWithPI 
 {
     get 
     {
         return  RoC.PI.ProxyCreator
             .CreateObject ();
     }
 }

Sadece property’leri değil event’larıda dışarı aktarabilmektesiniz.

[cs]

// 6- bir metod olarak dışa aktarım kontratı
 [Export (typeof (Func))]
 public  string  GetNext ()

Olayları uygulama genelinde paylaşırken dikkatli olmak gerekmektedir. Zira bir olayı dışarı aktarmak için taşıyıcı önce olayı barındıran sınıfı oluşturacaktır. Olayı barındıran sınıf olaya bağlanan tüm delegeler yok olduğunda yok edilecektir. Fakat olaya bağlanan delegelerin olduğu sınıflarda yok edilmek içinde delegelerin yok edilmesini bekleyecektir. Yani uygulamanızda gereksiz yere bir birini bekleyen nesneleriniz kalacak ve bunlar yok edilmeyecektir. Bu durumda kullanılacak WeakReference , WeakEventPattern ve EventAggregator gibi yöntemler bulunmaktadır. Başka bir yazıda açıklamak üzere uygulama genelinde olay paylaşımının tehlikesinden bahsedip geçmiş olalım.

Bazen de bir üst sınıf veya ara yüze ait tüm alt sınıfların dışarı aktarılabilir olmasını istersiniz. Bu durumda üst sınıfınızı işaretleyerek tüm alt sınıflarınızın taşınabilir olmasını sağlayabilirsiniz.

[cs]

// 7- bir üst sınıf olarak dışa aktarım kontratı
 [InheritedExport ]
 public  interface  INext { .. }
 public  class  NextClass1  : INext { .. }
 public  class  NextClass2  : INext{ .. }

Böylelikle kullanabileceğimiz dışa aktarım kontratlarını inceledik. Hafızada yer tutan tüm programlama öğelerini dışarı aktarabiliriz. Aktarırken bir etiket verebilir veya program öğesinin tipi ile değil de dönüşebileceği her hangi bir tip ile aktarabiliriz.

İçe Aktarım

Tabii ki dışarı aktardığımız program parçalarının diğer program parçaları tarafından içeri aktarılması gerekmektedir. Dışa aktarıma benzer şekilde içe aktarım yolları vardır.

Nesnenin oluşması sırasında Cunstructor parametresi olarak diğer parçaları ithal edebilirsiniz. Aşağıdaki örnekte SimpleImport nesnesi oluşturulurken taşıyıcı InterfaceContractExport tipine ait bir dışa aktarım kontratı olup olmadığına bakar. Bulduğu InterfaceContractExport kontratı ile eğer taşıyıcı üzerinde nesne örneği yoksa önce aktaracağı InterfaceContractExport nesnesini oluşturur. Taşıyıcıda içe aktarılacak InterfaceContractExport nesne örneğini alır ve SimpleImport sınıfına constructor parametresi olarak gönderir.

[cs]

// 1- constractor parametresi olarak kontratları içe aktarım
[ImportingConstructor ]
public  SimpleImport (InterfaceContractExport  simpleInterfaceImplemantation)

Her zaman ithal etmek istediğiniz parça için bir dışa aktarım kontratı olmayabilir. Eğer ithal etmek istediğiniz parça var ise parçayı ithal etmeniz gerekmektedir.

[cs]

// 2- opsiyonel içe aktarı 
 // eger import edilen parça var ise getir yoksa Default(T) değerini getir 
 [ImportingConstructor ]
 public  SimpleImport ([Import (AllowDefault  = true )]INext  next)

İçe aktarımı property olarak da kullanabilirsiniz. Nesne create edildikten sonra property aktarımları atanacaktır.

[cs]

// 3-  Property olarak kontratları içe aktarım
 [Import ]
 public  SimpleContractExport  Simple  { get ; set ; }

Property içe aktarımla aynı şekilde değişkenlerde de içe aktarım yapılabilmektedir.

[cs]

// 4. Field olarak içe aktarım 
  // etiketleri tüm aktarımda kullanabilirsiniz
 [Import ("SimpleTagedTest" )]
 private  SimpleTagedTypeExport  _simpleTagedTypeExport ;

Eğer içe aktarmak istediğiniz parça taşıyıcı üzerinde birden çok varsa bunların hepsini birden alabilirsiniz. Bir sonra ki yazıda taşıyıcı üzerinde ki parçalar üzerinde sorgular çalıştırıp istediğiniz özel parçaları bulmayı inceleyeceğiz. Şimdilik belirli bir tipte ki tüm parçaları alalım.

[cs]

// 5 -kontratları çoklu içe aktarım
 [ImportMany ]
 private  INext [] _nextArray ;

Bu şekilde parça paylaşan programlarda en büyük ihtiyaçlardan biriside her hangi bir parçanın ilk erişildiğinde oluşturulmasıdır. Yani içe aktardığınız parçaya ait bir metot veya property erişimi yapana kadar parça oluşturulmayacaktır. Parçayı kullanan sınıf ilk ne zaman erişim sağlarsa o zaman içe aktarılan parça oluşturulacaktır.

[cs]

// 6 -kontralları gevşek bağlama (lazyload ) ile içe aktarım
 // ilk erişeme kadar nesneyi taşıyıcı üzerinden almaz.
 // ilk erişimde nesne taşıyıcıdan getirilir
 [Import ]
 public  Lazy  SimpleLazy  { get ; set ; }
 public  void  UseLazy ()
 {
     // SimpleLazy.IsValueCreated  == false
 
     // ilk erişimi yapılıyor bu kod satırı çalışana
     // ISimpleInterface nesne örneği oluşturulmuyor
     SimpleLazy .Value .GetExecutingAssemblyName ();
 
     // SimpleLazy.IsValueCreated  == true 
 }

Basit Genişletilebilir Uygulama

Basit bir uygulama ile genişletilebilir (composite) özelliklerini test edelim. Öncelikle katalogu olmayan bir taşıyıcıyı nasıl kullanacağımızı görelim:

[cs]

[Export ]
 public  class  ParameterTest1 
 {
     public  int  Number1 { get  { return  10; } }
 }
 public  class  ParameterTest2 
 {
     [Export ("ParameterTestNumber" )]
     public  int  Number2  { get  { return  11; } }
 }
 public  class  ContainerWithOutCatalog 
 {
     public  ContainerWithOutCatalog ()
     {
         // katalog olmadan taşıyıcı oluştur
         var  container = new  CompositionContainer ();
         // this içinde ki [Import] ları parametrelerle doldur
         container.ComposeParts (this ,new  ParameterTest1 (), new  ParameterTest2 ());
         Console .WriteLine (
             "ContainerWithOutCatalog.ParameterTest.Number1:{0}" ,
             ParameterTest .Number1 );
         Console .WriteLine (
             "ContainerWithOutCatalog._numberTest:{0}" ,
             _numberTest );
     }
     [Import ]
     public  ParameterTest1  ParameterTest  { get ; set ; }
     [Import ("ParameterTestNumber" )] private  int  _numberTest ;
 }

Yukarıda ki kodlar sorunsuz şekilde çalışmaktadır. Dikkat edilmesi gereken [Import] yapılan tüm parçaların ComposeParts fonsiyonuna parametre olarak vermek durumundasınız. Çünkü gerekli parçalara ait [Export] kontratlarını alabilecek bir kataloga sahip degilsiniz.

Kataloga sahip bir taşıyıcı ile çalışmayı görelim:

[cs]

public  class  ContainerWithCatalog 
 {
 public  ContainerWithCatalog ()
 {
     var  assemblyCatalog = new  AssemblyCatalog (GetType ().Assembly );
     var  compositionContainer = new  CompositionContainer (assemblyCatalog);
     compositionContainer.ComposeParts (this );
     //üüm [Export] ve [Import]ıı y dene 
     Console .WriteLine ("ContainerWithCatalog.ImportSimple:{0}" , 
         _simpleImport .SimpleContractImport .Number ());
     Console .WriteLine (
         "ContainerWithCatalog.ImportAllowDefault:{0}" ,
         _simpleImport .NonExported  == default (INext ) ? "default"  : "non default" );
     Console .WriteLine ("ContainerWithCatalog.ImportDrivenType:{0}" ,
         _simpleImport .DrivenTypeImport .Number ());
     Console .WriteLine (
         "ContainerWithCatalog.ImportWithLabel:{0}" ,
         _simpleImport .GetSimpleTagedTypeExport2 ());
     Console .WriteLine (
         "ContainerWithCatalog.ImportingConstructor:{0}" ,
         _simpleImport .SimpleInterface .Number ());
     Console .WriteLine (
         "ContainerWithCatalog.ImportProperty:{0}" ,
         _simpleImport .NextStringProperty );
     Console .WriteLine (
         "ContainerWithCatalog.ImportEvent:{0}" ,
         _simpleImport .NextNumber .Invoke ());
     Console .WriteLine (
         "ContainerWithCatalog.ImportMany:{0}" ,
         _simpleImport .WriteAllNext ());
 }
 [Import ] private  SimpleImport  _simpleImport ;
 }

Uygulamanın çıktısı şu şekilde görülecektir:

 Temel seviyede genişletilebilir bir uygulama geliştirebilir duruma geldik. Artık özgürüz! Kendimiz için bir katalog oluşturabiliriz. Oluştuğumuz katalogu kullanarak uygulama parçalarını bir araya getirecek taşıyıcıyı oluşturabiliriz. Taşıyıcı uygulama parçalarımızı bizim belirlediğimiz kontratlar ile uygulama içinde yönetecektir. Bir sonra ki yazıda katalog üzerindeki kontratları nasıl sorgulayacağımızı, parçalarımızın hayat döngüsünü, taşıyıcıyı ve katalogu çalışma anında değiştirmeyi inceleceğiz.

4 Ocak 2011 Salı

150 satırda Policy Injection

Programlamada kodlar uzadıkça kodların anlaşılabilirliği düşmektedir ve çoğu zamanda içinden çıkılmaz bir hal almaktadır. Tek bir "basit" kayıt işlemini yapan kod bloğu bile tüm uygulamada geçerli olan hata yakalama, loglama, yetki kontrolü gibi çapraz modüllere ait kodlardan dolayı "karmaşıktır". Örneğim aşağıda bu "basit" kayıt işlemine bir örnek verilmiştir.

[cs]

public  void  SaveOrderToDB (Order  order, User  user)
 {
     LogManager .BeginPerformanceCounter ("SaveOrderToDB" );
     if  (order == null  || user == null )
         throw  new  NullReferenceException ();
     if  (!user.IsInRole ("Sales" ))
         throw  new  SecurityException ();
     var  totalAmount = order.Unit *order.UnitAmount ;
     var  balance = GetBalanceFormAccount (order.AccountId );
     if  (totalAmount > balance)
         throw  new  ApplicationException ();
     try 
     {
         var  dbContext = new  DbContext ();
         dbContext.Orders .Add (order);
         dbContext.SaveChanges ();
     }
     catch  (Exception  exception)
     {
         LogManager .EndPerformanceCounter ("SaveOrderToDB" );
         LogManager .SaveException (exception);
         throw ;
     }
     LogManager .EndPerformanceCounter ("SaveOrderToDB" );
 }

Burada görüldüğü üzere müşterinin hesabından yapılan basit bir ödeme işleminde bile kodu karmaşık hale getiren bir çok çapraz modül söz konusudur. Öncelikle fonksiyona girerken loglama modlü çalıştırılıyor sonra parametrelerin doğrulanması daha sonra işlemi yapan kullanıcının yetki doğrulaması yapılıyor. Daha sonra hata kontrolleri ve en son fonksiyondan cıkmadan önce tekrar loglama yapılıyor. Aslında yapılmak istenen sadece veri tabanına doğru bir kayıt eklemektir. Buda 26 satırlık fonksyonumuzda ki 9 satırda ya yapılmaktadır. Poliçe enjekte veya Cephe Yönelimli (Asspect Oriented) programlama yaklaşımı bu çapraz modülleri mümkün olduğunca bir noktadan yönetilmesini ve fonksiyonun içinde ki kod bloğundan uzaklaştırılmasını amaçlamaktadır. Eğer bu fonksiyonu Policy Injection kullanarak yazmış olsaydık aşağıdaki şekilde görülecekti:

[cs]

[PerformanceCounterHandler ]
 [ExceptionHandler ]
 [CheckUserIsInRoleHandler ("Sales" )]
 [CheckArgumentsHandler ]
 public  void  SaveOrderToDB (Order  order, User  user)
 {
     var  totalAmount = order.Unit  * order.UnitAmount ;
     var  balance = GetBalanceFormAccount (order.AccountId );
     if  (totalAmount > balance)
         throw  new  ApplicationException ();
     var  dbContext = new  DbContext ();
     dbContext.Orders .Add (order);
     dbContext.SaveChanges ();            
 }

Görüldüğü üzere kodlama daha temiz ve sade oldu. Bu fonksiyonda tüm gerekli çapraz modüller fonksiyona bir poliçe olarak eklenmektedir. Fonksiyona yapılan çağrıda resimde görüldüğü şekilde bir sorumluluk zinciri takip edilmektedir. Önce Performans counter modülü sonra sırası ile diğer modüller çalışacak en son metot çalışacaktır. Daha sonra sonuç aynı yolu izleyerek geri döndürülecektir. Bu yapıya sorumluluk zinciri denmektedir. Yukarıda ki örneğimizde sorumluluk zinciri şu şekilde çalışacaktır: Performans Counter -> Exception Handler -> Sheck User Is In Role -> CheckArguments -> Metot -> Check Arguments -> Check User Is In Role -> Exception Handler -> Performans Counter. Bu sıralama sizi korkutmasın performans olarak kaybınız mili saniyelerin altındadır. Kod sadeliği ve yönetim kolaylığı olarak kazançınız çok daha fazla olacaktır.

Peki projemizde ki çapraz modülleri fonksiyonlarımıza birer poliçe olarak nasıl enjekte edebiliriz ?

Her şeyden önce poliçe enjekte edebilmek için nesnenin her hangi bir metotu çağrıldığında tetiklenecek bir yönetim fonksiyonuna ihtiyaçımız var. Yani sınıfa ait property'ler değiştiği zaman fırlatılan PropertyOnChanged olayına benzer, fonksiyon çağrılarında tetiklenecek bir mekanizmaya ihtiyacımız var. İhtiyaçımıza proxy sınıfı cevap vermektedi. Proxy sınıfı içlerinde gerçek nesneyi barındıran gerçek nesneye yapılan her çağrıyı izleyen sınıflardır. Proxy içende barındırdığı nesneye bir çağrı geldiğinde Invoke fonksiyonu tetiklenmekte ve gerçek nesneye ait çağrı bu fonksiyon içinde yapılmaktadır. Proxy'ler yardımı ile nesneye gelen metot çağrılarını tek bir yerden kontrol edebiliriz. Fakat nesneye bir proxy oluşturabilmek için nesnelerimizi MarshalByRefObject sınıfından türetmeliyiz. Böylece proxy metot çağrılarını takip edebilecektir.

[cs]

public  class  PITest2 : MarshalByRefObject 
 {
     [PerformanceCounterHandler ]
     [ExceptionHandler ]
     [CheckUserInRoleHandler ("Sales" )]
     [CheckArgumentsHandler ]
     public  void  SaveOrderToDB (Order  order, User  user)
     {

Nesnemizi proxy oluşturabilecek duruma getirdik.

Sonraki problemimiz çapraz modülleri merkezi bir noktaya çekmektir. Çapraz modüllerin her birini bir poliçe haline getirebiliriz. Policeden kasıt çalıştığı an ki ortam bilgilerini toplayan ve tek bir atomik iş yapan uygulama bloğu sınıflarıdır. Örneğim hata yakalama işlemi için uygulamada bulunan tüm fonksiyonlarda çalışacak bir ExceptionHandlingManager yazabilirsiniz. Bu durumda her bir fonksiyonun içinde ExceptionHandlingManager sınıfınıza en az bir defa çağrıda bulunmanız gerkecektir. Yada hata kayalama işlemini yapan tek bir police yapar ve hata yakalama gereksimi olan fonksiyonların bu police ile çalışmasını sağlayabilirsiniz. Böylece hata yakalama sisteminizde bir değişiklik yapmak için sadece poliçenizi değiştirmeniz yeterli olacaktır. Ayrıca fonksiyonların içinde ki gereksiz kod bloklarından kurtulmuş olacaksınız.

[cs]

public  class  ExceptionHandlingCallHandler  : CallHandler 
 {
 public  override  ReturnMessage  Invoke (IMethodCallMessage  message, object  realObject)
 {
     var  result = NextHandler .Invoke (message, realObject);
     if  (result.Exception  != null )
     {
         var  errorMessage = string .Format ("{0}{1}{2}" , DateTime .Now , result.Exception .Message ,
                                             result.Exception .StackTrace );
         Console .WriteLine (errorMessage, ConsoleColor.Red, true );
         Trace .TraceError (errorMessage);
     }
     return  result;
 }
 }

Yukarıda örnek bir hata yakalama policesini görmektesiniz. Police sorumluluk zincirinde yer alan kendsinden sonra ki policeleri çağırmakta eğer bu zincir üzerinde bir hata oluşursa bunu ekrana basmaktadır. Şimdi nesnemizin nasıl kullanıldığına bakalım:

[cs]

PITest2  instance = PolicyInjector .CreateObject ();
 var  order = new  Order  {AccountId  = 10, Unit  = 3, UnitAmount  = 5};
 var  user = new  User  {UserName  = "saleperson1" , Roles  = new [] {"Sales" }};
 instance.SaveOrderToDB (order,user);

Kullanımda ki tek fark nesneyi doğrudan oluşturmak yerine PolicyInjector Factory sınıfı ile oluşturmamızdır.

Şimdi PolicyInjector sınıfını inceleyelim. Nesnelere poliçeleri enjekte edebilmek için nesnelerin tek bir merkezden üretilmesi gerekmektedir. PolicyInjector bizim yerimize sınıflarımızın proxy'lerini oluşturan, metot çağrılarında ki sorumluluk zincirini oluşturan ve metot çağrısı sırasında poliçeleri enjecte eden Factory sınıfımızdır. Yaptığı iş çok sihirlide olsa kendisi oldukça basit ve 150 satırlık bir sınıfdır.

[cs]

public  class  PolicyInjector  : RealProxy  where  TObject  : new ()
 {
     private  readonly  TObject  _realObject ;
     public  PolicyInjector (TObject  realObject)
         : base (typeof (TObject ))
     {
         _realObject  = realObject;
     }
     public  static  TObject  CreateObject ()
     {
         var  reelInstance = new  TObject ();
         return  Wrap (reelInstance);
     }
     public  static  TObject  Wrap (TObject  reelInstance)
     {
         var  proxyObject = new  PolicyInjector (reelInstance);
         var  transparentProxyObject = (TObject )proxyObject.GetTransparentProxy ();
         return  transparentProxyObject;
     }
     public  override  IMessage  Invoke (IMessage  msg)
     {
         var  message = (IMethodCallMessage )msg;
         var  chain = CreateChainOfResponsibility (message);
         var  result = chain.Invoke (message, _realObject );
         return  result;
     }
     private  CallHandler  CreateChainOfResponsibility (IMethodMessage  message)
     {
         var  chain = new  List ();
         AddToChainFromInterfaceHandlers (message, chain);
         AddToChainFromClassHandlers (message, chain);
         // add method call 
         chain.Add (new  PolicyPear  { CallHandler  = new  MethodCallHandler () });
         // organize chain 
         for  (var  i  = 1; i < chain.Count; i++)
         {
             chain[i-1].CallHandler.SetChain(next:chain[ i ].CallHandler, 
                 handlerAttribute: chain[i - 1].HandlerAttribute );
         }
         return  chain[0].CallHandler ;
     }
     private  void  AddToChainFromInterfaceHandlers (IMethodMessage  message, ICollection  chain)
     {
         var  realType = _realObject .GetType ();
         foreach  (var attributes  in 
             realType.GetInterfaces ()
             .SelectMany (@interface => (
                 from  method in  realType.GetInterfaceMap (@interface).InterfaceMethods 
                 where  method.Name  == message.MethodBase .Name 
                 select  method.GetCustomAttributes (typeof (CallHandlerAttribute ), true ))))
         {
              attributes.Cast ()
                 .OrderBy (c => c.CallHandlerType .MetadataToken ).ToList ()
                 .ForEach (attribute =>
                     chain.Add (new  PolicyPear 
                     {
                         CallHandler=Activator.CreateInstance(attribute.CallHandlerType) as CallHandler,
                         HandlerAttribute  = attribute
                     })
                 );
         }
     }
     private  void  AddToChainFromClassHandlers (IMethodMessage  message, ICollection  chain)
     {
         var  realType = _realObject .GetType ();
         var  attributes =
             realType.GetMethod (message.MethodName ).GetCustomAttributes (typeof (CallHandlerAttribute ), true );
         if  ((attributes == null ) || (attributes.Length  <= 0)) return ;
         bool  needReserve = chain.Count  == 0;
         var  list = attributes.Cast ()
             .ToList ();
         if  (needReserve) list.Reverse ();
         list.ForEach (attribute => chain.Add (new  PolicyPear 
         {
             CallHandler  = Activator .CreateInstance (attribute.CallHandlerType ) as  CallHandler ,
             HandlerAttribute  = attribute
         }));
     }
     private  sealed  class  MethodCallHandler  : CallHandler 
     {
         public  override  ReturnMessage  Invoke (IMethodCallMessage  message, object  realObject)
         {
             var  methodRetval = message.MethodBase .Invoke (realObject, message.InArgs );
             return  new  ReturnMessage (methodRetval, null , 0, message.LogicalCallContext , message);
         }
     }
     private  sealed  class  PolicyPear 
     {
         public  CallHandler  CallHandler  { get ; set ; }
         public  CallHandlerAttribute  HandlerAttribute  { get ; set ; }
     }
 }
 public  abstract  class  CallHandler 
 {
     internal  void  SetChain (CallHandler  next, CallHandlerAttribute  handlerAttribute)
     {
         NextHandler  = next;
         CallHandlerAttribute  = handlerAttribute;
     }
     public  CallHandler  NextHandler  { get ; private  set ; }
     protected  CallHandlerAttribute  CallHandlerAttribute  { get ; private  set ; }
     public  abstract  ReturnMessage  Invoke (IMethodCallMessage  message, object  realObject);
 }
 [AttributeUsage (AttributeTargets.Method  )]
 public  class  CallHandlerAttribute  : Attribute 
 {
     public  CallHandlerAttribute (Type  callHandlerType)
     {
         if  (callHandlerType !=  null  && !typeof (CallHandler ).IsAssignableFrom (callHandlerType))
             throw  new  TypeLoadException (string .Format ("{0} is not assingable from {1}" , callHandlerType.Name ,
                                                         typeof (CallHandler ).Name ));
         CallHandlerType  = callHandlerType;
     }
     public  Type  CallHandlerType  { get ; private  set ; }
 }

Tüm bu sihirli işleri yapan kodların hepsi bu kadar!

PolicyInjector yeni bir nesne oluştururken nesnenin proxy örneğini geri döndermektedir. Proxy örneği üzerinde yapılan tüm metot çağrıları PolicyInjector sınıfımızın Invoke metoduna düşmektedir. Invoke metotu içinde önce metotun poliçelerine göre bir sorumluluk zinciri oluşturulmaktadır. Sorumluluk zinciri oluşturulurken önce interface üzerinde ki metot tamına ait poliçeler zincire eklenmektedir. Daha sonra metota ait poliçelerde zincire eklenmektedir. Zincir oluşturulurken her bir poliçeye bir sonra ki poliçe atanmaktadır. Zincir oluşturulduktan sonra zincirin ilk elemanı çağrılmaktadır. Zincir üzerinde ki her bir police kendi işlevini yapmakta ve bir sonraki policeyi çağırmaktadır. Sorumluluk zincir üzerinde ilerlemekte ve en son metodu çağırmaktadır. Metot çalıştıktan sonra sorumluluk zincir üzerinde sondan başa doğru hareket etmekte ve PolicyInjector Invoke metotu içinde ki result değişkenine sonuç atanmaktadır. Invoke fonksiyonu da bu sonucu metodu çağıran koda dönmektedir.

Böylelikle 150 satırda projenizde ki karmaşayı ortadan kaldıraçak bir PolicyInjector sınıfına ulaşmış olduk. Policy Injection metotu ile çapraz modülleri policeler haline getirip çok ciddi bir kod temizliği ve yönetim kolaylığı sağlayabilirsiniz.

Yardımlarından dolayı Engin Özer Beye teşekkürler...

10 Haziran 2010 Perşembe

WCF Ria 3 - Veri Erisimi

Tüm iş uygulamaları veri ile ilgilenirler. Şimdi Ria Servis uygulamasında veri işlemlerinin nasıl yapıldığını inceleyeceğiz. Yeni bir Ria uygulaması açalım. Ben bu uygulamada Linq to SQL kullanacağım. Ria servis Entity Framework veri modeli, xml dosyalar, web servisleri gibi veri kaynakları ile sorunsuz çalışmaktadır. Veri yapımızı oluşturalım.

Sorumuz şu silverlight istemcisi üzerinden veriye nasıl ulaşacağız? Geliştiriciler veri iletişiminde çok katmanlı mimari yapısına güvenmektedir. WCF ve ADO.NET Data servislerini kullanan NET Ria servisleri ile çok katmanlı uygulamalar kolaylıkla kurulabilmektedir.

Yukarıda Ria Servis uygulama mimarisi görülmektedir. Burada ön yüz, uygulama mantığı ve veriyi saklayacak çeşitli kaynaklar bulunmaktadır. En önemli kazançlardan bir tanesi veri kaynağı olarak tercihlerinizi ön yüzde değişiklik yapmadan değiştirebilmenizdir.

Ria Servismizi oluşturalım. Sunucu projemize sağ tıklayalım ve yeni bir “Domain Service” sınıfı oluşturalım.

Ekledikten sonra bizi bir sihirbaz karşılayacak ve bizden veri kaynağını seçmemiz bekleyecek.

AdventureWorksDomainService.cs sınıfında sizin verilerine ulaşmak kullanabileceğiniz tüm CRUD metot taslakları var. Tabiki bu metotları kendi etki alanıza (Domain) göre özelleştirebilirsiniz. Şimdi gidip GetEmpployess() metodunu biraz özelleştirelim.

[cs]

public IQueryable GetEmployees()
        {
            return this.DataContext.Employees
                .Where(r=>r.SalariedFlag == true)
                .OrderBy( r=>r.Title)
                .ThenBy(r=>r.BirthDate);
        }

İstemci tarafa geçelim. İstemcinin sunucu üzerinde ki bu servise erişebilmesi için uygulamayı built edelim. Sunucu projesi istemci projeye bağlanmıştır çünkü servisi oluştururken seçtiğimiz “Enable client access” özelliği ASP.NET erişimine açık bir servis oluşturur ve bu servise ait Proxy sınıfını istemci tarafında üretir. 

Şimdi önyüzü hazırlayalım. Data -> Show Data Sources menüsü ile veri kaynakları panelini açalım.  Burada iki veri kaynağı göreceksiniz. Birisini Ria servis tarafından standart olarak sunulan ve kullanıcı ekleme işlemi için kullanılan UserRegistrationContext. Diğer veri kaynağı bir önceki adımda oluşturduğumuz AdventureWorksDomainContext. AdventureWorksDomainContext Employee tablosunu göreceksiniz. Employee tablosuna tıkladığınızda bu tablonun GetEmployeesQuery fonksiyonu ile üretildiğini görüyoruz. Eğer Employee tablosunu üreten birden fazla fonksiyon olsa idi bu menüden seçebilecektik. Aynı menüde tablonun form üzerine hangi bileşen ile gösterileceğini seçebiliyoruz. Tabloları DataGrid, ListBox veya Details olarak gösterebiliyoruz. Aynı menüden veri form üzerinden göstermek için kullanılabilecek yeni bir bileşende seçebiliriz.

Employee tablosu için DataGrid gösterimini seçelim daha sonra Employee tablosunu form üzerine sürükleyelim. Visual Studio bizim için Employee tablosunu ait bir grid ve gridin veri kaynağını oluşturacaktır.

Oluşan kodları inceleyelim.

<riaControls:DomainDataSource AutoLoad="True" Height="0" Width="0"   
    Name="employeeDomainDataSource" QueryName="GetEmployeesQuery" >
     <riaControls:DomainDataSource.DomainContext>
           <my:AdventureWorksDomainContext />
      </riaControls:DomainDataSource.DomainContext>
<sdk:DataGrid AutoGenerateColumns="False" Height="200"
    ItemsSource="{Binding ElementName=employeeDomainDataSource, Path=Data}"
    Name="employeeDataGrid"
    RowDetailsVisibilityMode="VisibleWhenSelected" Width="400">

Veri kaynağı olarak riaControls altında ki DomainDataSource nesnesinin kullanıldığını görüyoruz. Veri erişim fonksiyonu olarak da AdventureWorksDomainContext sınıfı altında ki GetEmployessQuery metodunu kullandığını görüyoruz. AdventureWorksDomainContext sınıfı nerede?

Web projesi içinde yazdığımız Domain Service sınıfımız Proxy nesnesi Silverlight uygulamamızın Generated_Code dizini içine kod üretimi ile yerleştirilmiştir. Uygulamayı deneyelim şimdi.

Çok fazla kayıtın gride dolduğunu görüyoruz. Sayfalama özelliği eklemek için forma bir DataPager ekleyelim. Eklediğimiz DataPager veri kaynağını ayarlamak için Data Sources alanında ki Employee tablosunu sürükleyip DataPager bileşenine bırakabiliriz.

<riaControls:DomainDataSource AutoLoad="True"
    Name="employeeDomainDataSource"
    QueryName="GetEmployeesQuery" LoadSize="20">
    <riaControls:DomainDataSource.DomainContext>
                        <my:AdventureWorksDomainContext />
                    </riaControls:DomainDataSource.DomainContext>
                </riaControls:DomainDataSource>
<sdk:DataPager Name="dataPager1" PageSize="10"
    Source="{Binding ElementName=employeeDomainDataSource, Path=Data}" />

Böylelikle her sayfada on kayıt gösterilecek ve sunucudan her seferinde yirmi kayıt çekilecektir. Bir seferde yirmi kayıt yüklenirken sadece on tanesi gösterilecektir. Dolayısı ile ikinci sayfaya geçince istemci veri yüklemek için sunucuya gitmeyecektir. Üçüncü sayfaya geçerken sonra ki yirmi kayıt istemciye yüklenecektir. Böylece tüm kayıtlar yerine gereken kayıtlar istemciye taşınacaktır.

Grid üzerine Title kolonuna göre bir gruplama ekleyelim.

<riaControls:DomainDataSource.GroupDescriptors>
     <riaControls:GroupDescriptor PropertyPath="Title"/>
</riaControls:DomainDataSource.GroupDescriptors>

Filtreleme ekleyelim. Bunun için filtre bilgisinin girileceği bir TextBox   ihtiyacımız var.

  <StackPanel Orientation="Horizontal" >
    <TextBlock Text="National ID: "></TextBlock>
    <TextBox x:Name="txtNationalID" Width="150" Height="25"></TextBox>
  </StackPanel>

Veri kaynağımıza nasıl filtre yapması gerektiğini söyleyelim.

<riaControls:DomainDataSource.FilterDescriptors >
   <riaControls:FilterDescriptor Operator="StartsWith"
    PropertyPath="NationalIDNumber"
    Value="{Binding ElementName=txtNationalID, Path=Text}"/>
</riaControls:DomainDataSource.FilterDescriptors>

Şimdi bu işlemlerin nasıl çalıştığını inceleyelim. AdventureWorksDomainService.GetEmployees fonksiyonuna bir break point ekleyelim. Projeyi çalıştırıp yeni bir filtre değeri girelim.  Inttelli Trace penceresinde yapılan tüm işlemleri görebilirsiniz.

Yukarda görüldüğü gibi girdiğimiz filtre değeri girildiği anda Ria Domain Data Source nesnesi ilk olarak web servis çağrısı yapmaktadır.

Bu web çağrısından sonra istenen veri kümesinde yer alan toplam kayıt sayısı bulunmuş. Toplam kayıt sayısı bulunurken bizim ön yüzden girdiğimiz filtre değeri ve AdventureWorksDomainService.GetEmployees fonksiyonu içinde belirttiğimiz Where koşul ve Order By koşullarınında çalışan Query içine eklendiğini görüyoruz. Böylelikle Query’inler dinamik olarak orta seviyede hem web hem ön yüz tarafının ihtiyaçlarına göre üretildiğini görüyoruz.

Son olarak kullanılacak verinin çekildiğini görüyoruz. En son olarak da üretilen verinin istemciye gönderildiğini görüyoruz.

Tüm bu işlemleri biz sadece Ria araçlarını kullanarak oluşturduk. Ria Domain Data Source gerekli noktalarda web tarafına giderek gereken Query’leri çalıştırmadadır. Böylece biz sadece işin kurallarına eğilip gereksiz kodlamalardan uzak durmuş olmaktayız.

Ria servisleri ile veri erişimi erişimini ve veri gösterimini örneklemiş olduk. Şimdi Ria servisleri ile veri ekleme/değiştirme ve silme olaylarını inceleyelim. Veri güncelleme işlemi içinde Ria servis araçlarından faydalanacağız.  Formumuza Silverlight Toolkit içinde yer alan bir DataForm ekleyelim.

<sdk:DataGrid AutoGenerateColumns="True" Grid.Column="0"
    ItemsSource="{Binding ElementName=employeeDomainDataSource, Path=Data}"
    Name="employeeDataGrid"   
    MinHeight="300" MaxHeight="300"/>
<sdk:DataPager Name="dataPager1" PageSize="10" Grid.Column="0"
    Source="{Binding ElementName=employeeDomainDataSource, Path=Data}"  />
<dataForm:DataForm Grid.Column="1"
    CommandButtonsVisibility="All"
    ItemsSource="{Binding ElementName=employeeDomainDataSource, Path=DataView}"/>

DataForm bileşeni ile Ria Domain Data Context üzerinde ki kayıtları manipüle edebilirsiniz. Değişen kayıtlar için yukarıda görüldüğü gibi sol üst köşede bir yıldız belirir. Bu yıldızın sizin istemci üzerinde yaptığınız değişiklikler henüz sunucuya gönderilmediğini gösterir. Form üzerine bir buton ekleyip değişiklikleri sunucuya Ria Domain Data Context üzerinde ki değişiklileri sunucuya gönderelim.

[cs]

private void btnSave_Click(object sender, System.Windows.RoutedEventArgs e)
{
 dataForm1.CommitEdit();
 employeeDomainDataSource.SubmitChanges();
}

Böylece Silverlight tarafında bulunan Ria Domain Data Context sınıfı kendi üzerinde tüm değişiklikleri Web tarafında ki Ria Domain Servise iletmekte ve daha sonra Domain Servisin değişikleri veri tabanına yazılmasını sağlamaktadır. Bizim ön yüzde yaptığımız tüm değişiklikler SubmitChanges() metodu çağrılana kadar ön yüzde beklemektedir. Domain Data Context SubmitCahnges() metotunun çağrılması ile ilgili Domain Servis içinde ki gerekli Insert, Update ve Delete metotları tek tek çağırılacak ve tüm değişiklik kümesi sunucuya iletildikten sonra Domain Servis üzerinde Submit metodu çağrılacaktır. Web tarafında tüm işlemleri bir transaction içine alalım ve değişiklikleri bu şekilde geçmesini sağlayalım.

[cs]

public class AdventureWorksDomainService : LinqToSqlDomainService
{
 public override bool Submit(System.ServiceModel.DomainServices.Server.ChangeSet changeSet) 
 {
  try
  { 
   using (TransactionScope scope = 
    new TransactionScope(TransactionScopeOption.RequiresNew))
   {  
    base.Submit(changeSet);  
    scope.Complete();  
    return true; 
   } 
  }catch { } 
  return false;
        }
}

Şimdide CRUD işlemlerinin nasıl yapıldığını inceleyelim.

Yukarıda da görüldüğü gibi Ria Domain Servis bizim için gerekli Insert/Update/Delete Query’lerini oluşturdu ve kayıt işlemlerimizi yaptı.

Tüm süreci özetleyelim. Veri erişimimizi sağlayacak yöntemi seçtik. Bu uygulamada birçok alternatif arasından Linq To SQL uyguladık. Daha sonra Domain Servisini oluşturduk ve standart veri erişim metotlarımız geldi. Domain Servis sınıfımız Silverlight istemci uygulamamızdan çağırabileceğimiz Domain Context sınıfımız Ria tarafından otomatik olarak oluşturuldu. Ria bileşenlerinin gerekli veri sorgulamaları otomatik olarak yapabildiklerini gördük. Tüm bu işlemleride Ria Domain Servis gerekli Query’leri bizim yerimize oluşturdu.

9 Haziran 2010 Çarşamba

WCF Ria 2- Ön yüz ve Şablonlar

Bir iş uygulaması profesyonel duruşa sahip olmalıdır. Fakat bu görünümü verecek bir tasarımcıya her zaman sahip olamayabilirsiniz. Ria uygulama şablonu temiz anlaşılır uygulama yapısı ve kolaylıkla değiştirilebilen güzel bir ön yüz ile gelmektedir.

Öncelikle Silverlight Ria uygulaması sayfalar arasında ileri geri gezinmeye izin veren bir konumlanma desteğine sahiptir.

Uygulama adres satının da ki url bilgisi kopyala-yapıştır yapılabilmektedir.Böylelikle url’lerinizi paylaşabilirsiniz.


Silverlight Ria uygulaması uygulama içinde oluşan hataları çok kalay bir şekilde yönetmekte ve göze hoş gelen bir şekilde göstermektedir. Aşağıda yanlış link hatası gösterilmiştir.

Şimdi uygulamanın Views dizini altılana yeni bir Silverlight sayfası ekleyelim.

Açılan sayfanı içine bir şeyler ekleyelim. Şimdi uygulamanın menüsüne bu sayfa ait bir link ekleyelim. Link eklemek için MainPage.xmal içine mevcut linkler ile aynı formatta yeni bir bağlantı ekleyelim.

<Rectangle Style="{StaticResource DividerStyle}"/>                        
    <HyperlinkButton Style="{StaticResource LinkStyle}"                  
        NavigateUri="/Page1"                  
        TargetName="ContentFrame"                  
        Content="sayfa 1"/>

Uygulamayı bize özel yapmak için uygulama adını değiştirelim. Bunun için Assets\Resources atında ki ApplicationStrings kaynak dosyasında “Application Name” olan uygulama adını istediğimiz gibi değiştirelim.

Şimdide uygulamanın renk ayarlarını kendi istediğimiz şekilde değiştirelim. Assets dizininde ki Styles.xmal dosyasını açalım. Tüm uygulama önyüz sitilleri burada ve bu ön yüz sitilleri yazılımcılar tarafından kolaylıkla değiştirilebilirler.

Uygulamayı deneyelim şimdi:

Stlye.xmal ile tüm  uygulama görünümünü değiştirebilirsiniz. Birden çok resource dosyası ile farklı görünüşler elde edebilirsiniz. Ne yazık ki silverlight uygulamamızda dinamik olarak resourceleri değiştiremiyoruz. Uygulama görünümünü tasarım anında App.xmal içinden değiştirmemiz gerekiyor.

<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary   Source="Assets/SeeingSound/Styles.xaml"/>
</ResourceDictionary.MergedDictionaries>

http://expression.microsoft.com/ adresinden Silverlight Navigation Application veya Silverlight Ria Application alanında yer alan şablonları kendi uygulamamızda kullanabiliriz.

8 Mayıs 2010 Cumartesi

WCF Ria 1 - RIA Nedir?

Çok katmanlı uygulama mimarisinin en önemli geliştirme sorunu uygulama mantığının web servis üzerinde ki orta seviye ile ön yüz arasında ki koordinasyondur. Yazılım geliştiriciler uygulama mantığının orta seviyede olması gerektiğinin farkındalar. Fakat hem orta seviyede hem ön yüzde uygulama mantığını yönetmek zorunda kalmaktadırlar. Çözüm önerisi uygulama mantığının tamamen ön yüzden kurtarmak ve orta seviyeye taşımaktır. Sunulan çözümün işlemesi için orta seviyede ön yüzde yönetilen uygulama mantığının taşına bilmesi gerekmektedir.

Microsoft’un WCF ürün ailesi içinde sunduğu WCF Rich Internet Application (RIA) çok katmanlı uygulama geliştirme için sunulan bir uygulama şablonudur. RIA servis problemi aşmak için alt yapı bileşenleri araçları ve uygulama lojiğini taşıyacak olan servisler sunmaktadır. RIA istemci uygulamasının uygulama mantığını çoklama ihtiyacını duymamaktadır. Çünkü Ria istemcileri orta seviyede sunulan uygulama mantığını temel alarak güncelleyebilme özellikleri vardır.

Aşağıda ki şekilde çok katmanlı bir uygulamanın basit bir gösterimi bulunmaktadır.

Ria servisleri kapalı kutu ile gösterilen ön yüz ve orta seviyede bulunan uygulama mantığı ile ilgilenirler. Visual Stdio içine eklenen Ria servis araçları aynı Solution içinde yer alan istemci ve sunucu uygulamalarının birbirlerine bağlar ve orta seviyede yazılan ortak kodları kod üretimi ile istemciye taşınmasını sağlar.  Aynı zamanda alt yapı bileşenleri ile orta seviyede yazılan uygulama mantığının çalışan ön yüzün ihtiyaçlarına göre şekil değiştirerek tekrar kullanılmasını sağlamaktadır. Orta seviye servisleri kodlama aşamasında özelleştirilecek olan uygulama için ortak ve temel yetkilendirme, kullanıcı ayarları ve yönetim gibi temel iş kurallarını taşırlar.

Ria Servis Şablonun da Neler Var?

Yeni bir Ria uygulaması oluşturarak incelemeye başlayalım. Silverlight sekmesi içinde yer alan Ria servis bir uygulama şablonunu ile yeni bir proje oluşturulalım.

Oluşan proje yapısı şu şekilde olacaktır.

Ria Bağlantısı

Görüldüğü gibi istemci Silverlight uygulamasında bir web servis referansı yok. Bunun yerine gizlenmiş olan Generated_Code klasörü için istemcinin ihtiyaç duyacağı tüm sunucu bileşenlerinin kodlarının üretildiğini görüyoruz.

İstemi uygulamanın özelliklerine baktığımızda ise aradığımız web servis referansını görebiliyoruz. Ria uygulaması çalışılan proje içinde ki tüm Ria servislerini bir liste üzerinde bize sunmakta ve kullanmak istediğimiz servisi seçmemizi beklemektedir.

Ria uygulaması aşağıda ki şekilde görüldüğü gibi bir orta seviye web uygulaması ve bir istemci uygulaması ve aralarında Ria servis araçları ile oluşturulan bir Ria bağlantısından oluşmaktadır.

Burada ki Ria bağlantısı Genrated_Code dizini içinde Visual Studio içinde eklenen araçlar ile sunucu tarafında oluşturulan veri modeli, veri modeli özellik sınıfları Resource dosyaları ve uygulama mantığını taşıyan servislerin istemci sınıflarını istemci uygulamada oluşturmaktadır.

Hazır Servisler

Şablon tarafından oluşturulan kodlara tekrar baktığımızda bizim için hazır bazı ön yüzlerin ve sınıfların oluşturulmuş olduğunu görüyoruz. Bunlar bir uygulamanın ana sayfası, hata gösterim sayfası ve kullanıcı giriş ekranlarıdır. Bununla birlikte web tarafında yetkilendirme ve kullanıcı giriş servislerinin hazır olduğunu görüyoruz.

İlk Görüş

Şablon üzerinde hiçbir kodlama yapmadan çalıştıralım. Bize hazır olarak sunulan servis ve ön yüzleri deneyelim.

Gördüğüz gibi hazır bir kullanıcı yönetimi ile birlikte geliyor.  Ria uygulama şablonu ASP.NET Membership Provider’ı kullanmaktadır yetkilendirme sistemi olarak. Uygulama içinde ilk kullanıcı oluşturduğunuzda Ria Web projesi altında ki App_Data dizinine yeni bir ASPNETDB SQL Server dosyası oluşturmakta ve ASP.NET Membership Provider burada ki veri tabanı dosyası ile kullanıcı yönetimini yapmaktadır.

Böylelikle ilk Ria projemizi oluşturmuş ve kullanıcı girişi yapmış bulunmaktayız. Bir sonraki makalede veri erişimini inceleyeceğiz.