Makale Özeti

Tüm uygulamanın ortak kullandığı Menü, toolbar gibi IU elemanların yönetimi, Command Pattern kullanıcı tetiklemeli olaylar ve fonksiyon bazlı yetkilendirme ile Action'ları inceliyoruz.

Makale

UIElement


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

Ö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.
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.
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<NavigatorView>();
_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.
public class NavigatorViewPresenter {
…….
[CommandHandler("Refresh")]
public void OnRefresh(object sender, EventArgs e) {
List<ProcessInfo> data = _processService.EnumProcesses();
      View.DataSource = data;
}
………………..
}
Evet hepsi bu kadar! 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.
Demo
namespace Interface.Service {
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.
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<IActionCatalogService>();
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.
public abstract class SmartClientApplication<TWorkItem, TShell> : FormShellApplication<WorkItem, TShell> 
{
protected override void AddServices() {
base.AddServices();
…….
RootWorkItem.Services.AddOnDemand<ActionCatalogService, IActionCatalogService>();
IActionCatalogService actionCatalog = RootWorkItem.Services.Get<IActionCatalogService>();
      actionCatalog.RegisterGeneralCondition(new SimpleActionCondition());
}
protected override void AddBuilderStrategies(Microsoft.Practices.ObjectBuilder.Builder builder) {
     base.AddBuilderStrategies(builder);
        builder.Strategies.AddNew<ActionStrategy>(
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.
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.
namespace Microsoft.Practices.CompositeUI.Services {
    public interface IAuthenticationService {
        void Authenticate();
    }
}

public class WinFormAuthenticationService : IAuthenticationService {        
…..
[EventPublication("UserAuthorize", PublicationScope.Global)]
public event System.EventHandler<EventArgs> 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.
public abstract class SmartClientApplication<TWorkItem, TShell> : FormShellApplication<WorkItem, TShell> {
………
protected override void AddServices() {
base.AddServices();
RootWorkItem.Services.Remove<IAuthenticationService>();
      RootWorkItem.Services.AddNew<WinFormAuthenticationService, IAuthenticationService>();

            …..
 }
}
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.
public class ContextViewPresenter {
…….

[EventSubscription("NeedChangeProcess", ThreadOption.UserInterface)]
public void OnNeedChangeProcess(object sender, EventArgs<List<ProcessDetailInfo>> 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>), "ProcessDetailInfo");
      List<ProcessDetailInfo> info = (List<ProcessDetailInfo>)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.