Makale Özeti

Her birimiz kendi projemizin uygulama alanına ait bir takım kütüphaneler oluşturmuşuzdur. Bu kütüphanelerin Enterprise Library ile birlikte çalışmasını ve Enterprise Library gibi yapılandırma ve iş kısımlarının bir birinden ayrılmasını ister misiniz? Enterprise Liblary sitilinde uygulama blokları nasıl yazıldığını inceliyoruz. İlk kısımda Enterprise library için Provider kütüphaneleri yazıyoruz.

Makale

Her birimiz kendi projemizin uygulama alanına ait bir takım kütüphaneler oluşturmuşuzdur. Bu kütüphanelerin Enterprise Library ile birlikte çalışmasını ve Enterprise Library gibi yapılandırma ve iş kısımlarının bir birinden ayrılmasını ister misiniz? Enterprise Liblary sitilinde uygulama blokları nasıl yazıldığını inceliyoruz. İlk kısımda Enterprise Library’e ait mevcut Application Block’lar üzerine yeni Provider’lar Application Block Software Factory(ABSF) ile nasıl eklendiğini inceleyeceğiz. İkinci kısımda kendi Block’larımızı yazmak için gerekli kavramları göreceğiz ve yeni bir uygulama bloğu yazacağız. İlk kısımda uygulama olarak kendimize çok muhtemel bir senaryoyu ele alalım. Program üzerinde oluşan kritik hataları yazacağımız/oluşturacağımız ExceptionHanding Application Block Provider ile yakalayalım ve Logging Aplication Block Provider ile kayıt etmek üzere bir web servise yönlendirelim. Tabi bu arada Cripto Apllication Block ile web servis iletişimini şifreleyelim ve web servi üzerinde veri tabanına kayıt etmek için Data Access Application Block kullanalım. Öncelikle buradan Enterprise Library son sürümünü Guidance Automation Extensions (GAX) ve Guidance Automation Toolkit (GAT) bilgisayarınıza yükleyiniz. Şimdi önce Aplication Block Software Factory ile yeni bir Provider Library oluşturalım.

 Smart Client Software Factory serisini takip etti iseniz projeyi oluşturmadan önce Software Factory’nin sizden projenin genel özellikleri sormasını beklersiniz.
  Evet Finish ile Provider kütüphanemizi oluşturalım. Dört ayrı proje oluşacaktır.
  CriticalLibrary bizim provider sınıflarımızın bulunacağı yerdir. CriticalLibrary.Configuration.Design config dosyası içinde bizim provider sınıflarımıza ait node tanımlarının bulunduğu yerdir. Her iki projeye dair test projeleri de Unit Tests dizini içerisinde oluşmaktadır. Şimdi yeni bir Exception Handler ekleyelim.

Application Block Software Factory içerisinde her bir Provider için Typed/Untyped secenekleri görmektesiniz. Typed Provider kendisine ait veri yapısı olan Provider’lardır. Configuration dizini içinde Provider’a ait veri yapısı ile birlikte oluşturulurlar.
Evet CriticalExceptionHandler Povider sınıfımızı ekleyelim. Typed opsiyonunu seçtiğimiz için Configuration dizini içerisinde CriticalExceptionHandlerData sınıfı da oluşacaktır.

/// <summary>
/// TODO: Add CriticalExceptionHandler comment.
/// </summary>
[ConfigurationElementType(typeof(CriticalExceptionHandlerData))]
public class CriticalExceptionHandler :IExceptionHandler {
/// <summary>
/// <para>Initializes a new instance of the <see cref="CriticalExceptionHandler"/>.</para>
/// </summary>
/// <param name="configuration">The configuration object used to set the runtime values</param>
public CriticalExceptionHandler(CriticalExceptionHandlerData configuration) {
    // TODO: Use the CriticalExceptionHandlerData object to set runtime values for the CriticalExceptionHandler.
}
#region IExceptionHandler Members
/// <summary>
/// Handles the exception.
/// </summary>
/// <param name="exception">The original exception.</param>        
/// <param name="handlingInstanceId">The unique identifier attached to the handling chain for this handling instance.</param>
/// <returns>Modified exception to pass to the next exceptionHandlerData in the chain.</returns>
public Exception HandleException(Exception exception, Guid handlingInstanceId) {
    throw new Exception("The method or operation is not implemented.");
}
#endregion
HandleException metodunu doldurarak Exception Handling Application Block Provider’ımızı tamamlayalım. Öncelikle hatalara ait bir veri standardı belirlememiz gerekmektedir. Bir xsd ile hatalara ait kayıt edilecek veri yapısını oluşturalım.
 
Gördüğünüz üzere oluşan hataları kayıt etmek için oldukça geniş detaylı bilgi sağlanmaktadır. Şimdi CriticalExceptionHandler için bu detaylı bilgiyi sağlayacak yeni bir untyped formater’a ihtiyacımız var. Benzer adımları tekrarlayacak CriticalExceptionFormatter sınıfını oluşturalım. CriticalExceptionFormatter Logger formatter sınıflarından farklı olarak ExceptionFormmatter sınıfından türetelim. ExceptionFormmatter özet sınıfı size yapmanız gereken işin gelen hata objesinden ve sistemden topladığınız bilgileri memoryStream üzerine kayıt etmek olduğunu söyler.
[ConfigurationElementType(typeof(CustomHandlerData))]
public class CriticalExceptionFormatter :ExceptionFormatter {
………………
public CriticalExceptionFormatter(MemoryStream memStream, Exception exception): base(exception) {
if (memStream == null)
      throw new ArgumentNullException("memStream", " 'memStream' parametresi null olamaz.");
if (exception == null)
      throw new ArgumentNullException("exception", "'exception' parametresi null olamaz.");
// hatanın saklanacağı stream
_memStream = memStream;
}
public override void Format() {
try {
     _errorDS = new ErrorDS();
     // kendi WriteException fonksiyonun ile log oluştur
     WriteException(base.Exception, Guid.NewGuid());
     // oluşan log bilgisini stream üzerine kayıt et
     _errorDS.WriteXml(_memStream);
} catch(Exception ex) {
     Debug.WriteLine("Hata Formatmala Hatası:" + ex.Message);
     throw ex;
}
} 
}

Evet, şimdide CriticalExceptionHandler sınıfını tamamlayalım.
/// <summary>
/// <summary>
/// Handles the exception.
/// </summary>
/// <param name="exception">The original exception.</param>        
/// <param name="handlingInstanceId">The unique identifier attached to the handling chain for this handling instance.</param>
/// <returns>Modified exception to pass to the next exceptionHandlerData in the chain.</returns>
public Exception HandleException(Exception exception, Guid handlingInstanceId) {
Guid issueTag = handlingInstanceId;
string xmlExceptionData = "";
using(MemoryStream memStream = new MemoryStream()) {
    // hata ile ilgili raporlamaları ErrorDS içinde topla 
    CriticalExceptionFormatter formatter =
        new CriticalExceptionFormatter(memStream, exception);
    formatter.Format();
    xmlExceptionData = Encoding.UTF8.GetString(memStream.ToArray());
}
// ekstra bilgiler için
Dictionary<string, object> dictionary = 
    new Dictionary<string, object>();
// managed security context 
ManagedSecurityContextInformationProvider mgdInfoProvider =
  new ManagedSecurityContextInformationProvider();
mgdInfoProvider.PopulateDictionary(dictionary);
//unmanaged security context
UnmanagedSecurityContextInformationProvider unmgdInfoProvider =
    new UnmanagedSecurityContextInformationProvider();
unmgdInfoProvider.PopulateDictionary(dictionary);
// com plus information
ComPlusInformationProvider complusInfoProvider =
    new ComPlusInformationProvider();
complusInfoProvider.PopulateDictionary(dictionary);
// Logla
LogEntry logEntry = new LogEntry();
logEntry.EventId = GetNextEventId();
logEntry.Severity = TraceEventType.Critical;
logEntry.Priority = 2;
logEntry.Message = xmlExceptionData;
logEntry.Categories.Add(Categories.CriticalExceptionCategory);
logEntry.ExtendedProperties = dictionary;
logEntry.ExtendedProperties.Add("IssueTag", issueTag);
// MAC address al
NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
if(interfaces.Length > 0) {
    string macAddress = interfaces[0].GetPhysicalAddress().ToString();
    logEntry.ExtendedProperties.Add("SiteCode", macAddress);
}
// hatayı logla
Logger.Write(logEntry);
// hatayı geri döndür
return exception;
} 
Artık ExceptionHandler sınıfımız hazır fakat işimiz henüz bitmedi. Loglama için Logging Applicataion Block üzerine Critical Logging Provider sınıfları eklemeliyiz. İlk olarak Hataları formatlamak için CriticalLogFormater sınıfını ABSF kullanarak diğer provider sınıflarına benzer şekilde (typed) oluşturalım. CriticalLogFormater kendisine formatlanmak üzere gelen LogEntry nesnesini ErrorDS.LogEntry kayıtına daha sonrada xml şeklinde çevirir. Eğer gelen LogEntry ErrorDS modeline ait bir xml ise aynı model üzerine eklenir.
/// <summary>
/// Formats a log entry as a serialized representation.
/// </summary>
/// <param name="log">The <see cref="Microsoft.Practices.EnterpriseLibrary.Logging.LogEntry"/> to format.</param>
/// <returns>A string version of the <see cref="Microsoft.Practices.EnterpriseLibrary.Logging.LogEntry"/> that can be deserialized back to a <see cref="Microsoft.Practices.EnterpriseLibrary.Logging.LogEntry"/> instance.</returns>
public override string Format(LogEntry logEntry) {
string xmlLogEntry = "";
try {
    _errorDS = new ErrorDS();
    bool isErrorMessage = TryLoadMessage(logEntry);
    // yeni bir kayıt ac errorDS üzerinde
    ErrorDS.LogEntryRow row = _errorDS.LogEntry.NewLogEntryRow();
    row.id = Guid.NewGuid();
    WriteSiteCode(row, logEntry);
    WriteIssueTag(row, logEntry);
    row.activityId = logEntry.ActivityId;
    row.appDomainName = logEntry.AppDomainName;
    row.eventId = logEntry.EventId;
    row.loggedSeverity = logEntry.LoggedSeverity;
    row.machineName = logEntry.MachineName;
    row.managedThreadName = logEntry.ManagedThreadName;
    row.priority = logEntry.Priority;
    row.processId = Convert.ToInt32(logEntry.ProcessId);
    row.processName = logEntry.ProcessName;
    row.severity = logEntry.Severity.ToString();
    row.timeStamp = logEntry.TimeStamp;
    row.title = logEntry.Title;
    row.win32ThreadId = Convert.ToInt32(logEntry.Win32ThreadId);
    row.categoriesId = Guid.NewGuid();
    WriteCategories(logEntry.CategoriesStrings, row.categoriesId);
    row.errorMessages = logEntry.ErrorMessages;
    if(isErrorMessage)
        WriteExceptionId(row);
    else
        row.errorMessages = logEntry.Message;
    row.extendedPropertiesId = Guid.NewGuid();
    WriteExtendedProps(logEntry.ExtendedProperties, row.extendedPropertiesId);
    _errorDS.LogEntry.Rows.Add(row);
    using(MemoryStream memStream = new MemoryStream()) {
        _errorDS.WriteXml(memStream);
        xmlLogEntry = Encoding.UTF8.GetString(memStream.ToArray());
    }
} catch(Exception e) {
    Debug.WriteLine("LogEnty Formatlama Hatası: " + e);
    throw e;
}
return xmlLogEntry;
}
private bool TryLoadMessage(LogEntry logEntry) {
try {
    using(StringReader exceptionStream = new StringReader(logEntry.Message)) {
        XmlReadMode mode = _errorDS.ReadXml(exceptionStream);
    }
} catch(Exception) {
    return false;
}
return true;
}

Şimdiye kadar olan adımlar ile oluşan hatayı detaylı olarak ErrorDS modeli ile xml formatlı olarak oluşturduk. Artık oluşan hataları yakalayacak ve web servise iletecek bir dinleyiciye ihtiyacımız var. En önemli sınıf gelen loglama isteklerini web servise iletecek olan CriticalLogTraceListener sınıfıdır.

CriticCriticalLogTraceListener kendisine gelen LogEntry’leri önce formatlar daha sonra mesajını şifreler ve hataların veri tabanına kayıt edileceği web servisine gönderir. İlk defa burada bir yapılandırma ihtiyacımız oluştu. Konfigürasyon ile programcı loglamanın yapılacağı URL ve local loglamanın yapılacağı path’leri belirtmelidir.
public> class CriticalLogTraceListener :FormattedTraceListenerBase {
…..
public CriticalLogTraceListener(CriticalLogTraceListenerData configuration) {
    _localEntry = configuration.LocalEntryDirectory;
    _url = configuration.ReportingURL;
} 
public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data) {
if(data is LogEntry && this.Formatter != null) {
    if(this.Formatter is CriticalLogFormatter) {
        FormatAndLog(data as LogEntry);
    } else {
        throw new
            LoggingException("CriticalLogTraceListener formatter olarak CriticalLogFormatter kullanmalıdır.");
    }
}
}
private void FormatAndLog(LogEntry logEntry) {
string entryMessage = this.Formatter.Format(logEntry);
// localede kayıt et
WriteLocalEntry(entryMessage);
// acık bir servis olduğu için datayı şifrele
byte[] entryBytes = Encoding.Unicode.GetBytes(entryMessage);
byte[] encryptedEntryBytes =
    Cryptographer.EncryptSymmetric("RijndaelManaged", entryBytes);
string encodedEncryptedEntryMessage =
    Convert.ToBase64String(encryptedEntryBytes);
byte[] encodedEncryptedEntryBytes =
    Encoding.UTF8.GetBytes(encodedEncryptedEntryMessage);
try {
    CriticalErrorServiceProxy.Service reportService = 
        new  CriticalErrorServiceProxy.Service(_url);
    reportService.Credentials = CredentialCache.DefaultCredentials;
    reportService.ReportError(encodedEncryptedEntryBytes);
} catch(Exception ex) {
    throw ex;
}
}
CriticalLogTraceListener önce veri şifreler daha sonra şifreli veriyi web servisine iletir. Hataların web servis üzerinde ki kayıt işlemi oldukça sadedir. Önce CriticalLogTraceListener tarafından şifrelenen bilgi aynı key dosyası ile çözümlenir. Daha sonra Data Access Application Block ile veri tabanı üzerine kayıt edilir.
[WebMethod]
public void ReportError(byte[] message) {
    if(message.Length == 0)
        return;
    string base64EncryptedEntryMessage = Encoding.UTF8.GetString(message);
    byte[] encryptedEntryBytes =
        Convert.FromBase64String(base64EncryptedEntryMessage);
    byte[] entryBytes =
        Cryptographer.DecryptSymmetric("RijndaelManaged", 
            encryptedEntryBytes);
    string entryMessage = Encoding.Unicode.GetString(entryBytes);
    Debug.WriteLine(entryMessage);
    Database db = DatabaseFactory.CreateDatabase("ErrorConnectionString");
    ErrorDS errorDS = new ErrorDS();
    errorDS.ProcessError(entryMessage, db);
}
Evet şEvet şu anda tüm gereksinimlerimizi tamamladık fakat hala kendi uygulama projemiz içerisinde yazdığımız yeni provider sınıfları kolayca kullanabileceğimiz bir yol oluşturmadık. Artık Desing projesini kullanabiliriz. Design projesi config dosyalar üzerinde ki node yapılarını sağlayan projedir. Öncelikle yeni bir Exception Provider Node oluşturalım.

 
Node Name: Node sınıfına vereceğiniz isindir. CriticalExceptionHandler provider sınıfı için ben CriticalExceptionHandlerNode ismini seçtim. Run Time Configuration Type: Node ile temsil edilecek Provider sınıfının verisini taşıyan veri sınıfıdır. Bizim örneğimiz için veri sınıfmız CriticalExceptionHandlerData’dır. Base Design Node: Oluşturduğumuz node sınıfının hangi sınıftan türetilmesi gerektiğini göstermektedir. CriticalExceptionHandlerNode için doğru temel sınıf ExceptionHandlerNode sınıfıdr. Parent UI Node: Oluşturduğumuz yeni node konfigürasyon içerisnde hangi node altında olması gerektiğini göstermektedir. CriticalExceptionHandlerNode Exception Type Node altında yer almalıdır. Cardinality: Node parent node altında bir defa mı birden cok defa mı yer alabileceğini göstermektedir. CriticalExceptionHandlerNode aynı Exception Type altında bir defa olmalıdır. Böylelikle ExceptionHandler için yeni bir ExceptionHandlerNode oluşturduk. Oluşturduğumuz CriticalExceptionHandlerNode ExceptionNode sınıfından türetilmekte ve konfigürasyon içerisinden Exception Type Node altına sadece bir defa eklenebilemektedir. Önemli bir konuda design projesi içerisinde ki Resource bilgilerini doldurmanız gerekmektedir. Aynı adımı CriticalLogNode ve CriticalLogTraceListener için tekrarlayalım. Şu anda projemiz içerisinde kullanmak için her şey hazır. Yazdığımız bu iki dll’i Enterprise Library kurum dizinine kopyalayalım. Artık istediğimiz proje içersinizde CriticalException provider ve diğer provider nesnelerimizi kullanabiliriz.
Tüm yapılandırmalardan sonra config dosyanızın görüntüsü muhtemelen şu şekilde oluşacaktır.
Böylelikle Enterprise Library için yeni provider’lar nasıl yazılırdığını ve kullanıldığını öğrendik. Enterprise Library bizim için gerekli genel uygulama blokları oluşturmuştur. Biz kendi iş ihtiyaçlarımıza göre nasıl yeni bloklar oluşturabiliriz? Bir sonra ki makale ile yeni uygulama blokları ABSF ile nasıl oluşturulduğunu inceliyeceğiz. Şimdi tüm bu yaptıklarımızı denemek için örnek bir uygulama oluşturalım.

 

Emre Coşkun

http://www.emrecoskun.net/