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.

8 Ocak 2008 Salı

Smart Client Software Factory MVP - Event Broker

Model-View-Presenter

Uygulamalar dinamik olarak yüklenen SmartPart ara yüzlerinden ve bu SmartPart ara yüzlerine taşıyıcılık yapan Workspace nesnelerinden ibaret değildir. Uygulamanın iş kuralları ile ara yüz işlemleri açık kodlama ve anlaşılabilirlik için tamamen bir birinden ayrılmalıdır. Her bir SmartPart ara yüzü sadece ara yüz işlemlerini içermelidir.

Her bir View sadece ara yüz işlemleri gerçekleştirmektedir. Presenter uygulanın iş kodlarının bulunduğu yerdir. View ile Presenter ortak bir Interface aracılığı ile haberleşir. Model ise uygulamanın kabul ettiği veri yapısıdır. Model sınıflar uygulamanın ‘Business Entity’ sınıflarıdır.

[cs]

[SmartPart]
    public partial class NavigatorView : UserControl, INavigatorView
    {
        ………
        private NavigatorViewPresenter _presenter;
        [CreateNew]
        public NavigatorViewPresenter Presenter {
            set {
                _presenter = value;
                _presenter.View = this;
                _presenter.OnViewReady();
            }
        }
        ……
    }
}

Örnek bir View ait kodları görmektesiniz. View oluşturulurken ObjectBuilder’a kendisine ait Presenter sınıfını oluşturmasını söylemektedir. Oluşturulan Presenter’e View olarak kendisini atamaktadır.

[cs]

public class NavigatorViewPresenter {        
        private INavigatorView _view;
        public INavigatorView View {
            get {
                return _view;
            }
            set {
                _view = value;
                OnViewSet();
            }
        }
        public void OnViewSet() {
        }

        public void OnViewReady() {………………..
        }
    }
}
}

SOA

Şimdi örnek uygulamamıza taskbar uygulamasına benzetmeye çalışalım ve MVP pattern iş başında inceleyelim. Öncelikle bize bilgisayar üzerinde ki tüm processleri ve processlere ait detayları verecek bir iş servisine ihtiyacımız var. Daha sonra mavi modül içerisinde tüm processleri listeyen bir ara yüze ve kırmızı modül içerisinde process detaylarını gösteren bir başka ara yüze ihtiyacımız var. Process bilgilerini sağlayan ProcessService’i yazalım:

[cs]

using System;
using System.Collections.Generic;
namespace Interface.Service {
    public interface IProcessService {
        // seçilen processe ait detay bilgilerini getir
        List EnumProcess(Interface.Service.ProcessInfo info);
        // tüm process bilgilerini getir
        List EnumProcesses();
    }
}
}

Evet uygulamamızın Model kısmını oluşturmuş olduk. ProcessInfo ve ProcessDetailInfo sınıfları bizim uygulamamız için Model sınıflarıdır. Projemize iki yeni dll daha eklemeliyiz. Birincisi tüm uygulama genelinde gerekli servislerin uygulandığı Library.dll ikincisi Library.dll içinde ki servislere ait arayüzlerin ve uygulamamızın ‘Model’ kısmını oluşturan sınıfların bulunduğu Interface.dll dir. Kırmızı ve Mavi modül ProcessService’in sadece tanımına yani Interface bilgilerine ihtiyaç duymaktadır.

Uygulama başlarken ProcessService RootWorkItem üzerine ekleyelim ki ProcessService tüm WorkItem nesneleri tarafından kullanılabilir olsun.

[cs]

namespace Library {
    public abstract class SmartClientApplication 
	: FormShellApplication 
        where TWorkItem : WorkItem, new()
        where TShell : Form{

        protected override void AddServices() {
            base.AddServices();
            // Uygulama açılırken ProcessService ilk çağrıldığı yerde 
            // create olacak şekilde register et
            RootWorkItem.Services.AddOnDemand();
        }
    }
}

Artık istediğimiz yerde ProcessService’e injection ile erişim sağlayabiliriz.

[cs]

namespace Library {
    public abstract class SmartClientApplication 
	: FormShellApplication 
        where TWorkItem : WorkItem, new()
        where TShell : Form{

        protected override void AddServices() {
            base.AddServices();
            // Uygulama açılırken ProcessService ilk çağrıldığı yerde 
            // create olacak şekilde register et
            RootWorkItem.Services.AddOnDemand();
        }
    }
}
Artık istediğimiz yerde ProcessService’e injection ile erişim sağlayabiliriz. 
public class NavigatorViewPresenter {
IProcessService _processService;

        [ServiceDependency(Type = typeof(IProcessService))]
      public IProcessService ProcessService {
          set {
                _processService = value;
}
}
      
private INavigatorView _view;
      public INavigatorView View {
          get {
                return _view;
}
set {
                _view = value;
                    OnViewSet();
}
}

public void OnViewSet() { }

public void OnViewReady() {
          List data = _processService.EnumProcesses();
View.DataSource = data;
}
}

public interface INavigatorViewController {
List DataSource {
          set;
}
}

Process listesini gösterecek olan Mavi modüle ait navigator view’dır. Bu view’a ait olan presenter içerisinde ProcessService’i yukarıda ki gibi aldık. Bilgisayarımıza ait process listesini bu servis ile oluşturduk. Daha sonra oluşan process listesini view’a ekranda göstermek üzere gönderdik. View üzerinde ki kodda oldukça basit gelen listeyi bağlamaktan başka bir görevi yok

[cs]

[SmartPart]
    public partial class NavigatorView : UserControl, INavigatorViewController
    {
        public NavigatorView()
        {
            InitializeComponent();
        }
        private NavigatorViewPresenter _presenter;
        [CreateNew]
        public NavigatorViewPresenter Presenter {
            set {
                _presenter = value;
                _presenter.View = this;
                _presenter.OnViewReady();
            }
        }
        
        public List DataSource {
            set {
                bindingSource1.DataSource = value;
            }
        }        
    }
}

Böylelikle bir demonun daha sonuna geldik. Uygulamamız artık bilgisayar üzerinde ki tüm precessleri listelemektedir. Bu uygulama ile her servis güdümlü yazılımın CAB içerisinde nasıl yapıldığını hem MVP pattern CAB içerisinde nasıl kodlandığı gördük.

Event Broker

View-Presenter arasında ki iletişimi incelemiştir olduk. Şimdi viewlar arasında ki iletişimi inceleyeceğiz. View’lar birbiri ile nasıl haberleşir? Uygulama genelinde oluşan bir olaya nasıl tepki verilir?

Uygulama içerisinde oluşan her hangi bir aktiviteyi duyurmak için bir Event publish edersiniz. Oluşan aktiviteden haber olmak içinse event’a subscript olursunuz. Demo

Yukarıda son şeklini gördüğümüz örnek uygulamamız üzerinden devam edelim. Kullanıcımız Process listesi üzerinden bir process detaylarını görmek için tıkladığında sağ tarafda ki pencere üzerinde process’e ait detay bilgilerini gösterelim. Önce liste üzerinde kullanıcın tıklamasını yakalayalım.

[cs]

[SmartPart]
    public partial class NavigatorView : UserControl, INavigatorViewController
    {
        ………………………
        private void bindingSource1_CurrentChanged(object sender, EventArgs e) {
            _presenter.ChangeProcess();
        }
    }
}

View içerisinde logic kod olmayacağı için View bu işi Presenter’a havale etmektedir. Presenter ProcessService’den seçili olan processe ait detayları alacak ve daha sonra bu olayı yayınlayacak.

[cs]

public class NavigatorViewPresenter {
………
internal void ChangeProcess() {
      List processDetail = _processService.EnumProcess(View.Current);
            OnNeedChangeProcess(processDetail);
}

[EventPublication("NeedChangeProcess", PublicationScope.Global)]
public event System.EventHandler>> NeedChangeProcess;
protected virtual void OnNeedChangeProcess(List data) {
if (NeedChangeProcess != null) {
          NeedChangeProcess(this, new EventArgs>(data));
           }        
}
    }
}

Oluşan uygulama olayı böylelikle tüm uygulamaya duyuruldu. Eğer her hangi bir WorkItem içerisinde oluşturulan nesnelerden her hangi birinde bu olayı bekleyen bir sınıf var ise bu şekilde haberdar edilmiş olmaktadır. NeedChangeProcess olayı mavi modül içerisinde ki NavigatorViewPresente sınıfı tarafında tüm uygulamaya duyuruldu. Kırmızı modül içerisinde ki ContextViewPresenter sınıfı bu olayı beklemektedir. Oluşan NeedChangeProcess olayı ile ContextView üzerinde process detayları gösterilmektedir.

[cs]

namespace KirmiziModul.View.ContextView {
    public class ContextViewPresenter {
       
…..
        [EventSubscription("NeedChangeProcess", ThreadOption.UserInterface)]
        public void OnNeedChangeProcess(object sender, EventArgs> eventArgs) {
            View.DataSource = eventArgs.Data;
        }
    }
}

[SmartPart]
public partial class ContextView : UserControl, IContextViewController
{
public List DataSource {
         set {
                bindingSource1.DataSource = value;
            }
        }
    }
}

Evet hepsi bu kadar! Artık uygulama genelinde oluşan olaylara uygulamanın nasıl tepki verdiğini görmüş olduk.


7 Ocak 2008 Pazartesi

Smart Client Software Factory Giriş

Microsoft Smart Client Software Factory (SCSF) microsoft pattern&practice(P&P) gurubunun hazırlamış olduğu tüm aplication block'ları entegre bir biçimde kullanan gene aynı gurup tarafından geliştirilen harika bir yazılımdır. Birbirine çok benzeyen WorkItem, Module, WorkSpace, Shell gibi kavramlardan dolayı ilk bakışta yazılımcılara karmaşık gelen bir yapısı vardır. Gerçektende dependency injection, command patterns, event brokers, model-view-presenter patterns gibi üst seviye kavramların anlaşılması son derece güçtür. Biz bu yazı dizisinde SCSF-CAB ile kullanılan kavramların hepsini ele alıp inceleyeceğiz. Aynı zamanda bu kavramların anlaşılması için Microsoft tarafından yayınlanan dokümanlar oldukça fazladır.

SCSF projesinin başlangıç noktası Composite Application Block (CAB)'dır. CAB aslında bizim masaüstü uygulamamızın temelli oluşturmaktadır. Diğer uygulama blokları CAB çevresinden CAB’ı destekleyen yapılardır.


 SCF-CAB Temel Mimarisi

CAB Nedir?

Composite UI Aplication Block Microsoft P&P grup tarafından karmaşık ve modüler Smart Client uygulamalar geliştirmek üzere kullanıma hazır olarak sunulan bir uygulama bloğudur. İki önemli yeteneği vardır:

  1. Birbirinden bağımsız olarak geliştirilen ve kullanılan bileşenler ile oluşturulan karmaşık ara yüzler dizayn eder. (Module)
  2. Yapılan ara yüz uygulamalarını alır ve bunları ortak bir pencere üzerinde entegre bir biçimde çalıştırır. (Shell)

CAB UseCase Driven Developing yaklaşımını temel alır. CAB mimarisinin öncelikli amacı bizim UseCase diyagramlarımızı gerçekleştirmektir.

Use case diyagramlarınızla 1:1 eşleşen WorkItem düzenleyebilirsiniz. WorkItem eklenen ara yüz işlemleri ve gerekli tüm parçalarla birlikte çalışan bir Use Case kontrolcüsüdür. Kodlama açısından baktığımızda ise WorkItem nesneleri Use Case işlemlerini gerçekleştirecek tüm alt parçalara sahiplik yapan bir üst nesnedir.

WorkItem

Bir senaryo üzerine düşünelim. Bir stok projesi için Back Office uygulamasını yazacaksınız. Use Case diyagramlarını çıkarttınız. Back Office – front Office çalışanı, stok yöneticisi, müşteri gibi çeşitli aktörleriniz olacaktır. Her bir aktörün yetki seviyelerine göre yerine getirebileceği stok isteği, müşteri tanımlama, kredi onaylama gibi birçok hareketleri olacaktır. Her bir aktör bir diğer aktöre ait hareketlerin bir kısmını da kullanacaktır. Mesela stok yöneticisi Back Office çalışana ait müşteri borçlandırma hareketini de işleyebilecektir.

Ortak kullanılan hareket gruplarını bir Sub-Use Case içine toplayalım. Geriye kalan her bir atomik hareket gruplarını da ayrı Sub-Use Case diyagramlarının içine toplayalım. Tüm bu Sub-Use Case diyagramlarını içeren bir Root-Use Case diyagramımız ve bu Root-Use Case içinde bir kısmı ortak bir kısmı sadece kendi aktörü ile çalışan Sub-Use Case diyagramlarımız olacaktır.

İşte size CAB. Bir önce ki kısımda her bir Use-Case ile 1:1 eşleşen ve görevi Use-Case diyagramını gerçekleştirmek olan WorkItem nesnelerini gördük. Seneryomuzda Sub-Use Case ve Root-Use Case diyagramları olması gerektiğini gördük. CAB içerisinde Root-Use Case diyagramını gerçekleştiren bir RootWorkItem vardır. Tüm Sub-Use Case işlerini gerçekleştiren WorkItem nesneleri bu RootWorkItem altında oluşturulurlar.

Şimdi böyle bir yazılımın ihtiyaçlarını düşünelim.

  1. RootWorkItem nesnesi WorkItem nesnelerine ihtiyaç duydukları parçaları sağlamadır. (Injection Pattern – Object Builder)
  2. RootWorkItem nesnesini içeren ortak bir uygulama sağlanmalıdır. Üstünde her biri sadece bir tek WorkItem içeren modüllerden oluşan esnek ve genişleyebilen uygulama yapısı sağlanmalıdır.(Shell – Module)
  3. Arayüz ile iş kodu birbirinden tamamen ayrılmalıdır.(Model-Viewer-Presenter Pattern)
  4. WorkItem nesneleri program kullanıcısıyla doğrudan etkileşime girebilmelidir. (Command Pattern)
  5. WorkItem nesneleri kullanıcı yetkisine göre iş kurallarını işletmelidir. (Action Pattern)
  6. WorkItem nesneleri gerekli durumlarda diğer WorkItem nesneleri ile konuşabilmelidir. (Event Broker)
  7. WorkItem nesneleri ortak kullanılan ToolBar-StatusBar gibi ara yüz elemanlarını erişebilmelidir. (Register-Unregister UI Element)
  8. Ara yüz geliştirirken sadece Visual Studio ile gelen componentlere bağlı kanılamayacağı için genişleyebilir bir mekanizma sağlanmalıdır.(CAB Extansions- Adapter- Factory Pattern)

Tabii bu ihtiyaçlar bizim Use Case güdümlü yazılım mimarimiz için özel ihtiyaçlardır. Birde tüm uygulamalar için ortak ihtiyaçlar vardır. (Application Blocks)

  1. Hata yönetimi
  2. Log
  3. Veri erişimi
  4. Güvenlik
  5. Şifreleme
  6. Veri doğrulama

Tüm bu uygulama bloklarının da kendi uygulamamız içerisinde kullanmamız gerekmektedir. Belki bu uygulama bloklarına kendi iş ihtiyaçlarımıza göre bir takım yeni özellikler eklemek veya yeni bir uygulama bloğu oluşturmak gerekebilir. (Application Block Software Factory)

Bu makalede SCSF-CAB konusuna giriş yaptık. CAB Use-Case güdümlü programlamayı nasıl yerine getirdiğine dair konuştuk. CAB uygulamasının ihtiyaçlarını gördük. Yukarıda ki sıraya göre bu yazılım ihtiyaçlarını CAB nasıl yerine getirmiş P&P serisinde anlatmaya çalışacağım. Bu seri makaleler içinde öğrenme yöntemimiz tümden gelim olacaktır.