23 Haziran 2008 Pazartesi

Unity Application Block

Smart Client Software Factory, Web Client Software Factory, Spring.net gibi bir çok popüler application framework yapıları kendilerine ait bir object builder nesnesi içerirler. Bu application framework yapıları asıl güçlerini kendi içinde barındırdıkları object builder nesnesini kulanarak injection yapabilmelerinden kazanırlar.

Injection yapabilme güçünü application framework yapılarından bağımsız olarak kullanma isteği Pattern&Practice gurubunun Unity Dependency Container Application Block ürünü tarafından yerine getirilmiştir. Unity application block zamanla SqlHelper/OracleHelper gibi tüm uygulamalara girecek çok geniş kullanım alanı olan bir uygulama bloğudur. Bu uygulama bloğunu anlamak için harcadığınız zaman kesinlikle boşa harcanmış bir zaman değildir.

Unity Application block projemiz için object builder ve dependency container sağlamaktadır.Unity consructor, property ve methodlara injection yapabilen hafif ve genişletilebilir dependecy container uygulama bloğudur. Builder Strategy ekleyerek uygulamanıza yeni 'aspect'ler kazandırablirsiniz. Builder Policy ekleyerek uygulamanıza yeni politikalar ekleyebilirsiniz. Nesneleri config dosyasından oluştabilirsiniz.

Bu makalede Unity dependency container nasıl kullanıldığını inceleyeceğiz. Bir sonra ki makalede uygulamaya yeni aspect ve politikaların nasıl kazandırıldığını ele alacağız. Sonra ki makalede Unity ve diğer Enterprise Library application bloklarının nasıl bir arada kullanıldığını, policy injection ve dependency injection nasıl bir arada çalıştığını inceleyeceğiz. Serinin son makalesinde Unity ve WCF, ASP.NET, MCV framework gibi uygulamaların içide Unity nasıl kullandığını inceleyeceğiz.

Hemen işe başlıyalım ve unity'nin güçünü sınayalım. Tüm projelerde karşılaştığımız bir senaryo vardır. Bu senaryo benim daha önce burada anlattığım ve injection pattern doğmasına sebeb olan ilişkisel katmanlar problemidir. Business logic katmanı data access, log, security gibi bileşenleri ihtiyaç duymaktadır. Log nesneside data access nesnelerine ihtiyaç duymaktadır. Burada klasik yöntem ile yapılan çözüm nesneler her ihtiyaç anında tekrar tekrar oluşturulmaktadır. Bu da hem lock hem fazla kaynak tüketimi sorunlarna yol açmaktadır.Bizim çözümümüz uygulama içinde ihtiyaç duyulan tüm ortak nesne tanımlarını Unity container üzerinde saklamak ve ihtiyaç duyulan yerde Unity containerdan bu nesneleri talep etmektir. Unity üzerinde nesne tanımları ile birlikte lifetime container, politikalar ve builder stratejileride mevcuttur. Unity her creation işlemini bu politika ve stratejilere göre yapmaktadır.

Şimdi unity container nesnemizi oluşturalım.

[cs]

// dependency container oluştur
IUnityContainer container = new UnityContainer();

Life time manager nesnelerin yaşam döngüsünü yönetirler. Nesnelerin tip tanımlarını yapılırken hangi life time manager ile saklanacağı seçilir.Kendi life time manager sınıfınızı oluşturup nesne yaşam döngülerini yönetebilirsiniz.Unity ile gelen iki adet life time manager vardır:

  • Nesne eğer singleton olarak oluşturulacaksa ContainerControlledLifetimeManager kullanılmalıdır.
  • Nesne eğer her ihtiyaçta tekrar oluşturulacaksa ExternallyControlledLifetimeManager kullanılır. Bu life time manager tip tanımlamasında default olarak kullanılır.

[cs]

//lifetime manager'ları oluştur
ContainerControlledLifetimeManager singletonLivetime = 
 new ContainerControlledLifetimeManager();
ExternallyControlledLifetimeManager externallyLivetime = 
 new ExternallyControlledLifetimeManager();

Artık tip tanımlarımızı ekleye biliriz.

[cs]

// tip tanımlarını ekle
//externallyLivetime varsayılan life time manager olduğu için parametre olarak eklemeye gerek yoktur

container.RegisterType(externallyLivetime);

// interface olmaksızın sadece sınıf tanımı ile containere eklenti yapılabilinir
container.RegisterType(singletonLivetime);

//IBusinessA çağrısı olduğu zaman geçerli BusinessA nesnesini döndür
container.RegisterType();

//IBusinessB çağrısı olduğu zaman geçerli BusinessB nesnesini döndür
container.RegisterType();

//ISecurity çağrısı olduğu zaman  Security nesnesini döndür
container.RegisterType();

//IDataAccessB tipinde ve dataAccessB anahtar kelimesi ile çağrı olduğu zaman geçerli DataAccessB nesnesini döndür
container.RegisterType("dataAccessB");

//IBusinessB tipinde 'singletonBusinessB' anahtarı ile çağrı olduğu zaman singleton BusinessB nesnesini döndür
container.RegisterType("singletonBusinessB", new ContainerControlledLifetimeManager());

Şimdi injectionların nasıl yapıldığını inceleyelim. Unity constructor method ve property'lere injection yapabilmektedir. Eğer sınıf tanımında tek bir constructor varsa unity o constructor çağıracak ve gerekli parametreleri enjekte edecektir

[cs]

public DataAccessA(Logger logger) {
 Console.WriteLine("Data access A build up");
 logger.AddLogger("Data access A build up");
}

Eğer sınıf tanımında birden cok constructor varsa unity'e hangi constructor'un kullanılacağını göstermemiz gerekmektedir.InjectionConstructor attribute ile unity'e nesneyi oluşturmak için kullanılacak constructor gösterilmektedir.

[cs]

public class DataAccessB : IDataAccessB {
 public DataAccessB() {
  Console.WriteLine("Data Access B build up with default constructor");
 }
 
 [InjectionConstructor]
 public DataAccessB(Logger logger) {
  Console.WriteLine("Data Access B build up with InjectionConstructor");
  logger.AddLogger("Data Access B build up with InjectionConstructor");
 }
 }

Unity constructor'e injection yaptıktan sonra sınıf tanımda injection bekleyen property'lere injection yapar. Tip tanımında isimlendirme var ise injection sırasında istediğimiz nesnenin ismini belirtmeliyiz.

[cs]

public class BusinessA : IBusinessA {
 public BusinessA() {
  Console.WriteLine("Business A build up");
 }
 
 private IDataAccessA dataAccessA;
 
 //property injection için dependency attribute kullanılır
 [Dependency]
 public IDataAccessA DataAccessA {
  set {
   this.dataAccessA = value;
   Console.WriteLine("IDataAccessA injecting to BusinessA");
  }
 }
}

public class BusinessB : IBusinessB {
 public BusinessB() {
  Console.WriteLine("BusinessB build up");
 }

 private IDataAccessB dataAccessB;
 //IDataAccessB tip tanımını yaparken isimlendirme kullandığımız için
 //injection sırasında istediğimiz nesnenin ismini veriyoruz
 [Dependency("dataAccessB")]
 public IDataAccessB DataAccessB {
  set {
   this.dataAccessB = value;
   Console.WriteLine("IDataAccessA injecting to BusinessA");
  }
 }
}

Unity object builder creation sırasında en son olarak metotlara injection işlemini uygular.

[cs]

public class Security : ISecurity {       
 [InjectionMethod]
 public void Init(IDataAccessA dataAccessA, 
  [Dependency("dataAccessB")]IDataAccessB dataAccessB, 
  Logger logger)
 {
  Console.WriteLine("Security init method");
  dataAccessA.GetData1();
  dataAccessB.UpdateData4();
  logger.AddLogger("security init ok");
 }
}

Böylelikle uygulamamıza ait tüm nesnelerimiz tanımladık. Gerekli injection'ları ayarladık. Şimdi uygulamamızı oluşturalım.

[cs]

// security nesnesini getir
ISecurity security = container.Resolve();
if (security.Check()) {
 security.Login();
 
 // geçerli BusinessA nesnesini getir
 IBusinessA businessA = container.Resolve();
 businessA.DoSameThing1();
 
 // geçerli BusinessB nesnesini getir
 IBusinessB businessB = container.Resolve();
 businessB.DoSameThing4();
 
 //singleton BusinessB nesnesi bir kez oluşturulacaktır
 IBusinessB singletonBusinessB1 = container.Resolve("singletonBusinessB");
 singletonBusinessB1.DoSameThing5();
 IBusinessB singletonBusinessB2 = container.Resolve("singletonBusinessB");
 singletonBusinessB2.DoSameThing6();
 
 // uygulama içinde oluşan nesneleride unity container'a ekleyebiliriz
 BusinessA singletonBusinessA = new BusinessA();
 singletonBusinessA.DataAccessA = container.Resolve();
 container.RegisterInstance("singletonBusinessA", 
  singletonBusinessA, new ContainerControlledLifetimeManager());
 
 //uygulama içinde oluşan singleton BusinessA nesnesi bir kez oluşturulacaktır
 IBusinessA singletonBusinessA1 = container.Resolve("singletonBusinessA");
 singletonBusinessA1.DoSameThing2();
 IBusinessA singletonBusinessA2 = container.Resolve("singletonBusinessA");
 singletonBusinessA2.DoSameThing3();

}

Son olarak childe unity container oluşturalım ve bu childe container ile config üzerinde yer alan tip tanımlamalarına göre yeni nesneler oluşturalım. Childe container ile parent container üzerinde ki tip tanımlamarını kullanabilirsiniz.

[cs]

//childe unity container oluştur
IUnityContainer childeContainer = container.CreateChildContainer();

// config dosyasına ac unity section oku
ExeConfigurationFileMap map = new ExeConfigurationFileMap();
map.ExeConfigFilename = @"D:\Project\ECoskun.UnitySample\ECoskun.UnitySample\dependency.config";
System.Configuration.Configuration config
 = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);

UnityConfigurationSection section
 = (UnityConfigurationSection)config.GetSection("unity");
// config dosyasına göre childe container'ı konfigüre et
section.Containers["childe"].Configure(childeContainer);

// config üzerinde ki tanımlara göre nesneleri oluştur
IServiceA serviceA1 = childeContainer.Resolve(typeof(IServiceA)) as IServiceA;
IServiceA serviceA2 = childeContainer.Resolve();
IServiceB serviceB = childeContainer.Resolve();

serviceA1.OperationA();
serviceA2.OperationB();
serviceA2.OperationC();
serviceB.OperationD();
serviceB.OperationE();

Konfigürasyon dosyası kendi kendisini açıklar şekildedir.

<unity>
<typeAliases>
    <!-- Lifetime manager types -->
    <typeAlias alias="singleton"
        type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager,
        Microsoft.Practices.Unity" />
    <!-- User-defined type aliases -->
    <typeAlias alias="IServiceA"
        type="ECoskun.UnitySample.IServiceA, ECoskun.UnitySample" />
    <typeAlias alias="ServiceA"
        type="ECoskun.UnitySample.ServiceA, ECoskun.UnitySample" />
    <typeAlias alias="IServiceB"
        type="ECoskun.UnitySample.IServiceB, ECoskun.UnitySample" />
    <typeAlias alias="ServiceB"
        type="ECoskun.UnitySample.ServiceB, ECoskun.UnitySample" />
</typeAliases>
<containers>
    <container name="childe">
        <types>
            <type type="IServiceA" mapTo="ServiceA">
            <lifetime  type="singleton"/>
        </type>
        <type type="IServiceB" mapTo="ServiceB" />
        </types>
    </container>
</containers>
</unity>


Bu yapıyı Composite application pattern üzerinde görmüştük. Her bir use case WorkItem nesnelerine denk gelmekteydi. Tüm WorkItem nesnelerini taşıyan bir RootWorkItem nesnesi vardı. Unity container ile yukarda ki örnekte görüldüğü gibi composite application pattern vb yapıları kolaylıkla oluşturabilirsiniz.Artık uygulama nesneleriniz avucunuzun içinde uygulama nesneleri ile istediğiniz gibi oynayabilirsiniz. 

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");
 }

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.

12 Ocak 2008 Cumartesi

Smart Client Software Factory VS Entegrasyonu

Bu seri ile birlikte sıfırdan başlayıp tüm CAB elemanlarını kullanmayı öğrendik. Şimdide Visual Studio içerisinde SCSF nasıl kullanılır olduğunu öğreneceğiz. Burada ki tüm kurulum adımlarını izlediniz ve SCSF bilgisayarınıza yüklediniz. Artık yeni bir SCSF uygulaması yazmaya hazırsınız. Bu seri ile incelediğimiz Easy TaskBar uygulamasını birde SCSF kullanarak tekrar yazalım.

Öncelikle Yeni bir SCSF uygulaması başlatmalıyız. Uygulamayı ilk oluşturmak istediğinizde karşınıza Root Namespace ve dll kütüphanesinin yerini soran bir ekran gelecektir.
Sağ tarafta SCSF oluşturacağı dosyaları göstermektedir. Tüm uygulamalar için ortak olan kod bloğu Infrastructure içerisine alınmıştır. Daha önce ki makalelerden hatırladığınız Shell Liblary Layout ve Interface projeleri Infrastructure içerisindedir. Finish’i tıkladığınızda SCSF sizin için bu beş projeyi oluşturacak ve gerekli kodları ekleyecektir. Oluşan kodları çalıştırdığımızda aşağıda ki gibi bir ekran görüntüsü çıkacaktır.

Infrastructure.Library projesini inceleyelim. Burada uygulamamız için önceden tanımlanmış birçok hazır servisin olduğunu görmekteyiz.
Burada ki servisler tüm uygulama için geçerli genel servislerdir. Sizin SCSF işleyişine müdahale etmek istediğiniz zaman kullanacağınız servislerdir. Infrastructure.Interface içerisinde Constants dizini içerisinde CommandNames, EventTopicNames, UIExtasionNames, WorkspaceNames sınıfları bulunmaktadır. Bu sınıflar adından da anlaşılacağı üzerinde uygulama genelinde kullanılacak isimlendirmelerdir. Şimdi Easy TaskBar uygulamamızı geliştirmeye başlayalım. Önce ProcessService sınıfını Infrastructure.Library’e IProcessService interface’ini Infrastructure.Interface’e ekleyelim. Daha sonrada WinFormAuthenticationService’i ekleyelim. Servis interfaceleri Infrastructure.Interface projesinde Service dizini altına eklenir. Servis tanımları da Infrastructure.Library projesinde Service dizini altına eklenir. Tüm servisleri daha sonra RootWorkItem üzerine ekleyelim.
[cs]
namespace SmartClientDevelopmentSolution1.Infrastructure.Library {
public abstract class SmartClientApplication : 
 WPFFormShellApplication
where TWorkItem : WorkItem, new()
where TShell : Form {
protected override void AddServices() {
base.AddServices();
// scsf tarafında gelen servisler
RootWorkItem.Services.AddNew();
RootWorkItem.Services.AddNew();
RootWorkItem.Services.Remove();
RootWorkItem.Services.Remove();
RootWorkItem.Services.AddNew();
RootWorkItem.Services.AddNew();
RootWorkItem.Services.AddOnDemand();
RootWorkItem.Services.AddOnDemand();

// uygulama genelinde çalışacak kendi servislerimiz
IActionCatalogService actionCatalog = RootWorkItem.Services.Get();
actionCatalog.RegisterGeneralCondition(new SimpleActionCondition());
RootWorkItem.Services.Remove();
RootWorkItem.Services.AddNew();
RootWorkItem.Services.AddOnDemand();
}
}…….
}

Şimdi de modüllerimizi oluşturalım.Ben modülleri SCSF alt yapısından ayırmak için yeni bir ‘Module’ dizisinin altına ekliyorum. Module dizinin üzerine sağ tıkladığınız zaman açılan menüde Smart Client Factory menüsünü göreceksiniz. Burada Business Module ve Foundational Module ekleme opsiyonları görüyorsunuz

Business Module iş kodlarımızı yazdığımız içerisinde Servisler View’lar barındıran modüldür. Foundational Module Business Module’den farklı olarak uygulama genelinde kullanılan Infrastructure.Layout içerisinde ki ShellLayoutView Layout’u yerine bu modül için kullanılacak özel bir layout ile birlikte gelir. Biz yolumuza yeni bir Business Module oluşturarak devam edeceğiz. Menüden Business Modüle seçtikten sonra aşağıda ki ekran ile karşılaşıyoruz.

Burada OK tıklayıp devam ettiğimizde aşağıda ki ekran ile karşılaşıyoruz.

Burada oluşturduğumuz modül için Interface projesi, test projesi ve projeleri oluşturduktan sonra yardımcı bir doküman isteyip istemediğimizi sormaktadır. Eğer modül içerisinde ki her hangi bir bileşen tanımına diğer servislerden ulaşılacaksa Interface projesi eklememiz gerekmektedir. Eğer test projesi eklersek modülümüze eklediğimiz her View için test kodları otomatik olarak eklenecektir. OK tıklayıp modülümüzü oluşturalım. Sıra geldi tüm process’lerin listeleneceği NavigatorView’ını yazmaya.

WPF form veya normal Windows formdan View oluşturabilirsiniz. Biz normal View oluşturmayı seçelim. Bir sonraki adımda aşağıda ki ekran ile karşılaşacağız.

Burada oluşturduğumuz View’a ismini verelim. Genelde her View’ın kendi dizininde olması daha iyidir. Finish’i tıklayalım ve SCSF bizim için MVP pattern ile oluşturulmuş View ve Presenter sınıflarımızı oluştursun. Arayüz işlemlerini tamamladıktan sonra NavigatorViewPresenter üzerine bir event ekleyelim.

Bu adım ile birlikte NeedChanfePrecess event’ını presenter içine eklemiş olduk. Şimdi oluşturduğumuz navigator view’ı ilk çalışmada LeftWorkspace üzerinde açalım.
[cs]
namespace SmartClientDevelopmentSolution1.NavigatorModule {
public class ModuleController : WorkItemController {
 public override void Run() {
  AddServices();
  ExtendMenu();
  ExtendToolStrip();
  AddViews();
}

private void AddServices() {}

private void ExtendMenu() {
 ToolStripMenuItem miRefresh = new ToolStripMenuItem();
 miRefresh.Name = "miRefresh";
 miRefresh.Text = "Yenile";
 this.WorkItem.Commands[CommandNames.Refresh].AddInvoker(miRefresh, "Click");
 this.WorkItem.UIExtensionSites[UIExtensionSiteNames.MainMenu].Add(miRefresh);
}

private void ExtendToolStrip() {
 ToolStripButton btRefresh = new ToolStripButton();
 btRefresh.Name = "btRefresh";
 btRefresh.Text = "Yenile";
 this.WorkItem.UIExtensionSites[UIExtensionSiteNames.MainToolbar].Add(btRefresh);
 this.WorkItem.Commands[CommandNames.Refresh].AddInvoker(btRefresh, "Click");
}

private void AddViews() {
 NavigatorView navigatorView = ShowViewInWorkspace(WorkspaceNames.LeftWorkspace);
}
}
}
Yukarıda gördüğünüz gibi ModuleController üzerinde modlülün ilk çalışmasında uygulamaya eklenecek Service, Menü, ToolStrip ve View’lar için ayrı ayrı fonksiyonlar açılmıştır. View’ı eklerken Yenile menülerimizi de ekliyoruz. Daha önce ki bölümlerde Eventlar’ın ve Command’ları ne olduğu ve nasıl çalıştığını incelemiştik. Burada tanımlamalarına yaptıktan sonra gerekli kodları NavigatorViewPresenter içine yazıyoruz. Şimdi uyglamamızı çalıştıralım.

Aynı adımları izleyerek Context modülü ve Contextview ‘ı oluşturalım ve uygulamamızı tamamlayalım.
Burada biz SCSF Visual Studio içerisinde nasıl kullanır olduğunu gördük. SCSF ile gelen MVP, Event Broker, Command, Action gibi kavramları anlamak için önce ki makalelere bakmanız gerekmektedir. Son bir not SCSF şu anda VS 2008 desteklememektedir. VS 2008 SCSF şubat 2008 içerisinde yayınlanacağı duyuruldu. Böylelikle 8 makalelik SCSF serisinin sonuna geldik bir sonra ki makale serisi Application Block Software Factory konusudur.

10 Ocak 2008 Perşembe

Smart Client Software Factory UIElement - Command - Action

UI Element

Son uygulamamızda birden çok view’ın bir biri ile nasıl haberleştiğini inceledik. Şimdi uygulamaların ortak menu toolbar gibi elemanları nasıl kullandığını inceleyelim.

ShellLayoutView üzerinde View’larımızı taşıyan WorkSpace’lerimiz ve tüm uygulamanın ortak kullanımında olan UIExtansionSite nesnelerimiz mevcuttur. IUExtansionSite nesneleri genelte MenuBar ToolBar, StatusBar gibi elemanlardır. CAB MenuBar, ToolBar ve StatusBar için hazır register sınıflarını sunmaktadır. Kendi kontrollerinizi CommandAdapter sınıfı yazarak CAB uygulamasına IUExtansionSite olarak ekleyebilirsiniz.

Örnek uygulamamız üzerinden devam edelim. Uygulamamıza bir menü elemanı ve birde toolbar elemanı ekleyelim. Her iki menü elemanıda Click olaylarında process listelesi güncellesin. Öncelikle ortak kullanılacak olan MenuItem ToolbarItem ve StatusBar nesnelerini RootWorkItem üzerine register edelim.

[cs]

public class ShellLayoutViewPresenter  {
……………………
public void OnViewSet() {            
// layout module ait work item nesnesine UI elemanlarını register et
_workItem.UIExtensionSites.RegisterSite("MainMenu", View.MainMenuStrip);
_workItem.UIExtensionSites.RegisterSite("MainStatus",View.MainStatusStrip);
_workItem.UIExtensionSites.RegisterSite("MainToolbar",View.MainToolbarStrip);
}
…………
}

Artık her hangi bir modül bu üç ortak arayüz elemanına erişip kullanabilir.

[cs]

public class MaviModuleInit :ModuleInit {               
private WorkItem _workItem;
 [InjectionConstructor]
public MaviModuleInit([ServiceDependency] WorkItem workItem) {
    _workItem = workItem;
}
public override void Load() {
base.Load();
NavigatorView kirmiziView = _workItem.SmartParts.AddNew();
_workItem.Workspaces["NavigatorWorkspace"].Show(kirmiziView);

InitMenu();
InitToolstrip();            
}

private void InitToolstrip() {
ToolStripButton btRefresh = new ToolStripButton();
      btRefresh.Name = "btRefresh";
      btRefresh.Text = "Yenile";
      
_workItem.UIExtensionSites["MainToolbar"].Add(btRefresh);
      _workItem.Commands["Refresh"].AddInvoker(btRefresh, "Click");
}

private void InitMenu() {
ToolStripMenuItem miRefresh = new ToolStripMenuItem();
      miRefresh.Name = "miRefresh";
      miRefresh.Text = "Yenile";
      _workItem.Commands["Refresh"].AddInvoker(miRefresh, "Click");
      _workItem.UIExtensionSites["MainMenu"].Add(miRefresh);  
}
}

Command Pattern

Çalışma anında ekran görüldüğü gibi toolbar ve menü elemanımız eklenmiştir. Artık yenile menü elemanlarının ‘Click’ olaylarında ‘Refresh’ command’ı çalışacaktır. Event’lar kod ile programcının tetiklediği mekanizmalardır. Command’lar ile kullanıcının ara yüz elemanları ile tetiklediği mekanizmalardır. Command’lar bir invoker tarafındana tekillenirler ve inkover’ler her hangi bir event’a bağlanabilirler. _workItem.Commands["Refresh"].AddInvoker(miRefresh, "Click"); Biz burada Refresh Command’ını miRefresh nesnesinin Click olayına bağlamış olduk. Command genel bir olay duyurusunu beklememekte Command özel bir olayı beklemektedir. Genelde bu özel olay kullanıcı arayüz işlemleri ile tetiklenen olaylardır. Biz örnek uygulamamızda kullanıcı ‘Yenile’ menülerinden birine bastığı zaman process listesini güncellemeliyiz.

[cs]

public class NavigatorViewPresenter {
…….
[CommandHandler("Refresh")]
public void OnRefresh(object sender, EventArgs e) {
List data = _processService.EnumProcesses();
      View.DataSource = data;
}
………………..
}

Gelişmiş uygulamar için gerekli tüm aletlere sahibiz. Dinamik olarak modülleri view’ları yükleyebiliyoruz, iş kodlarımızı view’lardan ayırabiliyoruz, oluşan olayları yayınlaya biliyor veya olaylara üye olabiliyoruz, ortak ara yüz elemanlarını yönetebiliyoruz, özel durumlara Command Pattern ile cevap verebiliyoruz.

Action

Bir sonra ki uygulamada Action’lar ile fonksiyon bazlı yetkilendirmeyi inceeyeceğiz. Gelişmiş iş uygulamalarının en büyük ihtiyaçı kuşkusuz yetkilendirmedir. Fonksiyon bazlı yetkilendirme yapabilir misiniz?

Action’lar iş kodlarımızın yazıldığı yerlerdir.Action’lar ’Action’ attribute ile işaretlediğimiz ActionDelegate ile temsil edilebilen fonksiyonlardır. Bir workItem üzerinde ki Action’lar ActionCatalog tarafından saklanır ve yönetilirler. ActionCatalog üzerinde ActionCondition nesneleride mevcuttur. Çalıştırmak istediğiniz Action’ı ActionCatalogService söylersiniz. ActionCatalogService çalıştırılacak Action için geçerli Condition’ların hepsini denetler eğer tüm sınamalardan geçerse Action çalıştırılır.

[cs]

public delegate void ActionDelegate(object caller, object target);

public interface IActionCatalogService {
bool CanExecute(string action, WorkItem context, object caller, object target);
bool CanExecute(string action);
void Execute(string action, WorkItem context, object caller, object target);
void RegisterSpecificCondition(string action, IActionCondition actionCondition);
void RegisterGeneralCondition(IActionCondition actionCondition);
void RemoveSpecificCondition(string action, IActionCondition actionCondition);
void RemoveGeneralCondition(IActionCondition actionCondition);

void RemoveActionImplementation(string action);
void RegisterActionImplementation(string action, ActionDelegate actionDelegate);
}
}

Oluşan tüm Action’ları yakalamak için bir BuilderStrategy sınıfına olan ActionStrategy sınıfını yazılmalıdır. Injection Pattern’den hatırlayınız object builder’a ekleyeceğiniz Strategy nesneleri ile oluşturulan tüm nesnelere oluşturulma anında eriş ve oluşan nesneleri denetleyebilirsiniz. ActionStrategy oluşan tüm nesnelerde ActionDelegate ile temsil edilebilecek Action attribute’ne sahip fonksiyonları arar ve bulduğu fonksiyonları ActionCatalogServis üzerine ekler

[cs]

public class ActionStrategy : BuilderStrategy {
…………………
public override object BuildUp(IBuilderContext context, Type typeToBuild, 
	object existing, string idToBuild) {
WorkItem workItem = GetWorkItem(context, existing);
if (workItem != null) {
IActionCatalogService actionCatalog = workItem.Services.Get();
if (actionCatalog != null) {
Type targetType = existing.GetType();
foreach (MethodInfo methodInfo in targetType.GetMethods())
          RegisterActionImplementation(context, actionCatalog, existing, idToBuild, methodInfo);
}
}
return base.BuildUp(context, typeToBuild, existing, idToBuild);
}
…………
}

Böylelikle tüm Action’lar ActionCatalogService üzerine eklenmiş olamaktadır. ActionStrategy’i object builder’ın builder strategy’lerine ekleyelim

[cs]

public abstract class SmartClientApplication : FormShellApplication 
{
protected override void AddServices() {
base.AddServices();
…….
RootWorkItem.Services.AddOnDemand();
IActionCatalogService actionCatalog = RootWorkItem.Services.Get();
      actionCatalog.RegisterGeneralCondition(new SimpleActionCondition());
}
protected override void AddBuilderStrategies(Microsoft.Practices.ObjectBuilder.Builder builder) {
     base.AddBuilderStrategies(builder);
        builder.Strategies.AddNew(
BuilderStage.Initialization);
}
}

Yukarıda AddService içinde önce ActionCatalogService oluşturuldu. Daha Sonra SimpleActionCondition genel Action Condition olarak register ettik. Daha sonra AddBuilderStrategies içinde object builder’a yeni ActionStrategy’i ekledik. Şimdi SimpleActionCondion oluşturalım.

[cs]

public class SimpleActionCondition : IActionCondition {
#region IActionCondition Members

public bool CanExecute(string action, Microsoft.Practices.CompositeUI.WorkItem context, 
	object caller, object target) {
return Thread.CurrentPrincipal.IsInRole("ADMIN");
}
#endregion
}

Burada ‘ADMIN’ rolüne sahip ise Action çalıştırılacaktır. Tüm bu adımlar SCSF tarafında oluşturduğunuz projede olacaktır. Biz SCSF ile oluşturduğumuz projede sadece Action fonksiyonlarımızı oluşturacağız.

Authentication

Örnek uygulamamıza geri dönelim. Önce kullanıcıları Authenticate etmeliyiz. Daha sonra ADMIN rolune sahip kullanıcıların process detaylarını görmelerini sağlayacak kodları yazmalıyız. Önce kullanıcıya yetki verebilmek için kullanıcının uygulamaya login olmasınısı sağlamalıyız. CAB varsayılan olarak bir AuthenticationService bulundurur. Bu servis kullanıcının bilgilerini Aktive Directory üzerinden alır ve ona göre yetkilendirme uygular. Fakat biz kendi yetkilendirme kurallarımızı uygulayacağımıza göre kendi Authentication servisimizi yazmalıyız.

[cs]

namespace Microsoft.Practices.CompositeUI.Services {
    public interface IAuthenticationService {
        void Authenticate();
    }
}

public class WinFormAuthenticationService : IAuthenticationService {        
…..
[EventPublication("UserAuthorize", PublicationScope.Global)]
public event System.EventHandler UserAuthorizeHandler;

#region IAuthenticationService Members
public void Authenticate() {
    // login formu çıkart ve kullanıcı bilgilerini doğrula
UserData user = SelectUser();
      if (user != null) {
          GenericIdentity identity = new GenericIdentity(user.UserName);
            GenericPrincipal principal = new GenericPrincipal(identity, new string[] { user.Rol });
            Thread.CurrentPrincipal = principal;
            UserAuthorize();
} else {
          throw new AuthenticationException();
}
}

private UserData SelectUser() {……}

private void UserAuthorize() {
if (UserAuthorizeHandler != null)
          UserAuthorizeHandler(this, new EventArgs());
}
#endregion
}
}

Evet yazdığımız Authentication servisi mevcut servisin yerine eklemeliyiz şimdide.

[cs]

public abstract class SmartClientApplication : 
	FormShellApplication {
………
protected override void AddServices() {
base.AddServices();
RootWorkItem.Services.Remove();
      RootWorkItem.Services.AddNew();

            …..
 }
}

Artık uygulama ilk açıldığında Login fomumuz açılacaktır

Artık tüm alt yapımız hazır. Tüm Action’ların üzerine kayıt edildiği ActionCatalogServisimiz bu servisi register edilmiş ADMIN kullanıcılara çalıştırma izini veren SimpleActionCondition sınıfımız ve uygulamayı kullanan kullanıcının bilgilerini doğrulaya kendi WinFormAuthentication sınıfımız var. Artık ADMIN kullanıcılar için process detaylarını göstermeye izin veren Action fonksiyonumuzu yazabiliriz.

[cs]

public class ContextViewPresenter {
…….

[EventSubscription("NeedChangeProcess", ThreadOption.UserInterface)]
public void OnNeedChangeProcess(object sender, EventArgs> eventArgs) {            
_actionCatalogService.Execute("ChangeProcessAction", this.WorkItem, this, eventArgs.Data);            
}

[Action("ChangeProcessAction")]
public void OnChangeProcess(object caller, object target) {
Guard.TypeIsAssignableFromType(target.GetType(), typeof(List), "ProcessDetailInfo");
      List info = (List)target;
      View.DataSource = info;
}
…….
}

OnNeedChangeProcess fonksiyonu içerisinde “ChangeProcessAction” fonksiyonun çalıştırılması gerektiğini ActionCatalogService bildirdik. ActionCatalogServis gerekli tüm condition’ları denetler ve eğer kullanıcının bu Action’ı kullanmaya yetkisi var ise fonksiyonu çağıaracaktır. OnChageProcess Action’ı içerisinde gelen bilgileri ekranda göstermekten başka bir şey yapmadık. Böylelikle çok uzun bir serinin sondan bir önce ki adımını tamamladık. Actionlar ile birlikte Smart Client Software Factory tarafından kullanılan Composite Application Block elemanlarının hepsini incelemiş olduk. Bir sonra ki makalede Visial Studio içerisinden SCSF nasıl kullanılır inceliyeceğiz.

9 Ocak 2008 Çarşamba

Smart Client Software Factory Arayüz İşlemleri

Shell - Module

Bu makaleye başlamadan önce arka alanda bizim için çalışan Injecton Pattern ve ObjectBuilder sihirbazını incelemenizi öneririm. Anlamamız gereken iki kavram vardır ‘Module’ ve ‘Shell’. Modüller bir birinden bağımsız çalışan içinde kob bloklarımız, kullanıcı ara yüzlerimiz, iş servislerimiz, ara yüz parçalarımız ve çeşitli bileşenlerimiz olan kendi başına ayrı uygulamalardır. Modül kendi başına çalışan ayrı bir .NET projesi gibi görülebilinir. Modüller kendi başlarına atomik bir işi yerine getirirler. İkinci kavramımız ‘Shell’ kavramıdır. Shell basitçe ‘ortak pencere’dir. Shell bizim modüllerimizi ve ara yüz elemanlarımızı taşır. Shell ve müdüllerin hiç biri bir diğerini referans etmez. Tanım olarak;

Module: Karmaşık arayüzler kullanan tek başına bir projedir

Shell: Karmaşık ara yüzler için bir sunucu(host) uygulamasıdır.

Basit bir uygulama ile bu iki kavramın nasıl çalıştığına bakalım. Sadece kırmızı bir form üreten projemiz olsun. Bu projeden tamamen bağımsız çalıştığında mavi bir form üreten ikinci bir projemiz olsun. Bunlar bizim modüllerimiz. Üçüncü bir projede ise bu iki modülü taşıyacak olan Shell uygulamamız olsun. Shell sadece bu iki formu görüntüleyecektir.

Burada zor olan her üç uygulamanın aynı Solution üzerinde çalışıyor olmasıdır. Biz her üç formunda açılışta gösterilmesini istiyoruz. Ortada hiçbir referans olmadığı için reflection ile tabii ki her üç formu gösterebiliriz. Fakat biz bu işi CAB bırakıyoruz.

Önce üç adet .NET projesi oluşturalım. Shell ve modüllerimiz Mavi, Kırmızı. Her üç uygulamaya da ObjectBuilder, CAB ve CABWinforms dll’lerini referanslarını ekleyelim. Tüm uygulamalarda ‘Build Directory ‘ alanını aynı konum ayarlayalım ki bir arada çalışabilsinler. Her bir uygulamayı çalıştırdığımızda kendi özel formlarını görürüz.

Şimdi Shell uygulamasına diğer uygulamalara ‘Container’ olma yeteneğini verelim. Bunun için Shell uygulamasını CAB uygulamasında türetmemiz ve Main fonksiyonu içerisinde üst sınıf olan CAB uygulamasını başlatmamız yeterlidir.

[cs]

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Microsoft.Practices.CompositeUI.WinForms;
using Microsoft.Practices.CompositeUI;
 
namespace Shell {
    public class Program :FormShellApplication {
        [STAThread]
        static void Main() {
            new Program().Run();
        }
    }
}

CAB uygulama sınıfı FormShellApplication dır. İki generic parametresi vardır. Giriş bölümde tartıştığımız Use-Case diyagramlarımızı yerine getiren WorkItem sınıfı, diğeri bizim uygulamamızda ara yüz işlemlerini yerine getiren ve ortak pencere olma özelliğini taşıyan Form1 formumuzdur. CAB Shell uygulaması bir xml dosya içerisinde kendisine çalıştırılacak modüllerin sunulmasını bekler. Modül listesini taşıyan dosya uygulamanın çalışma dizininde yer alan ‘ProfileCatolog.xml’ dosyasıdır. Bu dosya çok basit bir şekilde çalıştırılacak mödül listesini gösterir.

<?xml version="1.0" encoding="utf-8" ?>
<SolutionProfile xmlns="http://schemas.microsoft.com/pag/cab-profile">
  <Modules>
    <ModuleInfo AssemblyFile="KirmiziModul.exe" />
    <ModuleInfo AssemblyFile="MaviModul.exe" />
  </Modules>
</SolutionProfile>

Son bir işimiz daha kaldı. Uygulama programını CAB uygulama sınıfından türettiğimiz gibi modül programlarını da CAB ile uyumlu hale getirmeliyiz.

CAB uygulaması Run() fonksiyonu ile çalıştırıldığında CAB ProfileCatolog.xml dosyasını acar. Burada yer alan modüllerde CAB ModuleInit sınıfından türetilmiş bir sınıfı arar. Eğer ModülInit sınıfını bulursa ModuleInit.Load() fonksiyonunu çağırarak modülleri yükler.

[cs]

using Microsoft.Practices.CompositeUI;
 
namespace KirmiziModul {
    public class KirmiziModuleInit : ModuleInit{
        public override void Load() {
            base.Load();
            Form1 form = new Form1();
            form.Show();
        }
    }
}

ModuleInit içerisinde biz bu örnekte formlarımız gösteriyoruz. Shell uygulamamızı çalıştırdığımızda her üç formunda açıldığını görüyoruz.

Hepsi bu kadar böylelikle ilk CAB uygulamızı yazmış olduk.

CAB uygulamalarının görsel her bir parçası [SmartPart]’dır. SmartPart bir UserControl nesnesidir. Her hangi bir temel sınıftan türetilmeleri gerekmemektedir. Sadece [SmartPart] attribute ile işaretlenmeleri yeterlidir. Bu parçalar uygulama içerisinde dinamik olarak yerleşmektedir. Dizayn anında uygulama içerisinde [SmartPart] nesnelerini yerleşeceği alanlar Workspace olarak adlandırılır. Workspace nesneleri IWorkspace ara yüzünü uygulayan ve içerinde [SmartPart] özelliğine sahip nesneleri saklayan Container nesnelerdir.

Böylelikle uygulamanız için en esnek ara yüz tasarımlarını gerçekleştirebilirsiniz. Ayrıca IWorkspace arayüzünü uygulayarak kendi SmartPart Container kontrolü olan Workspace sınıflarını yazabilirsiniz. RootWorkItem Injection Pattern kullanarak oluşan tüm nesneleri takip edebilmektedir. Dolayısı ile sizin tasarım anında ara yüze yerleştirdiğiniz Workspace nesneleri oluşturulurken RootWorkItem.WorkSpaces koleksiyonu üzerine eklenecektir. Şimdi uygulama üzerinde işler nasıl dönüyor inceleyelim. İlk demo uygulamada kırmızı ve mavi adlı iki ayrı uygulamayı aynı proje ile açmıştık. Şimdi bu iki ayrı uygulamayı aynı ara yüz içerisinde gibi bir araya getirelim. Öncelikle Shell form üzerine bir tane DeckWorkspace kontrolü ekleyelim. Uygulama genelinde gerekli genel ara yüz işlemleri için bir Layout modülü oluşturalım. Daha sonra bu workspace içerinde uygulamanın asıl görünümünü oluşturacak bir ShellLayoutView ekleyelim.

Şekil 1 Shell.exe içinde ShellForm


Şekil 2 Layout.dll için ShellLayoutView

ShellLayoutView tüm uygulama için ortak olan ara yüz elemanlarını barındırmaktadır. Daha sonra uygulama içerisinden ortak ara yüz elemanlarına nasıl erişim sağlandığını inceleyeceğiz. ShellForm üzerinde ki LayoutWorkspace RootWorkItem.Workspaces içerisine kendiliğinden eklenecektir. Layout modül içerisinde ki ShellLayoutView ara yüzünü LayoutWorkspace içerisine ekleyelim.

[cs]

namespace Layout {
    public class Module : ModuleInit {
        private WorkItem rootWorkItem;
        [InjectionConstructor]
        public Module([ServiceDependency] WorkItem rootWorkItem) {
            this.rootWorkItem = rootWorkItem;
        }

        public override void Load() {
            base.Load();

            // layout view shellform üzerine ekle
            ShellLayoutView shellLayout = rootWorkItem.SmartParts.AddNew();
            rootWorkItem.Workspaces["LayoutWorkspace"].Show(shellLayout);
        }
    }
}

Şimdi her iki iş modülümüz içinde ki ara yüzleri ayrı pencereler içine yerleştirelim.

[cs]

namespace KirmiziModul {
    public class KirmiziModuleInit : ModuleInit{
        private WorkItem _workItem;
        [InjectionConstructor]
        public KirmiziModuleInit([ServiceDependency] WorkItem workItem) {
            _workItem = workItem;
        }
        public override void Load() {
            base.Load();
            // Form1 view shellformview üzerinde adı 
            // 'ContextWorkspace' olan alan üzerine ekle
            Form1 kirmiziView = _workItem.SmartParts.AddNew();
            _workItem.Workspaces["ContextWorkspace"].Show(kirmiziView);
        }
    }
}


namespace MaviModul {
    public class MaviModuleInit :ModuleInit {
                
        private WorkItem _workItem;
        [InjectionConstructor]
        public MaviModuleInit([ServiceDependency] WorkItem workItem) {
            _workItem = workItem;
        }
        public override void Load() {
            base.Load();
            // MaviModule.Form1 view shellformview üzerinde adı 
            // 'NavigatorWorkspace' olan alan üzerine ekle
            Form1 kirmiziView = _workItem.SmartParts.AddNew();
            _workItem.Workspaces["NavigatorWorkspace"].Show(kirmiziView);
        }
    }
}

Gördüğünüz ara yüzü her bir parçası farklı bir dll dosyası içerisindedir. Tamamen dinamik olarak oluşturulduk.