14 Şubat 2008 Perşembe

Application Block Software Factory 3 New Block Design

Bir önce ki makalede kendimize ait yeni bir uygulama bloğu yazmıştık. Bu makale ile yazdığımız mesaj işlem süreci uygulama bloğunu Visual Studio içerisinde yapılandırma dosyalarında Enterprise Library Configuration Tool ‘u kullanmak için gereken tasarım tarafını oluşturacağız. Enterprise Library Configuration Tool yapılandırma dosyasını bir TreeList şeklinde gösteren Visual Stdio içerisine Enterprise Library 3.1 kurulumu ile eklenen bir araçtır. Bu araç tüm uygulama bloklarını ve provider nesnelerini arayüzde gösterdiği için yapılandırma dosyası karmaşasında kurtulup daha kolay işlem yapabilmemizi sağlamaktadır. Uygulama bloğumuzun yapılandırma dosyası tasarım eklentisi TreeNode ve ProviderData sınıflarını birbirine çevirme işleminden başka bir şey değildir. Enterprise Library Configuration Tool ProviderData sınıflarımızı TreeNode olarak ekranda göstermektedir. Dolayısıyla kendi yazdığımız uygulama bloğu için sadece TreeNode ile ProviderData arasında dönüşüm sağlayacak eklenti ile Enterprise Library Configuration Tool destekle hale gelebiliriz. ABSF bu konuda da bize yardımcı olmaktadır. Uygulama bloğumuzun mimari yapısına benzer bir şekilde ProviderData sınıflarımızı temsil edecek Node sınıfları ABSF yardımı ile oluşturalım.

Evet, bizim için temel Node sınıfını oluşturduk. Şimdi ProcessProviderData için Node sınıfını oluşturalım.

ProcessNode sınıfımız Workers elemanına dikkate almamalıdır. WorkerNode elemanını oluşturmaya sıra geldi. WorkerNode elemanı en alt seviyede ki elemanımız olduğu için WorkerNode’u ConfigurationNode elemanından türetiyoruz. Ayrıca WorkerNode ProcessNode altında görünmelidir.

WorkerNode içinde WorkerTypeName alanı bir tip ismini saklamaktaydı. Bu alan için özel bir editör kullanmalıyız. Enterprise Library içinde ihtiyaç duyabileceğiniz birçok editör hazır olarak sağlanmaktadır. WorkerTypeName bir tip ismi sakladığına göre bir TypeSelectorEditor ile değer atanabilmelidir. Temsil ettiği tipte BaseType ile gösterilmektedir.

[cs]

[Editor(typeof(TypeSelectorEditor), typeof(UITypeEditor))]
[BaseType(typeof(IWorker))]
[SRDescription("WorkerTypeNameDescription", typeof(Resources))]
[SRCategory("CategoryGeneral", typeof(Resources))]
public System.String WorkerTypeName {….

Böylelikle ihtiyacımız olan her üç node oluşmuş oldu fakat şu anda tamamen yapılandırma dosyasından bağımsız hareket ediyorlar. (Şu anda her node istediğimiz yerde gözükecektir fakat yapılandırma dosyasında WorkerProviderData ProcessProviderData altında değil bir üst alanda oluşacaktır.) Neler oluştuğuna bir göz atalım.

Enterprise Library Configuration Tool önce AddApplicationBlockSettingsNodeCommand ile acılır menü üzerine yazdığımız uygulama menüsünü eklemektedir. Her bir node üzerinde açılır menüye CommandRegistrar ile oluşturduğumuz node elemanlarını ekleme kodunu oluşturmaktadır. Enterprise Library Configuration Tool yapılandırma dosyasından (diğer bir değişle ProviderData sınıflarından) node elemanlarına dönmek için ApplicationBlockSettingsNodeBuilder sınıfını kullanmaktadır. Node elemanlarında yapılandırma dosyasına dönmek içinse ApplicationBlockSettingBuilder sınıfını kullanmaktadır. Birinden bağımsız hareket eden node ve yapılandırma dosyamızı ApplicationBlockSettingsBuilder ve ApplicationBlockSettingsNodeBuilder sınıflarına yazacağımız kodlar ile bir birleri ile çalışır hale getirmemiz gerekmektedir. Bizim için problem sadece ProcessProviderData sınıfı içinde ki Workers elemanını yapılandırma dosyasında gösterme durumudur.

[cs]

sealed partial class ApplicationBlockSettingsNodeBuilder {        
private void BuildProcesserProviderProviders() {
    foreach (ProcesserProviderData providerData in 
        blockSettings.ProcesserProviderProviders) 
{
        ProcesserNode providerNode =
NodeCreationService.CreateNodeByDataType(
providerData.GetType(), 
new object[] { providerData }) as ProcesserNode;
        if (providerData is ProcessProviderData) {
            // her ProcessProviderData için Workers alanını doldur
            BuildProcessProvider(providerNode as ProcessNode, 
providerData as ProcessProviderData);
        }
        node.AddNode(providerNode);
    }
}

private void BuildProcessProvider(ProcessNode processNode, 
  ProcessProviderData processProviderData) {
    WorkerNode workerNode;
    foreach(WorkerProviderData workerData in processProviderData.Workers) {
        workerNode = 
        NodeCreationService.CreateNodeByDataType(
workerData.GetType(),
                new object[] { workerData }) as WorkerNode;
        processNode.AddNode(workerNode);
    }
}    
}

Böylelikle ProcessProviderData içinde ki Workers elemanlarını node olarak oluşturduk ve TreeList üzerine eklemiş olduk. Node elemanlarını ProviderData’ya ceviren sınıfada kendi eklentilerimizi yazalım.

[cs]

sealed partial class ApplicationBlockSettingsBuilder {
private void BuildProcesserProviderProviders() {
    ProcesserProviderData processerData;
    foreach (ProcesserNode node in hierarchy
 .FindNodesByType(blockSettingsNode, typeof(ProcesserNode))) {
        processerData = node.ProcesserProviderData;
        if (node is ProcessNode) {
            // ProcessProviderData sınıfına ait Workers elemanlarını oluştur
            FillWorkersData(processerData as ProcessProviderData, node as ProcesserNode);
        }
        blockSettings.ProcesserProviderProviders.Add(processerData);
    }
}

private void FillWorkersData(ProcessProviderData processerData, ProcesserNode processerNode) {
    IList workersNode = 
 hierarchy.FindNodesByType(processerNode, typeof(WorkerNode));
    WorkerProviderData workerData;
    WorkerNode workerNode;
    foreach (ConfigurationNode currentNode in workersNode) {
        workerNode = (currentNode as WorkerNode);
        workerData = new WorkerProviderData(workerNode.WorkerProviderData);
        processerData.Workers.Add(workerData);
    }
}        
}

Resource üzerinde ki açıklama ve başlık alanlarını da kendimize göre güncelleyelim. Artık tasarım ve uygulama bloğu dll dosyalarını Enterprise Library Bin dizinine ekleyerek istediğim proje içerisinden MessageProcessApplicationBlock kullanabiliriz.


Böylelikle bir makale serisinin daha sonuna geldik. Bu makale serisi ile önce hazır uygulama blokları için yeni Provider kütüphanelerinin nasıl yazıldığını inceledik. Daha sonra kendi uygulama bloğumuzu oluşturduk. En son olarak oluşturduğumuz uygulama bloğunu daha kolay yapılandırılabilmesi için Enterprise Library Configuration Tool ile uyumlu hale getirdik. Bir sonra ki makale serisi MVC Framework ve Injection Policy Application Block olacak.

8 Şubat 2008 Cuma

Application Block Software Factory 2 New Block

Bir önce ki bölümle Enterprise Library için yeni Provider’lar nasıl eklendiğini inceledik. Ayrıca eklediğimiz yeni providerların Enterprise Library Configuration ile birlikte nasıl kullanıldığını da inceledik. Şimdi Enterprise Library sitilinde yeni bir uygulama bloğu oluşturalım. Aspx page işlemlerinin anımsatacak bir mesaj işlem süreci uygulama bloğu yazıyoruz. Bu uzun makalenin sonuna kadar sabır edebilirseniz kendi Enterprise Library kütüphanenizi yazabilirsiniz.

Mesaj İşlem Süreci Uygulama Bloğu

Muhtemel bir uygulama senaryosu kullanacağız. Bir mesajlaşma uygulama bloğu oluşturacağız. Müşterileriniz sizden bilgi talebinde bulunur. Daha sonra her talep işlenir ve müşteriye geri dönüş yapılacak mesajlar oluşturulur ve müşteriye cevap dönülür. Yazacağımız uygulama bloğu aslında Aspx page işlemlerinin bir benzeri. Uygulama bloğumuz her bir müşterimiz için Request -> Calculate -> Response döngüsü içinde mesaj trafiğini yönetmeli. Uygulama bloğumuz sadece mesaj saklama ve mesaj işlem sınıfları arasında mesajları hareket ettirmeden sorumlu olacak. Bizim uygulama bloğumuzu kullanan projeler her bir mesaj işleme adım için bir veya daha fazla sayıda kendi mesaj işlem sınıfları kullanacak. Hemen konuyu soyut olmaktan kurtaralım.

<MessageProcessApplicationBlock>
<ProcesserProviders>
<add type=" MessageProcessApplicationBlock.ProcessProvider" name="Test Message Process 1">
<Workers>
  <add ExecuteTime="60" MessageQPath=".\Private$\colorQ" State="Calculate" WorkerType="ColorCalculaterWorker, ….."
    type="MessageProcessApplicationBlock.WorkerProvider,…. "
    name="ColorWorker" />
  <add ExecuteTime="60" MessageQPath=".\Private$\errorQ" State="Error"
    WorkerType="ErrorWorker, ….."
    type="MessageProcessApplicationBlock.WorkerProvider, ……"
    name="Error" />
  <add ExecuteTime="60" MessageQPath=".\Private$\intQ" State="Calculate"
    WorkerType="IntCalculater….."
    type="MessageProcessApplicationBlock.WorkerProvider, ……"
    name="IntWorker" />
  <add ExecuteTime="10" MessageQPath=".\Private$\requestQ" State="Request"
    WorkerType="RequestWorker,……"
    type="MessageProcessApplicationBlock.WorkerProvider……"
    name="Request" />
  <add ExecuteTime="10" MessageQPath="Private$\responseQ" State="Response"    WorkerType="ResponseWorker,……"
    type="MessageProcessApplicationBlock.WorkerProvider, ……"
    name="Response" />
</Workers>
</add>
</ProcesserProviders>
</MessageProcessApplicationBlock>

Yukarıda ki xml ile göreceğiniz gibi yazmayı amaçladığımız uygulama bloğu her bir müşteri için bir mesajlaşma işlemi açmaktadır. Örneğimizde “Test Message Process 1” ile açılan mesajlaşma işlemi beş adet mesaj işlem sınıfı eklentisine sahiptir. Uygulama bloğumuza mesaj işlem sınıflarını eklerken mesaj işlem sınıfı ile birlikte bir mesaj kuyruğu ve mesaj işlem sınıfının çalışma aralığını da belirtmekteyiz.

<add ExecuteTime="10" MessageQPath=".\Private$\requestQ" State="Request"
    WorkerType="RequestWorker,……"
    type="MessageProcessApplicationBlock.WorkerProvider……"
    name="Request" />

Uygulama bloğu tüm mesaj kuyruklarını, mesajları, mesaj işlemci sınıfları ve mesaj işlemci sınıflarının çalışma zaman aralıklarını yönetmektedir. Mesaj işlemci sınıfları ise sadece kendisine verilen mesajı işlemekle yetinmektedir. Uygulama bloğu her ExecuteTime sürecinde şu şekilde çalışacaktır: 1) Mesaj kuyruğu üzerinde ki tüm mesajlar bitene kadar 2. Adımı tekrar et 2) Mesaj kuyruğunda en üste ki mesajı çek 2)a. Mesaj işlem sınıfına işlemek üzere gönder 2)b. Eğer mesaj işlem sınıfı sonuç mesajı döndürdü ise 2)b.i. Yeni sonuç mesajını bir sonra ki “State “ değerine sahip mesaj işlem sınıfı kuyruğuna ekle 2)b.ii. Eğer ekleyecek bir mesaj işlem kuyruğu bulanamazsa “Error State” özellikli mesaj kuyruğuna ekle Böylelikle nasıl bir uygulama bloğu yazacağımızı planladık. Şimdi sıra geldi uygulama bloğumuzu oluşturmaya.

New Application Block

Önce projemizi oluşturalım.

Şimdi uygulama bloğumuzu temel işlemlerini gösteren temel sınıfı ve bu soyut temel sınıfı yapılandırma dosyası üzerinde ki verilere göre üretecek factory sınıfını oluşturalım.

Temel sınıf hiçbir ekstra özelliğe sahip değildir. Sadece üst sınıfın adını vermekteyiz. Bu adım ile bizim için birçok sınıf oluşturulmaktadır.

 IProcesserProvider bizim uygulama bloğumuzun temel işlevlerini gösteren ara yüzdür. ProcessProviderFactory ise config dosyadan IProcesserProvider ara yüzünü uygulayan ProcesserProvider nesnelerini üreten sınıftır. Mesaj işleme sürecinin çok basit iki fonksiyonu vardır: Start, Stop

[cs]

namespace MessageProcessApplicationBlock {
/// 
/// Defines the basic functionality of an ProcesserProvider
/// 
[ConfigurationNameMapper(typeof(ProcesserProviderDataRetriever))]
[CustomFactory(typeof(ProcesserProviderCustomFactory))]
public interface IProcesserProvider {
    /// 
    /// Mesaj işleme sürecini başlat
    /// 
    void Start();

    /// 
    /// Mesaj işleme sürecini bitir
    /// 
    void Stop();
}
}


Daha sonradan yazacağımız tüm uygulama bloğu provider sınıflarının atası olacak soyut ProcesserProvider sınıfı IProcesserProvider ara yüzünü basit bir uygulamasıdır.

[cs]

namespace MessageProcessApplicationBlock {
/// 
/// Abstract implementation of the  interface.
/// 
public abstract class ProcesserProvider : IProcesserProvider {
    #region IProcesserProvider Members

    public abstract void Start();

    public abstract void Stop();

    #endregion
}
}


Şimdi mesaj işleme sürecini temsil edecek olan ProcessProvider sınıfı oluşturalım.


Süreci temsil edecek olan ProcessProvider sınıfı uygulama bloğumuza ait temel sınıflardan türetilmektedir.

Assembler

Burada durup işin mutfağına küçük bir göz atalım. Uygulama bloğun yazarken en önemli sorun config üzerinde ki xml node elemanları ile kendi uygulama sınıflarımızı oluşturma sorunudur. Injection Pattern’i hatırlayınız. Injection Pattern ile config üzerinde ki xml node bilgilerinden sınıflarımızı oluşturabiliriz. Enterprise Library’de config üzerinde ki xml node bilgilerinden gerçek sınıflar oluşturmak için Injection Pattern kullanmaktadır. Data(xml node) sınıfları ile gerçek sınıflar arası dönüşümleri IAssembler ara yüzünü uygulayan sınıflar yapmaktadır. AssemblerBasedObjectFactory sınıfı Factory sınıflarımızın temel sınıfıdır. AssemblerBasedObjectFactory nesne oluşturmak için önce oluşturulacak nesneye ait Assembler sınıfı bulur. Daha sonra oluşturulacak nesneye ait Assembler sınıfından nesneyi oluşturması ister.

[cs]

public abstract class AssemblerBasedObjectFactory
    where TObject : class
    where TConfiguration : class
{……………………
public virtual TObject Create(IBuilderContext context, TConfiguration objectConfiguration, 
 IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache)
{
IAssembler assembler = GetAssembler(objectConfiguration);
    TObject createdObject = assembler.Assemble(context, objectConfiguration, configurationSource, reflectionCache);
    return createdObject;
}}

Assembler sınıflar ABSF tarafından bizim için oluşturulmaktadır. Sadece tek bir fonksiyona sahiptir. Assemble fonksiyonu önce oluşturulacak nesneye ait yapılandırma bilgisini alır ve nesne için uygun yapılandırma bilgisine çevirir. Daha sonra bu yapılandırma verisi ile nesneyi oluşturur.

[cs]

IAssembler {

public MessageProcessApplicationBlock.IProcesserProvider Assemble(IBuilderContext context, 
 MessageProcessApplicationBlock.Configuration.ProcesserProviderData objectConfiguration, 
 IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache) {

    ProcessProviderData castObjectConfiguration
        = (ProcessProviderData)objectConfiguration;

    ProcessProvider createdObject
        = new ProcessProvider(castObjectConfiguration);

    return createdObject;
}
}


Enterprise Library Factory

Peki nesneler nerede saklanmaktadır. Injection yapacak Builder nerededir? Tüm nesneler static EnteriseLibraryFactory sınıfı içinde ki Builder nesnesi ile oluşturulmaktadır. ConfiguredObjectStrategy özel Factory sınıfına sahip nesneleri kendi factory sınıfı ile oluşturmaktadır. Böylelikle bizim Assembler sınıflarımız EnterpriseLibraryFactory tarafından fark edilmekte ve yazdığımız provider’lar kendilerine ait assembler sınıfları yardımı ile oluşturulmaktadırlar.

[cs]

public static class EnterpriseLibraryFactory
{
private static IBuilder builder;
private static ConfigurationReflectionCache reflectionCache = new ConfigurationReflectionCache();
static EnterpriseLibraryFactory()
{
    builder = new BuilderBase();
builder.Strategies.AddNew(BuilderStage.PreCreation);
builder.Strategies.AddNew(BuilderStage.PreCreation);
builder.Strategies.AddNew(BuilderStage.PreCreation);
builder.Strategies.AddNew(BuilderStage.PostInitialization);
}
…………..
public static T BuildUp(IReadWriteLocator locator, string id, IConfigurationSource configurationSource)
{
if (string.IsNullOrEmpty(id))
    throw new ArgumentException(Resources.ExceptionStringNullOrEmpty, "id");
if (configurationSource == null)
    throw new ArgumentNullException("configurationSource");
return GetObjectBuilder().BuildUp(locator, id, null, GetPolicies(configurationSource));
}
}

public class ConfiguredObjectStrategy : EnterpriseLibraryBuilderStrategy
{
……..
public override object BuildUp(IBuilderContext context, Type t, object existing, string id)
{
…..
ICustomFactory factory = GetCustomFactory(t, reflectionCache);
if (factory != null)
{
    existing = factory.CreateObject(context, newId, configurationSource, reflectionCache);
} else{….}
….
return base.BuildUp(context, t, existing, newId);
}
}


Böylelikle Enterprise Library kütüphanesinde ki ve ABSF ile yazdığımız kendi uygulama bloğumuz içinde ki tüm nesneler EnterpriseLibraryFactory sınıfı tarafında yukarıda gösterildiği şekilde üretilmektedir.

WorkerProvider

Mesaj işleme sürecini oluşturduk. Şimdi süreç içerisinde her bir mesaj işleme adımını yerine getirecek olan WorkerProvider sınıfımızı oluşturalım.  

 ProcessProvider sınıfı birçok WorkerProvider sınıfa ihtiyaç duymaktadır. Süreci yöneten ProcessProvider süreç içinde her bir mesaj işleme adımını sahibi olduğu WorkerProvider elemanlarına yaptırmaktadır. Öncelikle ProcessProviderData sınıfına sürec işci sınıflarını ekleyelim.

[cs]

[Assembler(typeof(ProcessProviderAssembler))]
public class ProcessProviderData : MessageProcessApplicationBlock.Configuration.ProcesserProviderData {
……
private const string workersProperty = "Workers";
// konfigürasyon versine Worker sınıflarını ekle
[ConfigurationProperty(workersProperty)]
public NamedElementCollection Workers {
    get {
        return (NamedElementCollection)this[workersProperty];
    }
}        
}

public class ProcessProviderAssembler : IAssembler<…..> {
public MessageProcessApplicationBlock.IProcesserProvider Assemble(…….) {
ProcessProviderData castObjectConfiguration
          = (ProcessProviderData)objectConfiguration;
ProcessProvider createdObject
= new ProcessProvider(castObjectConfiguration);
// konfigürasyonda yer alan Worker sınıflarıne gerçek sınıfa cevir
foreach (WorkerProviderData workerData in castObjectConfiguration.Workers) {
            WorkerProvider worker = new WorkerProvider(workerData);
            createdObject.Workers.Add(worker);
            worker.ProcessWorkers = createdObject.Workers;
}
return createdObject;
}
}

[ConfigurationElementType(typeof(ProcessProviderData))]
public class ProcessProvider : MessageProcessApplicationBlock.ProcesserProvider {
public ProcessProvider(ProcessProviderData configuration) {
    _workers = new List();
}
private List _workers;
// konfigürasyonda yer alan Worker sınıflarında taşıyan liste
internal IList Workers {
    get {
        return _workers;
    }
}

public override void Start() {
    foreach (WorkerProvider worker in Workers) {
        worker.Start();
    }
}

public override void Stop() {
    foreach (WorkerProvider worker in Workers) {
        worker.Stop();
    }
}

Böylelikle konfigurasyon dosyasında her bir Process xml node içinede “Workers” node açmış olduk. Workers node içerisinde WorkerProvider node’larını barındırmaktadır. Konfigurasyon sınıfına eklediğimiz “Workers” node’a karşılık olarak ProcessProvider sınıfına “Workers” listesini ekledik. Konfigürasyon ile gerçek sınıflar arasında dönüşümü sağlayan ProviderAssembler sınıfında konfigürasyonda ki “Workers” node bilgisinden gerçek WorkerProvider nesneleri ürettik ve üretilen WorkerProvider nesnelerini ProcessProvider nesnesi üzerinde ki Workers listesine ekledik. Artık mesaj işlem sürecini yöneten ProcessProvider sınıfımız hazır. Şimdi mesaj işlem adımlarını yerine getirecek WorkerProvider sınıfmızı tamamlamalıyız. WorkerProvider sınıfı mesaj işleme adımının çalışma periyodunu ve mesaj kuyruğu işlemlerini yönetmektedir.Mesaj işleme adımında mesajı değerlendirecek ve sonuç mesajı üretecek olan IWorker arayüzünü uygulayan sınıflardır. IWorker sınıfları sürec yönetimini düşünmeden sadece mesajı alır işler ve sonuç mesaj üretir.

[cs]

/// 
/// mesaj işleme arayüzü
/// 
public interface IWorker {
/// 
///  bu işlemci tarafında işlenebilir mi testini yapar
/// 
/// test edilecek mesaj nesnesi ///  işlenebilirse true değilse false bool CanExecute(object message);  ///  ///  nesnesini işle ve sonuc mesajı döndür ///  /// işlenmesi gereken mesaj /// eğer sonuc mesaj varsa return edilmeli yoksa null object Execute(object message);  ///  /// yeni bir mesaj oluştu uygun mesaj kuyruğunu bul ve kuyruğa ekle ///  ///  /// Her hangi bir mesaj kuyruk üzerine eklenmesi gerekiyorsa bu event çağrılır. /// Event MessageProcessApplicationBlock tarafında handle edilmektedir. /// Mesajı alır ve CanExecute true olan WorkerProvider'ın mesaj kuyruğune ekle ///          event EventHandler AddMessageQ;  ///  /// Her executetime sürecinde bir defa çağırılır ///  void Check();  ///  /// Mesaj kuyruğunda her hangi bir değişiklik olduğu zaman  /// istemciyi uyarmak için kullanılır ///  /// kuyruk üzerinde ki tüm mesajlar void OnMessageQChanged(Message[] messages); }

WorkerProvider dışarıdan IWorker arayüzünü uygulayan nesneyi almaktadır. Ayrıca mesaj kuyruğunu ve çalışma periyotlarını bilmesi gerekmektedir.

[cs]

[Assembler(typeof(WorkerProviderAssembler))]
public class WorkerProviderData : MessageProcessApplicationBlock.Configuration.ProcesserProviderData {
………
 [ConfigurationProperty(executeTimeProperty, IsRequired = true, DefaultValue = 30)]
public int ExecuteTime {  get {…}  set {…}}
…
[ConfigurationProperty(messageQPathProperty, IsRequired = true, DefaultValue = "")]
public string MessageQPath {  get {…}  set {…}}

[ConfigurationProperty(stateProperty, IsRequired = true, DefaultValue = MessageState.None)]
public MessageState State {  get {…}  set {…}}

[ConfigurationProperty(workerTypeProperty, IsRequired = true)]
public string WorkerTypeName {  get {…}  set {…}}

public Type WorkerType {
    get {
        return (Type)typeConverter.ConvertFrom(WorkerTypeName);
    }
    set {
        WorkerTypeName = typeConverter.ConvertToString(value);
    }
}
}

WorkerProviderData sınıfına bilmemiz gereken tüm verileri ekledik.WorkerProivderData sınıfı çalışma zamanı periyotunu, mesaj kuyruğunu ve IWorker arayüzünü uygulayan sınıfı almaktadır. Uygulama bloğumuzun asıl işini oluşturan WorkerProvider sınıfını yazalım.

[cs]

[ConfigurationElementType(typeof(WorkerProviderData))]
public class WorkerProvider : MessageProcessApplicationBlock.ProcesserProvider {
private Timer _timer;
private MessageQ _messageQ;
private IWorker _workerClient;
private IList _workers;
private MessageState _state;

public WorkerProvider(WorkerProviderData configuration) {
    _state = configuration.State;
    _timer = new Timer();
    _timer.Interval = configuration.ExecuteTime * 1000;
    _timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
    _messageQ = new MessageQ(configuration.MessageQPath);
    _workerClient = (IWorker)Activator.CreateInstance(configuration.WorkerType);
    _workerClient.AddMessageQ += delegate(object sender, MessageEventArgs e) {
        AddMesssageQ((MessageState)(_state + 1), e.Message);
    };
    _messageQ.MesssageQChanged += delegate(object sender, MessageQEventArgs e) {
        _workerClient.OnMessageQChanged(e.Messages);
    };
}

internal IList ProcessWorkers {
    set {
        _workers = value;
    }
}

……
void _timer_Elapsed(object sender, ElapsedEventArgs e) {
    Message message;
    object resultMessage;
    while (_messageQ.MessageCount > 0 && _timer.Enabled) {
        message = _messageQ.Recive();
        resultMessage = _workerClient.Execute(message.Body);
        if (resultMessage != null)
            AddMesssageQ((MessageState)(_state + 1), resultMessage);
    }
    if (_timer.Enabled)
        _workerClient.Check();

}

private void AddMesssageQ(MessageState nextState, object message) {
    if (message != null) {
        if (nextState == MessageState.None) {
            nextState = (MessageState)(_state + 1);
        }
        foreach (WorkerProvider worker in _workers) {
            if (worker.State == nextState && worker.Worker.CanExecute(message)) {
                worker.MessageQ.Send(this, message);
                System.Threading.Thread.Sleep(1000);
                return;
            }
        }
        AddErrorQ(message);
    }
}
……….

public override void Start() {
    _timer.Start();
}
public override void Stop() {
    _timer.Stop();
}
}

İşte bu kadar! Mesaj işleme süreci uygulama bloğumuzu tamamladık. Aşağıda ki diyagramda görüldüğü gibi mesaj işlem süreci uygulama bloğumuz IProcesserProvider ile belirtilen işleri yerine getiren bir uygulam bloğudur. IProcesserProvider arayüzüne ait işleri özet ProcesserProvider sınıfı uygulamaktadır. Ata sınıfımız olan ProcesserProvider sınıfını uygulayan ProcessProvider mesaj işlem adımlarını yerine getirecek olan bir çok WorkerProvider sınıfına sahiptir. WorkerProvider sınıfı süreç kontrollerini yerine getirirken mesaj işleme işini IWorker arayüzünü uygulayan eklenti sınıfından beklemektedir. 

Bir program ile uygulama bloğumuzu deneyelim. Bizim için rasgele mesajlar üreten bir RequestWorker sınıfı yazalım. Bu sınıf Color, Int ve String tipinde mesajlar oluştursun. ColorWorker, IntWorker ve StringWorker sınıfları bu mesajları alsın ve string tipine dönüştürüp ResponseWorker sınıfına versin. ResponseWorker sınıfı her ExecuteTime ile kendi mesaj kuyruğunda ki tüm mesajları silsin. Tüm mesaj kuyrukları girdler ile ekranda gösterelim ve ne olup bittiğini ekran üzerinden takip edelim.

[cs]

public class RequestWorker : IWorker{
public RequestWorker() {
}

#region IWorker Members

public bool CanExecute(object message) {
// request worker hiçbir mesajı q üzerine kabul etmez
return false;
}

public object Execute(object message) {
// q üzerinde mesaj olmadı için mesajları işlemez
return null;
}

public event EventHandler AddMessageQ;

public void Check() {
// her çalışma zamanında random renkler, sayılar ve stringler üretir ve MessageQ üzerine ekletir            
Random random = new Random();
random.Next();
int randomColorCount = random.Next(1,5);
Color color;
int type;
for (int i = 0; i < randomColorCount; i++) {
    lock (this) {
        type = random.Next(1, 4);
        switch (type) {
            case 1:
                color = ColorTranslator.FromWin32(random.Next());
                AddMessageQ(this, new MessageEventArgs(color));
                break;
            case 2:
                AddMessageQ(this, new MessageEventArgs(random.Next()));
                break;
            case 3:
                AddMessageQ(this, new MessageEventArgs(random.Next(10000, 1000000000).ToString("X")));
                break;
            case 4:
                AddMessageQ(this, new MessageEventArgs(random.Next(1, 1000) / random.Next(2, 488)));
                break;
        } 
    }

    
}            
}

public void OnMessageQChanged(Message[] messages) {
// mesaj kuyruğu değişti ekran üzerinde göster
Program.MainForm.SetRequestMessages(messages);
}

#endregion
}


public class IntCalculater : IWorker{
public IntCalculater() {
}
#region IWorker Members

public bool CanExecute(object message) {
    return message is int;
}

public object Execute(object message) {
    return message.ToString();
}

public event EventHandler AddMessageQ;

public void Check() {
    
}
public void OnMessageQChanged(Message[] messages) {
    Program.MainForm.SetIntMessages(messages);
}
#endregion

}

Konfigürasyon üzerinde makalenin en başında ki gibi  tanımlanmış sürec bilgilerine ekleyelim. Artık tüm bu süreci başlatmak ve dururmak gerçekten çok kolay.

[cs]

public partial class Form1 : Form, IMainForm {
…..
IProcesserProvider _processer = null;
private void _btnStart_Click(object sender, EventArgs e) {
if (_btnStart.Text == StartString) {
    _processer = ProcesserProviderFactory.CreateProcesserProvider("Test Message Process 1");
    _processer.Start();
    _btnStart.Text = StopString;
} else {
    _processer.Stop();
    _btnStart.Text = StartString;
}
}

Bir sonra ki makalede konfigürasyon dosyasına kendi uygulama bloğumuzu elle eklemek yerine, ABSF kullanarak Visual Stidio icerisinde ki Enterprise Library Configuration tool ile nasıl eklenildiğini inceliyeceğiz

7 Şubat 2008 Perşembe

Application Block Software Factory 1 Provider Lib

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.

[cs]

/// 
/// TODO: Add CriticalExceptionHandler comment.
/// 
[ConfigurationElementType(typeof(CriticalExceptionHandlerData))]
public class CriticalExceptionHandler :IExceptionHandler {
/// 
/// Initializes a new instance of the .
/// 
/// The configuration object used to set the runtime values public CriticalExceptionHandler(CriticalExceptionHandlerData configuration) {     // TODO: Use the CriticalExceptionHandlerData object to set runtime values for the CriticalExceptionHandler. } #region IExceptionHandler Members ///  /// Handles the exception. ///  /// The original exception.         /// The unique identifier attached to the handling chain for this handling instance. /// Modified exception to pass to the next exceptionHandlerData in the chain. 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.

[cs]

[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.

[cs]

/// 
/// 
/// Handles the exception.
/// 
/// The original exception.         /// The unique identifier attached to the handling chain for this handling instance. /// Modified exception to pass to the next exceptionHandlerData in the chain. 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 dictionary =      new Dictionary(); // 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.

[cs]

/// 
/// Formats a log entry as a serialized representation.
/// 
/// The  to format. /// A string version of the  that can be deserialized back to a  instance. 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.

[cs]

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.

[cs]

[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 ş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.