17 Mayıs 2007 Perşembe

MS Message Queue ile Asenkron Programlama

Birden çok programı asenkron konuşturmak gerekirse ne yaparsınız?  Bir sunucu üzerinde veriyi çekeceksiniz başka bir sunucu ile o veriye ait hesaplamaları yapacaksınız üçüncü bir sunucu ile yeni iş süreçleri oluşturup farklı sistemleri ve kişileri haberdar edeceksiniz. Tüm bu sistem tamamen birbirinden yalıtılmış ve tamamen asenkron olması gerekiyorsa ne yapmalısınız?

MSMQ en basit çalışma yapısı şekilde gösterildiği gibidir. Birçok iş uygulamasında uzak sistemler arasında sender-reciver mantığı ile çalışan haberleşme mekanizmalarına sahiptir. Dağınık programlar arasında ki veri iletişimi MS Message Queue (MQ) ile yapılabilmektedir. MQ programların birbirleri ile asenkron haberleşmek için kullanabilecekleri bir FIFO kuyruktur. ObjectFormater nesnesini desteklediği için object remoting içinde kullanılabilir. Transaction desteğinden server cluster desteğine kadar iş ihtiyaçlarını karşılayabilecek güçlü bir yapıya sahiptir.
MQ işlemleri System.Messaging isim uzayı kullanılır. Kodlama ile bir kuyruk açabilir, kuyruğa yeni mesajlar ekleyebilir ve sıra ile mesajları okuyabilirsiniz.
MessageQueue nesnesine üzerinden çalışılacak message path ve formater'ı vermelisiniz.

[cs]
// eğer messageQ yoksa oluştur
if (!MessageQueue.Exists(this.SMSQInfo.QPath)) {
 MessageQueue.Create(this.SMSQInfo.QPath, true);
}
smsQ = new MessageQueue();
smsQ.Path = this.SMSQInfo.QPath;
((XmlMessageFormatter)smsQ.Formatter).TargetTypeNames = 
 new string[] { "System.String,mscorlib" };
Artık oluşturduğumuz MQ üzerine yeni mesajlar ekleyebiliriz. Send edilen bir mesajın reciver kuyruk tarafından alınması için reciver olarak kullanılan makinada MQ servisinin açık olması gerekmektedir. Eğer reciver kapalı ise veya send işleminde sorun cıkarsa mesajınız kaybolmamalıdır. Bu ihtiyaçtan dolayı MQ transaction yapıyı desteklemektedir.
Bir Message nesnesi oluşturalım daha sonra MessageQueue nesnemize transaction ile Send() edelim.
[cs]
MessageQueueTransaction messageTransaction = new MessageQueueTransaction();
Message message = new Message();
message.Formatter = new System.Messaging.XmlMessageFormatter();
message.Label = smsLabel.ToString();
message.Body = smsBody;
messageTransaction.Begin();
try {
 smsQ.Send(message, messageTransaction);
 messageTransaction.Commit();
} catch (MessageQueueException) {
 messageTransaction.Abort();
 throw;
}
smsQ.Close();
return message.Id;
Şimdi bir kuyruğu dinleyelim ve yeni bir mesaj geldiğinde kuyruktan çekelim ve işledikten sonra tekrar bir sonraki mesajı bekleyelim. Böylelikle bir mesajı işlerken bu kuyruğa mesaj gönderen uygulama sonucuyu beklememektedir. Her iki uygulama arasında kurmamız gereken asenkron işlem mantığını sağlamış olduk.
[cs]
#region start stop listen q
public void StartListenQueue() {
 smsQ.ReceiveCompleted += 
  new ReceiveCompletedEventHandler(smsQ_ReceiveCompleted);
 smsQ.BeginReceive();
}
bool _isStopReceive;
void smsQ_ReceiveCompleted(object sender, ReceiveCompletedEventArgs e) {
 Message m = smsQ.EndReceive(e.AsyncResult);
 m.Formatter = new XmlMessageFormatter(
  new string[] { "System.String, mscorlib" });
 if (SMSQReceived != null)
  SMSQReceived(this, m);
 // bir sonrakini bekle
 if (!_isStopReceive) {
  smsQ.BeginReceive(); 
 }
}
public void StopListenQuese() {
 _isStopReceive = true; 
}
#endregion
Bugün: Asenkron programlama için MQ çok basit ve kullanışlı bir yoldur. Mevcut .net 2.0 teknolojisinde asenkron veri iletişiminde çözüm MS Message Queue haberleşmesidir.
Gelecekte: Bu yöntemde programcı tüm iş akışından sorumludur ve akışı adım adım izlemek gibi bir şansı yoktur. Yeni tüm programlar kendi başlarına çalışırlar. .net 3.0 ile hayatımıza girecek olan BPEL ile artık bu tür iş uygulamalarımızda tüm otomasyonu çalıştırmak bir üst programın sorumluluğunda olacaktır. BPEL bir workflow uygulaması tek farkı tüm işlemlerini WCF ile web servisler üzerinde yapmasıdır. WCF ise gene asenkron ve connectless iletişim için MSMQ kullanmaktadır.
İki ayrı sistem arasında MSMQ ile haberleşecekseniz tabii ki bir standart belirlemeniz gerekmektedir. Bir Dispacher nesnesini ve veri tanımlama standartı oluşturmanıza ihtiyacınız var.

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.

11 Mayıs 2007 Cuma

XML Validation

XML veri doğrulaması nedir? XML veri doğrulamasını koddan bağımsız olarak nasıl yaparız? DataRow nesnesini nasıl Serialise Deserialise yaparız?


Farklı platformlar arasında veriyi taşımak için taşınan verinin modelini de bilmek gerekmektedir. XML veriyi ve veri modelini bir arada saklanması ve taşınması mantığından ortaya cıkmış bir standarttır. XML veri modelleme ve doğrulama için iki geçerli yolumuz var. Biri çok eski bir standart olan DTD standardıdır. Diğeri web servisler ile adını duyuran XSD standardıdır.
XSD (XML Shema Definition) XML veri modelin doğrulanma ihtiyacından doğmuş olan ve ilk Microsoft tarafından duyurulan bir standarttır. Bilindiği üzere Typed DataSet sınıfları TypedDataSetGenerator sınıfı ile XSD dosyaları kullanılarak üretilen nesnelerdir.
DTD (Data Type Definition) XSD benzeri bir XML tanımlama standardıdır. Avantajı XSD ye göre çok basit olması.  Eğer hız her şey ise sizin sisteminizde DTD veri doğrulamak için uygun bir  seçim olabilir. Özellikle eski sistemlerde (web servislerden önce) sıkça kullanılırdı. Eski platformlarda sadece DTD verisini test eden sınıflarımız vardı.  Eğer sizde benim gibi işiniz gereği eski platformları kullanan sistemler ile haberleşmek zorunda kalırsanız DTD doğrulama için Dispatcher sınıfı size yardımcı olacaktır.
XML veriyi kontrol etmek için önce XmlResolver nesnesine veri doğrulama kaynağını vermemiz gerekmektedir. Bu uygulamada veri doğrulama kaynağını koddan bağımsız hale getirmek için veri doğrulama bilgisini dosyadan okuyacağız. Dosyadan okunan veri doğrulama bilgisi için önce XmlResolver sınıfından türetilene ve veri doğrulama şablonunu yerel dosyadan okuyacak bir sınıf yazmalıyız.
[cs]
#region local document type resolver
/// 
/// XML Resolver için uygulama sınıfı
/// 
internal class LocalDocumentTypeResolver : XmlUrlResolver,IDisposable {
 FileStream stream = null;
 public LocalDocumentTypeResolver(String systemEntry) {
  stream = new FileStream(systemEntry,
   FileMode.Open,
   FileAccess.Read,
   FileShare.Read);
 }
 override public object GetEntity(Uri absoluteUri, 
  string role, Type ofObjectToReturn) 
 {
  return stream;
 }
 
 #region IDisposable Members
 // xsd kullanırken data source olarak result gösterilirse file used kalıyor
 // onun için LocalDocumentTypeResolver IDispose olmak zorunda
 public void Dispose() {
  stream.Dispose();
  stream = null;
  GC.Collect();
 }
 #endregion
}
#endregion
Artık yerel dosyalardan doğrulama bilgisini okuyabildiğimize göre XML verileni bu dosyalara göre doğrulayabiliriz.
[cs]
/// 
/// verilen mesaja ve tip tanımlama dosyasına gre veri kümesi üretir
/// 
/// ayrıştırılacak mesaj        
/// veri kontrolünü sahlayacak danımlama dosyası tam yolu
/// mesajın root element
/// doğrulama yapılacak dosya uzantısı dtd veya xsd olmalıdır
/// Doğrulama dosyası xsd veya dtd değil
/// tanımlama dosyasına göre oluşturulmuş veri kümesi
public static System.Data.DataSet Dispacth(string message,string validateFilePath,string rootElement){
        if (string.IsNullOrEmpty(rootElement))
                throw new ArgumentException("rootElement", 
                       "XML doğrulaması yapılacak xml root element verilmelidir.");
        if (!Regex.IsMatch(validateFilePath.ToLower(), @"(.)*\.dtd$|(.)*\.xsd$"))
                throw new ArgumentOutOfRangeException("validateFilePath", 
                        "XML doğrulama dosyası 'dtd' veya 'xsd' uzantılı olmalıdır.");
        DataSet result = new DataSet();
        using(LocalDocumentTypeResolver fileResolver =new LocalDocumentTypeResolver(validateFilePath)){ 
                XmlReaderSettings xmlSetting = new XmlReaderSettings();
                if (Regex.IsMatch(validateFilePath.ToLower(), @"(.)*\.dtd$")) {
                        if (message.IndexOf("" + message;
                        xmlSetting.ValidationType = ValidationType.DTD;
                } else if (Regex.IsMatch(validateFilePath, @"(.)*\.xsd$")) {
                        xmlSetting.Schemas.Add(null, XmlTextReader.Create(validateFilePath));
                        xmlSetting.ValidationType = ValidationType.Schema;
                }
                StringReader textReader = new StringReader(message);
                XmlTextReader xmlTextReader = new XmlTextReader(textReader);
                xmlTextReader.XmlResolver = fileResolver;
                xmlSetting.ValidationEventHandler += 
   new ValidationEventHandler(xmlSetting_ValidationEventHandler);
                XmlReader xmlReader = XmlReader.Create(xmlTextReader, xmlSetting);
                isSuccess = true;
                result.ReadXml(xmlReader);
                if (!isSuccess)
                        result = null; 
        } 
        return result;
}
static void xmlSetting_ValidationEventHandler(object sender, ValidationEventArgs e) {
        isSuccess = false;
}
Yukarıda ki kod ne yapıyor: message ile verilen XML verisini validateFilePath konumunda bulunan DTD veya XSD uzantılı dosyayı LocalDocumentTypeResolver sınıfı kullanarak doğruluyor. XmlReader sınıfına XSD için kullanılacak şema eklemek gerekmektedir. DTD için ise şema bilgisi xml verinin en başında tanımlanmalıdır.
Uygulama kodu ise oldukça sadedir.
[cs]
private System.Data.DataTable Dispatch() {
        string xml = txtXML.Text;
        string shemaFile = optDTD.Checked ? DTDFile : XSDFile;
        string rootElement = txtSchema.Text
  .Substring(0, txtSchema.Text.IndexOf(Environment.NewLine))
                .Split(' ')[1];
        SaveSchemaFile(shemaFile);
        System.Data.DataSet result = 
  Framework.Dispatcher.Dispacth(xml, shemaFile, rootElement);
        return result.Tables[0];
}
Dispatch işleminde bize gereken bir diğer işlev ise Serializable DataRow nesnesidir. Yani önce test edilecek veriyi oluşturmak gerekmektedir. DataRow sınıfı varsayılan yapılandırıcıya sahip olmadı için hiçbir şekilde Serialize haline getirilemez. Çözüm DataRow nesnesini DataTable içine alıp serialize edilmektir.
[cs]
/// 
/// DataRow xml olrak serialize eder
/// 
/// 
public static string SerializeRow(DataRow row) {
    Type tableType = row.Table.GetType();
    DataTable table = (DataTable)Activator.CreateInstance(tableType);
    DataRow newRow = table.NewRow();
    newRow.ItemArray = row.ItemArray;
    table.Rows.Add(newRow);
    StringWriter writer = new StringWriter();
    table.WriteXml(writer);
    return writer.ToString().Replace("xml:space=\"preserve\"", "");
}

/// 
/// xml string üzerinden DataRow üretir
/// 
/// xml data source
/// data table type
public static DataRow DeseriazeRow(string xmlDataSource, Type tableType) {
    StringReader reader = new StringReader(xmlDataSource);
    DataTable table = (DataTable)Activator.CreateInstance(tableType);
    table.ReadXml(reader);
    return table.Rows[0];
}
Bir biri ile ayrı uzaylarda çalışan sistemler eğer web servisi gibi bir teknoloji kullanmadan haberleşiyorsa Dispatcher gibi bir çözüme gereksinim duyarlar. DTD ile temel XML doğrulama özellikle eski sistemler ile yapılan çalışmalarda çok fazla karşımıza çıkmaktadır.