WCF etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
WCF etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

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.

8 Haziran 2008 Pazar

WCF Unity

Smart client software factory – web client software factory gibi bir çok popüler uygulama mimarisinin kendi object builder nesnesi vardır. Fakat wcf için böyle bir uygulama mimarisi bulunmamaktadır. Buda bir eskikliktir.

Bu projede WCF için unity container nesnesinin nasıl kullanılacağını gösteriyorum. Unity ile policy injection application bloğu bereber kullandım.Böylelikle WCF servisleri için hem dependency injection hemde policy injection yapabilme imkanı doğdu. Tip tanımlarının ayrı config dosyalarında olduğunu varsayarak hareket ettim. Böylelikle hem config dosyasındakarmaşadan kaçındım hemde projeyi uygulama bağımsız olarak kullanma imkanı oluştu.

WCF için unity containerın kullanabilecek en iyi yer WCF servisin life time kontrolünü sağlayan IInstanseProvider arayüzüdür. Öncelikle dependency injection sağlayacak olan yeni bir IınstanceProvider yazdım.

[cs]

/// 
/// init dependency container
/// 
/// Dependency config file /// Dependency conainer name private void InitContainer(string dependencyFile, string containerName) {  string currentContainerKey = GetDependencyKey(dependencyFile, containerName);  if (!containers.ContainsKey(currentContainerKey)) {   lock (syncRoot) {    if (!containers.ContainsKey(currentContainerKey)) {     containers[currentContainerKey] = currentContainer = new UnityContainer();     ExeConfigurationFileMap map = new ExeConfigurationFileMap();     map.ExeConfigFilename = dependencyFile;     System.Configuration.Configuration config      = ConfigurationManager.OpenMappedExeConfiguration(       map, ConfigurationUserLevel.None);      UnityConfigurationSection section      = (UnityConfigurationSection)config.GetSection("unity");     section.Containers[containerName].Configure(currentContainer);     currentContainer.AddNewExtension();    }   }  } else {   currentContainer = containers[currentContainerKey];  } }

DependencyInjectionInstanceProvider iki tane parametreye ihtiyaç duymaktadır: dependencyFile tip tanımlamalarımızın bulunduğu config dosyasıdır. containerName config dosyası içinde tanımlı container adıdır. Her bir container sadece bir kez oluşturul ve saklanır. Oluşturulan container nesnelerine son olarak PolicyInjectionExtansion ile Policy injection destekler hale getirilir. PolicyInjectionExtansion sadece EnterpriseLibrary.PolicyInjection.ObjectBuilder altında ki strategy ve policy sınıflarını register eder. Son olarak Dependency injection instance provider'ı servislere uygulayacak yeni bir servis behavior yazmaya.

[cs]

public class DependencyInjectionServiceBehavior : Attribute, IServiceBehavior {
 public void ApplyDispatchBehavior(ServiceDescription serviceDescription, 
  ServiceHostBase serviceHostBase) 
 {
  foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers) {
   ChannelDispatcher cd = cdb as ChannelDispatcher;
   if (cd != null) {
    foreach (EndpointDispatcher ed in cd.Endpoints) {
     ed.DispatchRuntime.InstanceProvider =
      new DependencyInjectionInstanceProvider(
       serviceDescription.ServiceType, 
       configFile, 
       containerName);
   }
  }
 }
}

Evet artık WCF ile unity dependency container bir arada kullanılabilir.

[cs]

static void Main(string[] args) {
 ServiceHost hostA = null, hostB = null;
  try {
   hostA = new ServiceHost(typeof(ServiceA));
   hostB = new ServiceHost(typeof(ServiceB));
   hostA.Open();
   hostB.Open();
 

[ServiceBehavior]
public class ServiceA : IServiceAContract {
 public ServiceA() { }

 private IServiceBContract serviceB;
 private IServiceC serviceC;

 [InjectionConstructor]
 public ServiceA(IServiceBContract serviceB) {
  Console.WriteLine("Build up Service A");
  this.serviceB = serviceB;
  Console.WriteLine("Service A constructor calling to serviceB.OperationD");
  this.serviceB.OperationD("test1", "test2");
 }

 [Dependency]
 public IServiceC ServiceC {
  set {
   serviceC = value;
   Console.WriteLine("IServiceC injected to ServiceA parameter ");
  }
 }


public class ServiceB : IServiceBContract {

 public ServiceB() { }
 private IServiceC serviceC;

 [InjectionConstructor]
 public ServiceB(IServiceC serviceC) {
  Console.WriteLine("Build up Service B ");
  this.serviceC = serviceC;
  Console.WriteLine("Service B constructor calling to serviceC.OperationG");
  this.serviceC.OperationG("test");
  this.serviceC.OperationG("test");
 }

29 Eylül 2007 Cumartesi

WSE 3.0 Security

Web servisinize ulaşan bir mesajın acaba bir saldırımı yoksa gerçekten bir istemci mesajı mı olduğunu nasıl kontrol edersiniz? Peki, mesajın güvenliğini sağladığınızı varsayalım acaba istemci kullanıcının yetkisi dışında bir veriye talepte bulunmadığından emin olabilir misiniz?

WSE 3.0 Policy Framework özelliklerini inceliyoruz.

WSE Policy Framework

Politikalar çalışma zamanı kavramlarıdır. Görevleri istemci veya sunucuyu politikada yer alan bildirgelere göre mesajları zorlamaktır. Bildirgeler mesajları süzen filtrelerdir. Her bildirgede dört adet süzgeç bulunmaktadır. Bu süzgeçler her biri farklı mesaj işleme adımlarında çalıştırılmaktadır.

Geliştirici kendi güvenlik prensiplerine göre bu süzgeçleri oluşturmaktadır. SOAP Filter (süzgeç) nesneleri içinde mesajın bileşenlerine ekleme veya eklenen özellikleri kontrol ederek güvenlik prensiplerimizi gerçekleştirmekteyiz. Bu süzgeçler içerisinde mesajın hepsini şifrelemekte mümkün sertifika bilgilerini değiştirmekte mümkündür. SOAP Filter nesneleri bizim asıl olarak güvenlik kodlarımızı yazacağımız yerlerdir. Hem süzgeç hem bildiri için WSE içinde çok çeşitli kullanmaya hazır üst sınıflar mevcuttur. Biz kendi politikamıza uygun üst sınıfları kullanmalıyız.

Şekilde sistemin tam bir döngüsünü görmekteyiz. Örnek politikamıza iki adet bildirge eklenmiştir. Her bildirgemize ait dört tane SOAP Filter görülmektedir.

İlk olarak istemci bir web servis isteğinde bulunur. Daha sonra bu istek politikamıza ait bildirgelerin SOAP Filter Client Output nesnelerinden geçirilecektir. Bu kısımda mesaja doğrulama (authentication) için ihtiyacımız olacak token ve sunucu tarafında ki süzgeçte kullanılacak diğer güvenlik (security) nesneleri eklenmektedir. Daha sonra mesajımız network ile sunucuya ulaşmaktadır.

WSE sunucuya gelen SOAP mesajlarını doğrulamak (authentication) için Win32’ye ait LogonUser fonksiyonunu çağırmaktadır. LogonUser fonksiyonu varsayılan olarak Windows hesabına ait bir doğrulama (Windows Authentication) denemekte ve mesaja eklemektedir. Eğer doğrulama başarılı olursa oluşturulan Principal nesnesi UsernameToken’nın Principal alanına atanacaktır. Eğer WSE’nin UsernameToken nesnesini Windows ile doğrulamasını istemiyorsanız UsernameTokenManager sınıfın türetmeli ve AuthenticateToken metodunu tekrar yazmalısınız.

Kullanıcı doğrulama adımı geçildikten sonra mesaj ilk olarak politika bildirgelerimizin SOAP Filter Service Input nesnelerine gelmektedir. Eğer mesaj bu adımda ki süzgeçten geçebilirse web servis kodlarımız çalışacaktır. Daha sonra servis geri dönüşü mesajını oluşturacaktır. Oluşturulan geri dönüş (response) mesajı SOAP Filter Servis Output nesnelerinden geçecektir.

En son olarak tekrar istemci tarafa ulaşan geri dönüş mesajı SOAP Filter Client Input süzgeçlerinden geçecek ve web servis çağrımız yerde geri dönüşte bulunarak çağrımız son bulacaktır.

Bir örmek ile devam edelim. Biz mesajımıza Client Output süzgeci içerisinde bir UsernameToken ve aynı token ile mesajı imzasını ekleyeceğiz ve Service Input süzgecinden gelen mesajın önce token ile belirtilen kullanıcı adı ve şifresinin doğru olduğunu daha sonrada mesajın imzasının doğru olduğunu denetleyip mesajın doğruluğundan emin olacağız. Bu güvenlik prensiplerimizi web servisimize ve istemcimize ekleyeceğimiz politika ile uygulayacağız.

Öncelikle istemci tarafında SOAP Client Output süzgeci ile sunucu tarafta doğrulayacağımız token ve mesaj imzasını ekleyelim. Süzgeç için SOAP mesajlarında güvenlik, mesaj imzası ve şifrelemeyi destekleyen SendSecurityFilter sınıfı türetmeliyiz. Süzgecimizi kullanacak bildirge nesnemizi aynı şekilde güvenlik özelliklerine sahip SecurityPolicyAssertion sınıfından türetmeliyiz..

[cs]

Internal class UsernameClientAssertion : SecurityPolicyAssertion {
    private string username;
    private string password;
 
    public UsernameClientAssertion(string username, string password) {
        this.username = username;
        this.password = password;
    }
 
    public override SoapFilter CreateClientOutputFilter(FilterCreationContext context) {
        return new ClientOutputFilter(this, context);
    }
……
class ClientOutputFilter : SendSecurityFilter {
    UsernameClientAssertion parentAssertion;
    FilterCreationContext filterContext;
 
    public ClientOutputFilter(UsernameClientAssertion parentAssertion, FilterCreationContext filterContext)
        : base(parentAssertion.ServiceActor, false, parentAssertion.ClientActor) {
        this.parentAssertion = parentAssertion;
        this.filterContext = filterContext;
    }
 
    public override void SecureMessage(SoapEnvelope envelope, Security security) {
        UsernameToken userToken = new UsernameToken(
            parentAssertion.username,
            parentAssertion.password,
            PasswordOption.SendHashed);
 
        security.Tokens.Add(userToken);
 
        MessageSignature sig = new MessageSignature(userToken);
        security.Elements.Add(sig);
    }
}

Mesajımıza süzgeç içerisinde token ve mesaj imzası eklemiş bulunuyoruz. Artık istemci tarafı güvenlik politikamızı web servis Proxy nesnemize uygulayabiliriz.

[cs]

UsernameClientAssertion assert=new UsernameClientAssertion(userName,password);
Policy policy = new Policy();
policy.Assertions.Add(assert);
serviceProxy.SetPolicy(policy);

İstemci tarafta işimiz bitti şimdi sunucu tarafında istemci tarafta eklenen verileri doğrulayacak ve bu doğrulamaya göre mesajılar süzecek bir politika oluşturalım. Politikalar oluşmadan önce LoginUser metodu çalışacak ve kullanıcı doğrulaması yapılacaktır. Kendi politikamız için kendi kullanıcı doğrulama sınıfımızı yazalım. Senaryoda anlattığımız gibi UsernameTokenManager sınıfını türetmeli ve AuthenticateToken metodunu yeniden yazmalıyız.

[cs]

public class ServiceUsernameTokenManager : UsernameTokenManager {
protected override string AuthenticateToken(UsernameToken token) {
//TODO şifrenin olması gereken halini bul ve return et
    AuthenticationService proxy = new AuthenticationService();
    AuthenticationModel.PERSONELDataTable personel=proxy.AttempSelectUser(id);
     if (personel == null)
        throw new SecurityFault (id ,"Geçersiz kullanıcı ile service erşimi :"+token.Username);
     return personel[0].CH_SIFRE;
}
}

Sunucu politikamızı oluşturalım. İstemci kısmı gibi SOAP Security eklentilerini destekleyen sınıfları türeterek kendi mesaj süzgecimizi ve bildirgemizi oluşturalım.

Şimdi kendi kurallarımızı UsernameServiceAssertion içinde yazabiliriz. Bu politika sunucu tarafında çalışacağı için sadece CreateServiceInputFilter uygulamamız yeterlidir.

[cs]

public class UsernameServiceAssertion : SecurityPolicyAssertion {
    public override SoapFilter CreateServiceInputFilter(FilterCreationContext context) {
        return new ServiceInputFilter(this, context);
    }
public override void ReadXml(XmlReader reader, IDictionary<string, Type> extensions) {
    // TODO: bildirgenin kendisine ait node’u bul oku.
    // mesaj işlemeye devam etsin
    reader.ReadStartElement(tagName);
}

Süzgeç içinde gelen mesajın doğrulamasını yapacağız:

[cs]

public class ServiceInputFilter : ReceiveSecurityFilter {
public override void ValidateMessageSecurity(SoapEnvelope envelope, Security security) {
    bool IsSigned = false;
    if (security != null) {
        foreach (ISecurityElement element in security.Elements) {
            if (element is MessageSignature) {                            
                MessageSignature sign = element as MessageSignature;
                //Beklenen ve gelen mesaj özelliklerini karşılaştır
                if (CheckSignature(envelope, security, sign)) {                            
                    if (sign.SigningToken is UsernameToken) {                            
                        IsSigned = true;
                        // mesaj için gecerli token nesnesi olarak imzanın token nesnesini ata
                        envelope.Context.IdentityToken = sign.SigningToken;
                    }
                }
            }
        }
    }
    if (!IsSigned)
        throw new SecurityFault ("Mesaj güvenlik sorgulaması başarısız oldu. Gecersiz mesaj imzası.");
}………………

Sunucu politika nesnemizi kodlama ile veya config üzerine xml ile oluşturabiliriz.

[cs]

public class ServerPolicy : Policy{
    public ServerPolicy() {
        this.Assertions.Add(new UsernameServiceAssertion());
    }
}

[xml]

<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">
    <extensions>
        <extension name="usernameAssertion" 
type="UsernameAssertionLibrary.UsernameServiceAssertion, 
 UsernameAssertionLibrary"
  />
    </extensions>
    <policy name="ServerPolicy">
        <usernameAssertion />
    </policy>
</policies>

  Her iki şeklinde etkisi aynı olacaktır. İçerisinde sadece UsernameServiceAssertion bildirgesi olan bir politika oluşturacaktır. Daha sonra bu politikayı kendi web servisimize uygulamamız gerekmektedir.

[cs]

[Policy("ServerPolicy")]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService {

Artık Service web servisimiz ServerPolicy ile belirlediğimiz kurallara uyan mesajları alacaktır. Önce istemci tarafından kullanıcı adı ve şifre ile bir token oluşturduk bu token ile mesajı imzaladık. Daha sonra sunucu tarafında bu token ile kullanıcı (şifre) doğrulaması yaptık. Daha sonra mesaj imzasını ve mesaj özelliklerini test ettik. Tüm bu adımları gecen mesajın politikamıza uyan güvenli bir mesajdır.

Mesajın güvenliğinden emin olduğumuza göre son bir işlemimiz kaldı. İstekte bulunan kullanıcıya ait diğer bilgileri bir SOAP başlığına ekleyecek ve kullanıcının yetkisi dışında bir istekte bulunup bulunmadığını kontrol edeceğiz.

[cs]

public CustomSoapHeader header;
 
[WebMethod,SoapHeader("header")]
public byte[] GetAllRaporRow() {
    if(header.BolgeKod != 1000 )
         throw new SecurityFault ("Yetkiniz olmayan bir veriye 
 erişmeye çalışıyorsunuz"
 );
    //TODO birşeyler yap
    return new byte[]{};
}
………
public class CustomSoapHeader : SoapHeader {
    private int _bolgeKod;
 
    public int BolgeKod {
        get {
            return _bolgeKod;
        }
        set {
            _bolgeKod = value;
        }
    }

İstemci tarafında başlık verisini oluşturalım ve mesaja ekleyelim.

[cs]

serviceProxy.CustomSoapHeaderValue = new CustomSoapHeader();
serviceProxy.CustomSoapHeaderValue.BolgeKod = 1000;

8 Ağustos 2007 Çarşamba

Rapor Kütüphanesi Uygulaması

Birçok ipucu içeren ve her zaman başımıza gelebilecek bir uygulama örneğini anlatacağım. Senaryomuz şu:

Minimum 5K-50K arasında kayıta sahip ve veri tabanı maliyeti değerleri çok fazla olan raporlarımız var. Raporlar sunucu tarafında oluşturuluyor ve web servisler ile istemci uygulamalara dağıtılıyor. Bu veriyi Xml formatında istemciye göndermeye çalıştığınızda en küçük veri boyutu 10KB civarında olmaktadır. Xml web servisleri için önerilen veri boyutu maksimum 2KB’dır. O halde yapmamız gerekenler:

  1. İstemci kendisine gelen raporu disk üzerinde saklasın aynı rapor talep edildiğinde bir daha network ve sunucu sistemi meşgul etmesin
  2. Veri boyutunu mümkün olduğu kadar düşürerek network kaynaklarını yormayalım,
  3. İstemci gibi sunucuda oluşturduğu raporu saklasın ve bir raporu veri tabanından bir defa çeksin
  4. Veri güvenliğinde sağlayalım

Çözüm gereği kullanıcı bir rapor talep ettiğinde önce yerel diske bakılacak eğer uygun rapor yoksa uzak sistemden talep edilecektir. Uzak sistemde kendi diskinde rapora arayacak eğer yoksa veri tabanından hesaplatacaktır. Raporu elde eden sistem daha sonra ki çağrılar için raporu disk üzerinde saklayacaktır.

DataSerializer

Veriyi xml formatında serialize etmeye çalıştığımız zaman veri boyutumuz xml taglarından dolayı çok fazla artacaktır. Veri boyutunun düşürmek için varsayılan System.Data.DataSet serilalize formatı olan XMLSerializer’dan vazgeçmeli ve kendi DataSerializer sınıflarımızı yazmalıyız. Bu sınıfın görevi veriyi byte[] olarak serialize edip tekrar byte[]’den veri tipimize geri çevirmek.

İpucu 1: DataTable Binary Serialize

[cs]

public byte[] Serialize(params DataTable[] table) {
 lock (_lockObj) {
  BinaryFormatter formatter = new BinaryFormatter();
  MemoryStream stream = new MemoryStream();
  BinaryDataSet binSet = new BinaryDataSet(table);
  formatter.Serialize(stream, binSet);
  return stream.ToArray();
 }
}

Burada aslında tüm işi BinaryDataSet sınıfının yaptığı gayet acık. BinaryDataSet sınıfı çok sade bir mantıkla kendisine verilen DataTable[] dizisine ait tüm tabloların kolonlarını ve kayıtlarını serialize etmektedir.

[cs]

[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
 info.AddValue("DataSetName", _dataSet.DataSetName);
 info.AddValue("TableCount", _dataSet.Tables.Count.ToString());
 for (int i = 0; i < _dataSet.Tables.Count; i++) {
  List colNames = new List();
  List colTypes = new List();
  List dataRows = new List();   info.AddValue(i.ToString()+"TableName",_dataSet.Tables[i].TableName);   // kolon bilgilerini ekle   foreach (DataColumn col in _dataSet.Tables[i].Columns) {    colNames.Add(col.ColumnName);    colTypes.Add(col.DataType.FullName);   }   // row bilgilerini ekle   foreach (DataRow row in _dataSet.Tables[i].Rows)    dataRows.Add(row.ItemArray);   info.AddValue(i.ToString() + "ColNames", colNames);   info.AddValue(i.ToString() + "ColTypes", colTypes);   info.AddValue(i.ToString() + "DataRows", dataRows);  } }

İpucu 2:DataTable Binary Deserialize

[cs]

public DataSet Deserialize(byte[] data) {
 Guard.ArgumentNotNull(data, "data");
 if (_lastData == data)
  return _lastDataSet;
 lock (_lockObj) {
  BinaryFormatter formatter = new BinaryFormatter();
  MemoryStream stream = new MemoryStream();
  stream = new MemoryStream(data);
  formatter.Binder = new DeserializationBinder();
  BinaryDataSet binDataSet = 
   (BinaryDataSet)formatter.Deserialize(stream);
  if (binDataSet != null) {
   return binDataSet.DataSet;
  }
  return null;
 }
}

public TableType Deserialize(byte[] data) where TableType : DataTable { 
 DataSet dataSet = Deserialize(data);
 TableType result = Activator.CreateInstance();
 if (dataSet != null && dataSet.Tables.Contains(result.TableName)) {
  result.BeginLoadData();
  foreach (DataRow row in dataSet.Tables[result.TableName].Rows) {
   result.ImportRow(row);
  }
  result.EndLoadData();
 } else {
  throw new ApplicationException("Tablo:" + result.TableName 
   + " veri kümesi içinde mevcut değil.");
 }
 return result;
}

İlk Deserialize fonksiyonu verilen byte[] ile bir DataSet nesnesi oluşturmaktadır. İkinci Deserialize fonksiyonu aslında daha çok kullandığımız versiyonudur. İkinci versiyonda byte[] önce DataSet nesne çevirir daha sonra istenen DataTable nesne tipi ile uyuşan tablo aranır ve eğer bulunursa sadece bu tablo geri döndürülür

İpucu 3: Serializetion header bilgisini değiştirmek

Serializtion verisinin ilk 1024byte’lık ilk bölümünü seriaselize edilen tipe ait Assembly bilgileri yazılmaktadır. Bu bilgiler daha sonra deserialize işleminde Activator.CreateInstance(Type) ile nesneyi tekrar oluşturmak için kullanılmaktadır. DataSerializer sınıfının içinde bulunduğu dll hem sunucu hem istemci tarafında kullanılacaktır. Fakat sunucu tarafında byte[] çevirdiğiniz DataSet nesnesi içinde sunucu tarafında ki dll’e ait veriler bulunacağı için bu şekilde istemci tarafında açmaya çalıştığınızda bize Assembly bulunamadı hatası üretecektir. Bunun için byte[] serialize bilgisi içinde ki Assembly bilgisini tekrar göstermek gerekmektedir.

[cs]

internal sealed class DeserializationBinder : SerializationBinder {
 public override Type BindToType(string assemblyName, string typeName) {
  Type type;
  if (typeName.IndexOf("BinaryDataSet") > -1)
   type = typeof(BinaryDataSet);
  else if (typeName.IndexOf("BinaryDataTable") > -1)
   type = typeof(BinaryDataTable);
  else
   type = Type.GetType(String.Format("{0}, {1}",
     typeName, assemblyName));
  return type;
 }
}

Yukarıdaki yöntem bir nesnenin farklı sürümlerini açmak içinde kullanılabilinir.

Artık DataTable nesnelerimizi byte[] çevirebilmekteyiz ve aynı byte[] bilgisini farklı Assembly’ler içerisinde kullanabilmekteyiz. Ayrıca veri byte olarak sakladığımız için veri okunabilir halde değildir.

Veriyi hazırladığımıza göre rapor kütüphane servisini yazabiliriz.

Report Library Service

Rapor kütüphane servisi DataTable nesnelerini byte[] olarak disk üzerine kayıt eden ve bu kayıtları bir index dosyası ile takip eden bir servistir. Verinin DataTable ile byte[] arasında ki dönüşümleri bir önce ki kısımda tartışılan DataSerializer sınıfı ile yapmaktadır.

Servis her raporu index üzerinde saklayabilmek için bir kriter nesnesine ihtiyaç duymaktadır. BaseKriter sınıfından türetilen kriter nesneleri ile raporlar tipleri oluşturulmakta kriter nesnesine ait diğer property’ler ile rapor kriter verisi oluşmaktadır. Böylelikle raporu oluşturan kriter nesnesinin özellikleri ile unique bir HashCode oluşturulmaktadır.

İpucu 4: Raporlar için kriter özelliklerinden HashCode oluşturma

[cs]

Public string GetHashCode(object obj) {
 return Tip2String(obj) + "[" + Kriter2String(obj) + "]";
} 

private string Kriter2String(object obj) {
 Type type = obj.GetType();
 string result = string.Empty;//type.Name;
 foreach (PropertyInfo info in type.GetProperties()) {
  if (info.Name != VERSIYON_PROPERTY 
   && info.Name != RAPORAD_PROPERTY) {
   result += "[" + info.Name + ":";
   if (info.PropertyType.FullName.IndexOf("System") > -1) {
    if (info.GetValue(obj, null) != null)
     if (info.PropertyType == typeof(DateTime))
      result += ((DateTime)(info.GetValue(obj, null)))
       .ToString("yyyyMMdd");
     else
      result += info.GetValue(obj, null).ToString();
    else
     result += string.Empty;
    result += "]";
   } else if (info.GetValue(obj, null) != null) {
    result += Kriter2String(info.GetValue(obj, null));
   }
  }
 }
 return result.ToString();
}
private string Tip2String(object kriter) {
 Type type = kriter.GetType();
 PropertyInfo info = null;
 info = type.GetProperty(VERSIYON_PROPERTY);
 Guard.ArgumentNotNull(info, VERSIYON_PROPERTY);
 int versiyon = (int)info.GetValue(kriter, null);
 info = type.GetProperty(RAPORAD_PROPERTY);
 Guard.ArgumentNotNull(info, RAPORAD_PROPERTY);
 string raporAd = (string)info.GetValue(kriter, null);
 if (string.IsNullOrEmpty(raporAd))
  raporAd = type.Name.Replace("Kriter", "");
 return raporAd + "_v" + versiyon.ToString();
}

Rapor kütüphane servisi Raporları oluştururken önce raporun verisini byte[] çevrilir daha sonra raporun kriter nesnesinden rapor tipi ve rapor kriteri bilgilerini alırın index üzerine yeni rapor kayıttı eklenir ve son olarak byte[] çevrilmiş rapor verisi ayrı bir dosya olarak saklanır.

[cs]

Public byte[] CreateReportData(object objKriter, params DataTable[] tables) {
 Guard.ArgumentNotNull(tables, "tables"); 
 byte[] data = DataSerializer.Serialize(tables);
 CreateReportData(objKriter, data);
 return data;
}

public void CreateReportData(object objKriter, byte[] data) {
 Guard.ArgumentNotNull(data, "data"); 
 string tip = Tip2String(objKriter);
 string kriter = Kriter2String(objKriter);
 RaporModel.RAPORRow newRapor = null;
 ……
 newRapor = CreateReportRow(tip, kriter);
 ………
 _raporTable.AcceptChanges();
 File.WriteAllBytes(RaporPath + newRapor.SQ_RAPOR_NO + MRP_EXTANSION, data);
}

İpucu 5: Tersine hesaplama ile string veriden nesne oluşturma

[cs]

Public object String2Tip(string tipString) {
 PropertyInfo infoVersiyon = null;
 PropertyInfo infoRaporAd = null; 
 foreach (Type type in Assembly.GetCallingAssembly().GetTypes()) {
  infoVersiyon = type.GetProperty(VERSIYON_PROPERTY);
  infoRaporAd = type.GetProperty(RAPORAD_PROPERTY);
  if (infoVersiyon != null && infoRaporAd != null) {
   object kriter = Activator.CreateInstance(type); 
   if (Tip2String(kriter) == tipString)
    return kriter;
  }
 }
 return null;
}

public object String2Kriter(string kriterString, Type type) {
 object t = Activator.CreateInstance(type);
 string prop, val; int pos, len; object propVal = null;
 foreach (PropertyInfo info in type.GetProperties()) {
  if (info.Name != VERSIYON_PROPERTY 
   && info.Name != RAPORAD_PROPERTY) {
   if (info.PropertyType.FullName.IndexOf("System") > -1) {
    if (kriterString.Contains(info.Name)) {
     prop = "[" + info.Name + ":";
     pos = kriterString.IndexOf(prop) + prop.Length;
     len = kriterString.Substring(pos).IndexOf("]");
     val = kriterString.Substring(pos, len);
     if (!string.IsNullOrEmpty(val)) {
      if (info.PropertyType == typeof(string)) {
       propVal = val;
      }else if (info.PropertyType.FullName.IndexOf("Decimal") > -1){
       propVal = Convert.ToDecimal(val);
      }else if (info.PropertyType.FullName.IndexOf("DateTime") >-1){
       propVal = new DateTime(Convert.ToInt32(val.Substring(0,……
      }
      info.SetValue(t, propVal, null);
     } 
    }
   } 
  } else if (info.Name == RAPORAD_PROPERTY) {
   info.SetValue(t, type.Name.Replace("Kriter", ""), null); 
  }
 }
 return t;
}
 

Bol miktarda reflaction ve generic kullanarak rapor kütüphanesinin zor kısmını halletmiş bulunmaktayız. Kullandığımız mantık veri tabanında raporları oluşturmak için verilen parametreleri saklayan kriter entity nesnelerimizi anahtar olarak kullanıp oluşan raporları disk üzerinden saklamak ve takip etmektir. Aynı kriter değerleri ile bir kez daha rapor istendiğinden disk üzerinde hazır bulunan veriyi kullanılmaktadır.


[cs]

public byte[] GetReportDataById(int reportId) {
 RaporModel.RAPORRow rapor = _raporTable.FindBySQ_RAPOR_NOFL_SERVER(
  reportId, (System.Web.HttpContext.Current != null));
 if (rapor != null) {
  if (File.Exists(RaporPath + rapor.SQ_RAPOR_NO.ToString() + MRP_EXTANSION)) {
   AddRating(rapor);
   return File.ReadAllBytes(RaporPath 
    + rapor.SQ_RAPOR_NO.ToString() + MRP_EXTANSION);
  } else {
   rapor.Delete();
   SaveIndexFile();
  }
 }
 return null;
}
 

RAPORDataTable içerisinde yer alan FL_SERVER alanı çalışma alanı sunucumu istemcimi olduğunu gösteren bir bayraktır. System.Web.HttpContext.Current nesnesi IIS üzerinden gelen çağrılarda null’dan farklı bir değer almaktadır. Böylelikle rapor kütüphanesi sunucu ve istemci için her iki tarafta da oluşturulabilir ve kullanılabilir. Kütüphane içerinde aynı tipe ait raporlar JoinReports ile birleştirilebilmektedir. Ayrıca index verisinin treelist şeklinde gösterimine uygun olarak RAPOR_VIEWDataTable sınıfı ve bu sınıfı destekleyen GetReportView fonksiyonları mevcuttur. Böylelikle rapor kütüphanemiz profesyonel uygulamaları destekleyebilecek donanıma sahiptir olmaktadır.

Geri kalan kısımlar için küçük bir bakış yapalım:

[cs]

/// 
/// Rapor Kütüphanesi arayüzü
/// 
/// 
/// Fonksiyon postfix anlamları:
/// Row: Sadece rapor.indx dosyasına ait rapor kütüphanesi index verisi içinde işlem yapar 
/// Data: disk üzerinde yazılan byte[] verileri ile çalışır 
/// View: arayüz işlemlerini destekleyen raporview tablosu ile çalışır
/// Fonksiyon prefix anlamları:
/// CreateReport: Yeni bir rapor oluştur
/// GetAllRapor: Tüm raporlar bilgilerini getirir
/// GetRapor: Verilen parametrelere göre rapor(ları) getirir
/// 
Public interface IReportLibraryService

Cach Extansion

Son olarak web servisler tarafından kullanılacak rapor kütüphanesi için bir eklenti daha yazacağız. CachExtansion sınıfı talep edilen raporu Ram ve disk üzerine yazmaktadır. Yeni bir rapor talebi geldiğinde raporu önce Ram üzerinde yoksa disk üzerinde aramaktadır.

İpucu 6: Web Side Caching

[cs]

public static byte[] SetToCache(HttpContext context, 
  object key,params DataTable[] table) {
 byte[] data = ReportLibrary.CreateReportData(key, table);
 string strKey = ReportLibrary.GetHashCode(key);
 context.Cache.Insert(strKey, data, null, 
  DefaultExpration, DefaultSliding);
 return data;
}

public static byte[] GetFromCache(HttpContext context, object key) {
 string strKey = ReportLibrary.GetHashCode(key);
 // ram üzerinde ki cachde var mı?
 byte[] data = (byte[])context.Cache[strKey];
 if (data == null) {
  // ram üzerinde yok disk üzerinde var mı ? 
  data = ReportLibrary.GetReportDataByKriter(key);
  if (data != null) {
   // dosyayı 5 dk erişim olmasa cachden sil
   context.Cache.Insert(strKey, data, null, 
    DefaultExpration, DefaultSliding);
  }
 }
 return data;
}
 

Birde web servis içinde CachExtansion sınıfının nasıl kullanıldığına bakalım.

[cs]

[WebMethod] 
public byte[] GetBolgeSatisRapor(BolgeSatisKriter kriter) { 
 byte[] result = CachExtansion.GetFromCache(this.Context, kriter);
 if (result == null) {
  HedefRaporModel.CIRO_RAPORDataTable table = 
   _adapterCiro.GetData(kriter.BaslangicTarih, kriter.BitisTarih); 
  result = CachExtansion.SetToCache(this.Context, kriter, table);
 }
 return result;
}
 

Rapor kütüphanesi raporlama uygulamaları için çok değerli bir bileşendir. Benim çözümüm veri tabanı maliyetinin yüksek olan raporlar için raporu bir defa üretmek ve üretilen raporu saklamak paylaşmak şeklindedir. Her sistem talep edilen raporu kendi local diski üzerinde arayacak bulamaz ise uzak sistemden isteyecektir.