12 Mayıs 2007 Cumartesi

Custom Data Source Control

Veri bağlana yeteneğine sahip çeşitli Web Server bileşenleri ile asp.net veri güdümlü mimariyi desteklemektedir. Veri bağlama işlemini veri sunucusu bileşenler (Data Source Controls) ve veri bağımlısı bileşenler (Data-Bound Controls) arasında gerçekleşmektedir. Data Source kontrolleri ilişkisel veri tabanı, dosya, stream nesnesi, iş nesnesi (Businnes Object) gibi her türlü veri kaynağını gösterebilirler. Data Source nesneleri (alta yatan veri kaynağından ve veri formatından bağımsız olarak) istikrarlı bir şekilde veri güdümlü nesnelere veriyi gösterirler.
Veri güdümlü yazılım mimarisinde sahip asp.net projelerinde çok fazla kullanılan iş nesneleri için iş nesnesini veri kaynağı olarak kullanan özelleştirilmiş Data Source kontrolleri yazmak sıkça kullanılan bir yoldur.

Özelleştirilmiş Data Source Control nasıl yazılır?

Run-time

CustomDataSource nesnemiz için iş kurallarını ortaya koyan temel sınıf System.Web.UI.DataSourceControl sınıfıdır. Özet (abstract) DataSourceControl sınıfını incelediğimizde bizden sadece GetView fonksiyonu beklediğini görüyoruz:
[cs]
protected abstract DataSourceView GetView(string viewName);
Burada ikinci bir nesneye ihtiyaç duymaktayız DataSourceView. Verinin run-time görünüşünü sağlamak görevi DataSourceView sınıfından türeteceğimiz sınıfa ait olacaktır. DataSourceView sınıfı da özet bir sınıftır. DataSourceView sınıfı üzerinde veri işlemlerini gerçekleştirmek için yeniden yazmamız gereken Execute fonksiyonları vardır.
[cs]
protected virtual int ExecuteDelete(IDictionary keys, IDictionary oldValues);
protected virtual int ExecuteInsert(IDictionary values);
protected internal abstract IEnumerable ExecuteSelect(DataSourceSelectArguments arguments);
protected virtual int ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues);
Artık CustomDataSource yazabiliriz. Önce DataSourceControl sınıfından türetilmiş ve veri kaynağımıza ait özellikleri taşıyan asıl sınıfımız yazmalıyız. Daha sonra veri kaynağına ait görüntüyü ve işlemleri asıl sınıfta bulunan parametrelere göre yerine getirecek DataSourceView sınıfından türetilmiş yardımcı sınıfa ihtiyacımız var.
Örnek olarak event log üzerinde ki veriyi kullanan CustomDataSource geliştirelim.
Bilindiği üzere Event Log System.Diagnostics.EventLog sınıfı ile üzerine uygulama loglarının işletim sistemi yardımı ile kayıt edildiği genel loglarama yöntemidir. Çeşitli servisleri üzerinde barındıran bir makinenin Event Loglarını uzaktan inceleyebilmek işe yarar bir özelliktir.


EventLogDataSource sınıfı LogName ve LogSource özelliklerini kullanarak Event Log üzerinde ki verileri görüntüsünü GetView ile sağlamaktadır. İçeriğinde ki kodda olduk basit:
[cs]
#region override
protected override ICollection GetViewNames() {
 List ar = new List(1);
 ar.Add(_defaultViewName);
 return ar.ToArray() as ICollection;
}
/// 
/// Data soruce ait dataview eventlogdataview nesnesidir.
/// 
/// 
protected override DataSourceView GetView(string viewName) {
 // bu data sourse sadece bir view a sahipdir
 if (string.IsNullOrEmpty(viewName) || viewName == _defaultViewName)
  return EventLogView;
 return null;
}
private EventLogDataSourceView _eventLogView;

/// 
/// event log view
/// 
[Browsable(false)]
public EventLogDataSourceView EventLogView {
get {
 if (_eventLogView == null)
  _eventLogView = 
   new EventLogDataSourceView(this, _defaultViewName);
return _eventLogView;
}
}
#endregion

EventLogDataSource sınıfı tek bir view a sahip ve GetView fonksiyonu ile sahip olduğu view nesnesini döndürmektedir.
EventLogDataSourceView sınıfını ise Event Log içinde ki veri yönetmekle sorumlu ve başlarken belirlediğimiz gibi sadece Execute fonksiyonalrı ve gerekli özellikleri yeniden yazmış.
Event Log üzerinde ki log kayıtlarında Update veya tek bir kayıtı delete yapamazsınız bundan dolayı. EventLogDataSourceView CanDelete ve CancUpdate false değerlidir ve ExecuteDelete ve ExecuteUpdate fonksiyonlarında hata üretmektedir.
[cs]
protected override int ExecuteDelete(IDictionary keys, IDictionary oldValues) {
 throw new NotSupportedException("Event Log silme işlemi yapılamaz.");
}
ExecuteSelect fonsiyonu içerisinde Event Log EventLogDataSource parametrelerine göre okunmalı ve gerekli DataView geri döndürülmelidir. ExecuteInsert fonksiyonunda verilen veri Event Log içine eklenmelidir.
[cs]
/// 
/// EventSource ve EventLog özellikleri ile belirtilen event logları listeler
/// 
/// 
/// 
protected override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments) {
 DataView dataView = null;
 Model.EventLogRow row;
 try {
  _table.Rows.Clear();
  System.Diagnostics.EventLog objEventLog = 
   new System.Diagnostics.EventLog(_owner.EventLogName);
  EventLogEntryCollection objEntries = objEventLog.Entries;
  foreach (EventLogEntry objEntry in objEntries) {
   if (_owner.EventLogSource == objEntry.Source) {
    row = _table.NewEventLogRow();
    row.Index = objEntry.Index;
    row.EntryType = objEntry.EntryType.ToString();
    row.TimeGenerated = objEntry.TimeGenerated;
    row.Source = objEntry.Source;
    row.Source = objEntry.UserName;
    row.MachineName = objEntry.MachineName;
    row.Message = objEntry.Message;
    _table.Rows.Add(row);
   }
  }
  dataView = new DataView(_table);
  if (_table.Rows.Count > 0)
   dataView.Sort = arguments.SortExpression;
 } catch (Exception) {}
 return dataView as IEnumerable;
}

/// 
/// event source ile belirtilen alana yeni bir log ekler
/// 
/// 
protected override int ExecuteInsert(System.Collections.IDictionary values) {
 System.Diagnostics.EventLog.WriteEntry(_owner.EventLogSource, 
  (string)values["Source"]);
 // en son entry no
 return (new System.Diagnostics.EventLog(_owner.EventLogName))
  .Entries.Count - 1;
}
Artık EventLogDataSource sınıfımızı iş başında görebiliriz Önce projemize ekleyelim gerekli parametreleri verelim ve çalıştıralım:

Design-Tim

Fakat design-time görüntüsü hiçte hoş durmamakta. EventLogDataSource sınıfımıza design-time görüntüsü de eklemeliyiz. Design-Time içinde benzer mantıkla bir Designer birde DesignerView sınıflarını yazmalıyız. Designer sınıfını EventLogDataSource sınıfının designerı olarak da atamalıyız.


[cs]
/// 
/// EventSource ve EventLog özellikleri ile belirtilen event logları listeler
/// 
/// 
/// 
protected override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments) {
 DataView dataView = null;
 Model.EventLogRow row;
 try {
  _table.Rows.Clear();
  System.Diagnostics.EventLog objEventLog = 
   new System.Diagnostics.EventLog(_owner.EventLogName);
  EventLogEntryCollection objEntries = objEventLog.Entries;
  foreach (EventLogEntry objEntry in objEntries) {
   if (_owner.EventLogSource == objEntry.Source) {
    row = _table.NewEventLogRow();
    row.Index = objEntry.Index;
    row.EntryType = objEntry.EntryType.ToString();
    row.TimeGenerated = objEntry.TimeGenerated;
    row.Source = objEntry.Source;
    row.Source = objEntry.UserName;
    row.MachineName = objEntry.MachineName;
    row.Message = objEntry.Message;
    _table.Rows.Add(row);
   }
  }
  dataView = new DataView(_table);
  if (_table.Rows.Count > 0)
   dataView.Sort = arguments.SortExpression;
 } catch (Exception) {}
 return dataView as IEnumerable;
}

/// 
/// event source ile belirtilen alana yeni bir log ekler
/// 
/// 
[Designer(typeof(EventLogDataSourceDesigner))]
public class EventLogDataSource : DataSourceControl {
...
}
public class EventLogDataSourceDesigner : DataSourceDesigner {
...
public override DesignerDataSourceView GetView(string viewName) {
 if (viewName != _defaultViewName)
  return null;
 if (_view == null)
  _view = new EventLogDesignerDataSourceView(this, viewName);
 return _view;
}
}
/// 
/// design-time event log data source view
/// 
public class EventLogDesignerDataSourceView : DesignerDataSourceView {
...
public override IDataSourceViewSchema Schema {
get {
 return (new TypeSchema(typeof(Model.EventLogDataTable))).GetViews()[0];
}
/// 
/// design-time gösterimi için data getir
/// 
/// 
/// 
/// 
public override IEnumerable GetDesignTimeData(int minimumRows, out bool isSampleData) {
 isSampleData = true;
 Model.EventLogDataTable table = new Model.EventLogDataTable();
 DataView dataView = null;
 ...
 // EventLogDataView içinde olduğu gibi EventLog okunur
 // eğer veri yoksa örnek veri oluşturulur 
 if (table.Rows.Count == 0)
  GenerateSampleData(table);
 dataView = new DataView(table);
 return dataView as IEnumerable;
} 

private void GenerateSampleData(Model.EventLogDataTable table) {
 Model.EventLogRow row;
 table.Rows.Clear();
 for (int i = 1; i < 11; i++) {
  row = table.NewEventLogRow();
  row.Index = i;
  row.EntryType = "Sample Entry Type";
  row.TimeGenerated = DateTime.Today;
  row.Source = "Sample Source Type";
  row.UserName = Environment.UserName;
  row.MachineName = Environment.MachineName;
  row.Message = "Event Log Source and Event Log Name properties required to setup.";
  table.Rows.Add(row);
 } 
}
}
.
Burada küçük bir üçkâğıt var. Normalde EventLogDesignerDataSourceView sınıfa şema özelliği içinde IDataSourceViewSchema ara yüzüne sahip bir sınıf ile uzunca kullanılan verinin şema bilgisini anlatmanız gerekmektedir. Ben bir Typed DataSet içinde kullandığım veriyi modelledim ve şema bilgisini bu model üzerinden ürettim. Böylelikle CustomDataSource konusunun sonuna geldik. İşte design-time görüntümüz.