Schrittweises Entwickeln einer einfachen modularen Anwendungen mit dem “Composite Application Guidance

Schrittweises Entwickeln einer
einfachen modularen Anwendungen mit
dem “Composite Application Guidance
for WPF” Framework
Einführung
In diesem Dokument soll das schrittweise Erstellen einer modularen Anwendung mit dem
“Composite Application Guidance for WPF” Framework beschrieben werden. Dieses
Framework wurde unter dem Codenamen PRISM entwickelt. Dieser Codename wird der
Einfachheit halber im Dokument verwendet werden. Die Hauptseite des PRISM
Communityprojekte ist http://www.codeplex.com/CompositeWPF. Das PRISM Framework
ist unter Microsoft public license veröffentlicht. Aus meiner Sicht wichtige Aspekte dieser
Lizenzvereinbarung: das PRISM Framework kann kostenfrei genutzt werden und der unter
Verwendung von PRISM erstellte Code muss nicht offengelegt werden. Supportanfragen und
Featurerequest kann man nur auf der Codeplex Communityseite stellen, und können dort
von der Community beantwortet werden.
Die Motivation für dieses Dokument ist, dass viele Geschäftsanwendungen Anforderungen
haben, die mit dem PRISM Framework sehr gut abgedeckt werden können. Das Typische für
viele Geschäftsanwendungen ist, dass diese Anwendungen zur Abwicklung und
Durchführung von bestimmten geschäftlichen Abläufen entwickelt wurden. Diese
Anwendungen lassen sich häufig in verschiedene fachliche Module, wie z.B. „Human
Resources“, „Custom Relationship Management“, u.v.a.m. unterteilen.
Die Anforderungen an Modularität kann aus verschiedenen Gründen für den Entwurf einer
Anwendung gewünscht sein:
 Modularisierte Entwicklung für/durch fachliche Module
 Modularisierte Softwareverteilung (z.B. der „HR Arbeitsplatz“, der „CRM
Arbeitsplatz“, ... )
 Anwendungsmodule für den Zugriff auf verschiedene (Web)-Services bzw. BackendModule
 Modularisierte Updates
 U.v.a.m.
Diese Anforderungen für modularisierte Anwendungen unterstützt PRISM als integriertes
Basiskonzept und es soll in diesem Dokument gezeigt werden, wie einfach die Verwendung
von PRISM in Hinblick auf die Umsetzung dieser Anforderungen ist.
Die nächsten Seiten beschrieben also die Erstellung einer modularen Rahmenanwendung
mit PRISM, um die Grundfunktionen zu demonstrieren und die ersten Schritte zu einer
generischen modularen Anwendung zu demonstrieren.
Diese Anleitung soll nicht die Dokumentation zum PRISM Framework ersetzen. Es empfiehlt
sich einen kurzen Blick auf die PRISM Dokumenten zu werfen, die umfassend sehr viele
wichtige Aspekte von PRSIM beschreibt.
Dieses Dokument soll nur einfach nachzuvollziehender Wegweiser für die wichtigsten
Arbeitsschritte zum Anlegen einer einfachen modularen Anwendung sein.
Zu diesem Guide gibt es ein fertiges Demoprojekt, dass die komplette Lösung enthält.
Diese Anleitung wendet sich an Entwickler und technisch versierte Leser.
Technische Voraussetzungen


.NET Framework 3.5 (SP1)
Visual Studio (Express, Standard, …)
– (Anmerkung – bei Visual Studio Express gibt es den Projekttyp “WPF User Control
Library” nicht; aber eine „Class library“ mit den entsprechenden WPF Referenzen
kann natürlich einfach erzeugt werden.)
Umsetzungsschritte
Anwendungsshell
1. Bereitstellen des PRISM Frameworks und der Enterprise Library 4.0 (ETL optional)
Wenn man PRISM zur lokalen Entwicklung nutzen will, sollte man zuerst die Enterprise
Library 4.0 installieren.
Zwar ist das PRISM Framework auch ohne den ETL Download nutzbar, da aber PRISM auf
Funktionen der ETL 4.0 aufsetzt und wenn man Dokumentation und ggf. Sourcecode
nachschlagen möchte, ist die Installation der ETL empfehlenswert. Die Enterprise Library
bekommt man von 
http://www.codeplex.com/entlib/Release/ProjectReleases.aspx?ReleaseId=13498.
Danach muss man das PRISM Framework herunterladen 
http://www.microsoft.com/downloads/details.aspx?FamilyId=6DD3D0C1-D5B4-453BB827-98E162E1BD8D&displaylang=en.
Als Ergebnis des Downloads hat man ein Exe-File auf der lokalen Festplatte, das man
durch Ausführen in ein beliebiges Verzeichnis extrahieren kann. Hierzu sollte man ein
Verzeichnis wählen, in dem man auch bei der täglichen Arbeit Schreiben bzw. Verändern
darf, zum Beispiel um die Quickstarts auszuprobieren ....
Im Verzeichnis „<Extract Directory>Source\CAL“ befindet sich der Sourcecode des PRISM
Framework mit Unittests. Um das Framework zu kompilieren, muss man das File
„CompositeApplicationLibrary.sln“ öffnen. Nach dem Build findet man im Verzeichnis
„<Extract Directory>Source\CAL\Composite.UnityExtensions\bin\Debug“ die PRISM
Assemblies, die man zur Benutzung von PRISM benötigt. Die markierten Assemblies und
Files:
„Microsoft.Practices.Composite.*“,
„Microsoft.Practices.Composite.UnityExtensions.*“
und „Microsoft.Practices.Composite.Wpf.*“
sollte man in ein separates Verzeichnis kopieren. Dann kann man diese notwendigen
PRISM Assemblies binär in der eigenen Visual Studio Lösung referenzieren. Eine
Integration von PRISM als Sourcecode in eine eigene Visual Studio Lösung ist auch
möglich, aber den kompiliert man für jeden Test etc. Immer das PRISM Framework mit.
Abbildung 1: Kopieren der notwendigen PRISM Assemblies
2. Anlegen der Solution
In Visual Studio eine leere („blank“) Solution erstellen. File->New->Project hier dann
unter „Other Project Types“ den Eintrag „Visual Studio Solutions“ auswählen im großen
Fenster dann die „Blank Solution“ anklicken und den Namen der Solution eintragen. In
unserem Beispiel ist der Name der Solution PrismHowTo.
Danach müssen die eigentlichen Projekte angelegt werden. Dazu braucht man
mindestens 3 Projekte:
1.
Die Anwendungsshell = das WPF Projekt aus dem das Anwendungsfenster
gestartet wird
2.
Das Module 1 Projekt, um die Modularität zu demonstrieren
3.
Das Module 2 Projekt, um die Modularität zu demonstrieren
Im Hauptfenster unserer Anwendungsshell werden dann die fachlichen Module ihr UI
visualisieren.
Abbildung 2: Anlegen der Solution
3. Anlegen des Projektes für die Anwendungsshell
Das Hauptprojekt wird als WPF Application Projekt mit dem Namen „ApplicationShell“
angelegt. „File – Add New Project – Visual C# - Windows – WPF Application“.
Abbildung 3: 2. Anlegen des Projektes für die Anwendungsshell
4. Hinzufügen der notwendigen Referenzen zu den Prism und Enterprise Library
Assemblies.
Die drei zu referenzierenden Assemblies von Prism sind: Microsoft.Practices.Composite,
Microsoft.Practices.Composite.UnityExtensions und
Microsoft.Practices.Composite,Wpf. Aus der Enterprise Lib 4.0 sind zwei Assemblies zu
referenzieren: Microsoft.Practices.Unity und Microsoft.Practices.ObjectBuilder2.
Dazu auf das Project ApplicationShell rechts klicken und im Contextmenu „Add
Reference“ auswählen. Und dann über den Browse Button zu den Prism Libs navigieren
und hinzufügen. Dann die Enterprise Lib Assemblies aus dem GAC referenzieren.
Danach sollte das Projekt folgende Referenzen aufweisen.
Abbildung 4: Referenzen zu Prism und Enterprise Lib Assemblies
5. Sinnvolle Klassenstruktur im ApplicationShell Projekt erstellen
Um den Startvorgang der Anwendung und das Einstellen der Laufzeitumgebung nach den
eigenen Bedürfnissen zu gestalten, fügt man eine Klasse Program.cs in das Projekt ein. In
der Klasse Program wird es die Main Methode der Anwendung geben. Weiterhin
benötigt man eine neue Klasse Bootstrapper.cs, welche ebenfalls hinzugefügt wird, für
das Anpassen des PRISM Frameworks.
Mit rechter Maustaste auf das Project ApplicationShell klicken und dann mit
Add->Class die Program Klasse in Program.cs hinzufügen.
Die Klasse Program bekommt vorerst nur eine Methode:
[STAThread()]
public static int Main(string[] args)
{
return 0;
}
Danach kam man in den Properties des Projektes das Startobjekt auf die Program
Klasse einstellen. Rechtsklick auf das Project „ApplicationShell“ Properties anklicken.
Unter Application dann die Klasse ApplicatioShell.Program als Startup Klasse
auswählen.
Abbildung 5: Setzen des Startup Objektes
Auf die gleiche Art und Weise wie vorher jetzt die Klasse Bootsstrapper zum Projekt
hinzufügen. Die Bootstrapper Klasse leitet von der Klasse UnityBootstrapper aus dem PRISM
Framework ab. In dieser Klasse kann man die PRISM Funktionalität beeinflussen, um das
Framework nach seinen Wünschen zu initialisieren. In der Bootstrapper Klasse soll das
ShellWindow Klasse als Hautpfenster der Anwendung gesetzt werden. Weiterhin muss das
IModuleEnumerator Interface auf die Klasse ConfigurationModuleEnumerator setzen. Diese
Einstellung bewirkt, dass das App.Config File für das Laden der Module genutzt wird. Der
Code sieht jetzt so aus:
using System.Windows;
using Microsoft.Practices.Composite.Modularity;
using Microsoft.Practices.Composite.UnityExtensions;
namespace ApplicationShell
{
public class Bootstrapper : UnityBootstrapper
{
// Fields
protected ShellWindow mainWnd;
// Methods
protected override DependencyObject CreateShell()
{
this.mainWnd = new ShellWindow();
return this.mainWnd;
}
protected override IModuleEnumerator GetModuleEnumerator()
{
return new ConfigurationModuleEnumerator();
}
// Properties
public ShellWindow ApplicationShellWindow
{
get
{
return this.mainWnd;
}
}
}
}
Die Klasse ShellWindow wird jetzt noch nicht erkannt, da wir diese erst im XAML File
anlegen müssen.
Die Program Klasse hat als wichtigste Methode die Main Methode. Hier wird der
Anwendungsstart mit:
 Initialisierung der Fehlerbehandlung,
 Initialisierung der DefaultCulture,
 Initialisierung der Authentication Policy,
 Initialisierung des PRISM Framworks
 und dem Start des WPF Applicationobjektes
durchgeführt.
#define TRACE
using System;
using System.Diagnostics;
using System.Globalization;
namespace ApplicationShell
{
class Program
{
private static App applicationInstance;
private static Bootstrapper bootStrapper;
[STAThread()]
public static int Main(string[] args)
{
int iRetVal = 0;
try
{
// set the unhandled execption handler to find unhandled
exceptions
AppDomain.CurrentDomain.UnhandledException += new
UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
// set the prefered principal policy for your up domain
AppDomain.CurrentDomain.SetPrincipalPolicy(System.Security.Principal.Pri
ncipalPolicy.WindowsPrincipal);
// set the UI culture for your application
System.Threading.Thread.CurrentThread.CurrentCulture =
System.Threading.Thread.CurrentThread.CurrentUICulture = new
CultureInfo("en-US");
applicationInstance = new App();
// in RELEASE mode - capture unhandled exceptions
#if !DEBUG
applicationInstance.DispatcherUnhandledException += new
System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(applic
ationInstance_DispatcherUnhandledException);
#endif
// initialize the PRISM framework
bootStrapper = new Bootstrapper();
bootStrapper.Run();
// start the WPF application with the Shell window
generated in the bootstrapper
applicationInstance.Run(bootStrapper.ApplicationShellWindow);
}
catch (Exception ex)
{
Trace.WriteLine(ex.ToString());
iRetVal = -1;
}
return iRetVal;
}
static void
applicationInstance_DispatcherUnhandledException(object sender,
System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
Trace.WriteLine(e.Exception.ToString());
e.Handled = true;
}
static void CurrentDomain_UnhandledException(object sender,
UnhandledExceptionEventArgs e)
{
Trace.WriteLine(((Exception)e.ExceptionObject).ToString());
}
}
}
6. Visuelle Gestaltung des Anwendungshauptfensters
In dem Anwendungshauptfenster müssen jetzt explizit die Bereiche festgelegt werden, in
denen ein Modul visuelle Veränderungen vornehmen kann. Die typische visuelle
Veränderung, die ein Modul im Anwendungsfenster vornimmt, ist das Hinzufügen und
Entfernen von Controls. Zum Beispiel im Menu, Toolbar, Arbeitsbereich, ... .
Das PRISM Framework stellt für die Darstellungsbereiche „Regions“ eine
Verwaltungsklasse bereit, die den Zugriff der Module auf die Regionen erlaubt. Damit
diese Verwaltungsklasse im Hauptfenster die Bereiche erkennt muss man im WPF XAML
Code solche Vermerke eintragen:
<StatusBar DockPanel.Dock="Bottom"
prism:RegionManager.RegionName="MainStatusbar"
Name="MainStatusbar" Height="30" VerticalAlignment="Bottom">
Anhand dieser Vermerke können bei der Initialisierung des PRISM Frameworks durch den
Bootstrapper, im Hauptfenster die festgelegten Regions erkannt werden.
Das Hauptfenster dieses Beispiels hat ein Menu, einen Toolbar, einen Statusbar und
einen Arbeitsbereich. Im Arbeitsbereich sollen, die eigentliche „Dialoge“ zur
Durchführung der fachlichen Funktionen eingeblendet werden.
Zuerst wird die Datei Windows1.xaml im Solution Explorer in ShellWindow.xaml
umbenannt. Nach eintragen der Regions für diese Anwendung sieht die Datei
ShellWindow.xaml dann so aus:
<Window x:Class="ApplicationShell.ShellWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://www.codeplex.com/CompositeWPF"
Title="ShellWindow" Height="768" Width="1024">
<DockPanel Height="Auto" Width="Auto" LastChildFill="true">
<Menu DockPanel.Dock="Top" Height="30" Width="Auto"
Name="MainMenu" VerticalAlignment="Center"
HorizontalContentAlignment="Center">
<MenuItem Header="File" VerticalAlignment="Center"
HorizontalAlignment="Center" Margin="1">
<MenuItem Header="Exit" Click="MenuItem_Click" ></MenuItem>
</MenuItem>
<MenuItem Header="Module functions" Width="Auto"
prism:RegionManager.RegionName="ExtensionMenu"
VerticalAlignment="Center"
HorizontalAlignment="Center" Margin="1"></MenuItem>
<MenuItem Header="Help" VerticalAlignment="Center"
HorizontalAlignment="Center" Margin="1"></MenuItem>
</Menu>
<ToolBar DockPanel.Dock="Top"
prism:RegionManager.RegionName="MainToolbar" Name="MainToolbar" Height="35"
Width="Auto" VerticalAlignment="Top">
</ToolBar>
<StatusBar DockPanel.Dock="Bottom"
prism:RegionManager.RegionName="MainStatusbar" Name="MainStatusbar"
Height="30" VerticalAlignment="Bottom">
</StatusBar>
<ItemsControl Name="WorkArea"
prism:RegionManager.RegionName="WorkArea" Background="LightGray">
</ItemsControl>
</DockPanel>
</Window>
Um das Ganze jetzt vollständig zu machen müssen wir noch in der Datei
ShellWindow.xaml.cs einen Handler für den Click Event im Menu auf den Eintrag „Exit“
hinzufügen. Am einfachsten geht dies, wenn man im Xaml Source (Shellwindow.xaml) mit
der rechten Maustaste auf den String "MenuItem_Click" klickt und dann im Contextmenu
„Navigate To Event Handler“ anwählt. Im C# File muss dann auch noch „Window1“ durch
„ShellWindow“ ersetzt werden, so dass die Datei dann folgendermaßen aussieht:
using System.Windows;
namespace ApplicationShell
{
/// <summary>
/// Interaction logic for ShellWindow.xaml
/// </summary>
public partial class ShellWindow : Window
{
public ShellWindow()
{
InitializeComponent();
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
}
}
7. Konfiguration in der App.Config
Jetzt müssen wir noch eine Applikations Konfigurationsdatei zum Projekt hinzufügen.
Hierzu im Solution Explorer mit der rechten Maustaste auf ApplicationShell klicken und
dann über Add->New Item, dann im Dialog „Application Configuration File“ auswählen
und den Namen App.Config eingeben.
Abbildung 6. Hinzufügen der App.config Datei
In dieser App.Config können wir neben vielen anderen Einstellungen auch konfigurieren,
welche Module zum Anwendungsstart von PRISM geladen werden sollen. Dazu benötigt
die App.Config mindestens folgende Einträge:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="modules"
type="Microsoft.Practices.Composite.Modularity.ModulesConfigurationSecti
on, Microsoft.Practices.Composite"/>
</configSections>
<modules>
</modules>
</configuration>
Die im Bootstrapper festgelegte ConfigurationModuleEnumerator Klasse liest diese
Konfigurationseinträge aus.
Anlegen eines Modules
8. Anlegen des Projektes für ein Modul
Um ein fachliches Modul anzulegen, müssen wir in der Solution ein neues Projekt
hinzufügen. Wir benötigen ein neues Projekt, da wir ja die Fachlichkeit im Modul und die
Funktionen des Anwendungsrahmens explizit trennen wollen. „File – Add New Project –
Windows – WPF User Control Library“
Abbildung 7: Hinzufügen des Projektes für ein Modul
9. Setzen der notwendigen Referenzen
Durch den Projekt Typ „WPF User Control Library“ sind alle für WPF notwendigen
Referenzen schon im neu erzeugten Projekt gesetzt.
Um die für PRISM notwendigen Referenzen zu setzen, geht man genau wie im Abschnitt
„4. Hinzufügen der notwendigen Referenzen zu den Prism und Enterprise Library
Assemblies.“ beim Anlegen des ApplicationShell Projektes vor.
10. Anlegen der ModulInit Klasse
Während des Ladevorgangs eines Moduls ruft das PRISM Framework in einem Modul
eine bestimmte Initialisierungs-Methode einmalig auf. Das gibt dem Modul die
Möglichkeit, notwendige Erst-Initialisierungsschritte auszuführen, z.B. das Anlegen von
Menu- und Toolbareinträgen.
Zu Erzeugung dieser Klasse fügt man zum Modul-Projekt eine Klasse „ModulInit“ hinzu.
Diese Klasse muss das Interface IModule aus dem PRISM Framework implementieren
und public sein, damit sie von Moduleloader aus dem Hauptassembly (=
ApplicationShell) aufgerufen werden kann. Die Implementierung des Interfaces IModule
benötigt die Implementierung einer Methode „public void Initialize()“, die während der
Module Initialisierung Sequenz ausgeführt wird.
11. Der Constructor in der ModulInit Klasse
Durch den verwendeten Unity Applicationblock kann man sich bei der Initialisierung
eines Modules im Constructor vorhandene notwendige globale Funktionen/Variablen
übergeben lassen.
Ein typisches Beispiel für dieses Szenario ist es, sich den UnityContainer und den
RegionManager in Constructor des Modules zuweisen zu lassen. Dieser Code hierzu sieht
folgendermaßen aus:
public class ModuleInit : IModule
{
private readonly IUnityContainer unityCnt;
private readonly IRegionManager regionMgr;
public ModuleInit(IUnityContainer container,
IRegionManager regionManager)
{
unityCnt = container;
regionMgr = regionManager;
}
…
Wie schon erwähnt, wird beim Laden des Modules durch das Unity Framework hier die
richtige Zuweisung von Variablen ausgeführt; - via Dependency Injection. Damit stehen
im Module alle notwendigen Funktionen zur Verfügung, um z.B. die Regionen im
Anwendungshauptfenster mit eigenen Controls zu benutzen.
12. ModulInitialisierung
Man sollte sich die Logik, die während der Modulinitialisierung durchgeführt wird, genau
überlegen. Da ein Modul nicht als alleiniges Modul in einem Hauptfenster ausgeführt
wird, ist es empfehlenswert geteilte Darstellungsressourcen während der Initialisierung
nicht für sich allein zu beanspruchen. Wenn ein anderes, uns nicht bekanntes Modul
ebenso vorgeht, sind bestimmte UI Bereiche eventuell nicht sichtbar. Ein Beispiel wäre
hier in unserem Sample die „WorkArea“, das ist ein Darstellungsbereich, in dem
typischerweise nur das aktive Modul sichtbar ist und nicht mehrere Module gleichzeitig
sichtbar sein sollten.
Ein sinnvolles Vorgehen während der Modulinitialisierung ist es nur in zentralen
Steuerungselementen, wie Toolbars und Menus, Einträge zu machen, die es dem
Benutzer erlauben spezielle Modulfunktionen auf diesem Weg zu starten.
Der Code eines sinnvollen ModulInit könnte also so aussehen:
using
using
using
using
using
using
System.Diagnostics;
System.Windows;
System.Windows.Controls;
Microsoft.Practices.Composite.Modularity;
Microsoft.Practices.Composite.Regions;
Microsoft.Practices.Unity;
using SharedFuncs.Extensions;
namespace ModuleCRM
{
public class ModuleInit : IModule
{
// central controls to trigger business functionality inside the moduel
private MenuItem mniStartSearch;
private Button btnStartSearch;
// variables for usefull PRISM/UNITY framework classes
private readonly IUnityContainer unityCnt;
private readonly IRegionManager regionMgr;
// the constructor which assigns the necessary framework classes
public ModuleInit(IUnityContainer container, IRegionManager
regionManager)
{
unityCnt = container;
regionMgr = regionManager;
}
#region IModule Members
// the module initialization sequence
public void Initialize()
{
Trace.WriteLine("ModuleCRM.ModuleInit.Initialize() ...");
#region init ui
// create a menu item to call the "Start search handler"
IRegion r = regionMgr.Regions["ExtensionMenu"];
if (r != null)
{
mniStartSearch = new MenuItem() { Header = "Start search
customer" };
mniStartSearch.Click += StartSearchEvtHdl;
r.Add(mniStartSearch);
}
// create a toolbar button to call the "Start search handler"
r = regionMgr.Regions["MainToolbar"];
if (r != null)
{
btnStartSearch = new Button() { Content = "Start search
customer" };
btnStartSearch.Click += StartSearchEvtHdl;
r.Add(btnStartSearch);
}
#endregion init ui
}
#endregion IModule Members
// the "Start search handler" creates an search UI with module
specific functionality
// and shows it in the Workarea
private void StartSearchEvtHdl(object sender, RoutedEventArgs e)
{
IRegion r = regionMgr.Regions["WorkArea"];
if (r != null)
{
r.RemoveAllOtherControlsFromRegion();
VwCrmSearch vw = new VwCrmSearch();
r.Add(vw);
}
}
}
}
Die Modulinitialisierung ist ggf. auch ein geeigneter Zeitpunkt, um
Berechtigungsüberprüfungen durchzuführen. Im Module selbst weiß man, welche
Fachlichkeit geladen werden sollte und könnte hiererstmalig die Berechtigung
überprüfen. Z.B. darf User XYX das Module HR benutzen? Bei den einzelnen fachlichen
Methoden eines Modules müssen natürlich noch granularere Berechtigungsprüfungen
stattfinden.
13. Module UI
Es gibt verschiedene Weg aus einem Module in den Regionen des Hauptfensters sichtbar
zu werden. Die einfachste Variante ist es, Basiscontrols aus WPF zu verwenden. Wenn
man eine komplexere fachlichere Logik, zum Beispiel für eine Suchemaske mit
Ergebnisanzeige, darstellen muss, empfiehlt es sich WPF User Controls zu verwenden. Ein
WPF User Control kann in den Regionen des Hauptfensters dargestellt werden und
bieten während der Anwendungsentwicklung WYSWIG Designer Unterstützung im Visual
Studio.
Abbildung 8: Hinzufügen eines WPF User Controls zum Modulprojekt
Hier der Xaml Code des User Controls, in unserem Fall nur ein Button und ein Texteingabe
feld:
<UserControl x:Class="ModuleCRM.VwCrmSearch"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="768" Width="1024">
<Grid>
<Button Height="22" HorizontalAlignment="Left" Margin="32,30,0,0"
Name="btnStartSearch" VerticalAlignment="Top" Width="159"
Click="btnStartSearch_Click">Start Search Customer</Button>
<TextBox Height="20" Margin="228,30,238,0" Name="tbxSearchString"
VerticalAlignment="Top" />
</Grid>
</UserControl>
Über den Rechtsklick auf „btnStart Search_Click“ und Navigate toHandler wir wiederum ein
EventHandler für den Click Event erzeugt. In VwCrmSearch.xaml.cs:
using System.Windows;
using System.Windows.Controls;
namespace ModuleCRM
{
/// <summary>
/// Interaction logic for VwCrmSearch.xaml
/// </summary>
public partial class VwCrmSearch : UserControl
{
public VwCrmSearch()
{
InitializeComponent();
}
private void btnStartSearch_Click(object sender, RoutedEventArgs e)
{
}
}
}
14. Modulübergreifende Funktionen
In der Methode „StartSearchEvtHdl” wird eine Funktion
„RemoveAllOtherControlsFromRegion” verwendet, die nicht Bestandteil des PRISM
Frameworks ist. Diese Methode stellt bei der Anzeige eines WPF UserControls sicher, dass
alle anderen Controls aus der „WorkArea“ Region entfernt werden.
Diese Funktionalität ist so allgemein, dass sie modulübergreifend verwendet werden kann.
Deswegen kann man ein extra Assembly erzeugen („File – Add – New Project – Windows –
WPF User Control Library “ Als Namen „SharedFuncs.cs“ eintragen), dass für alle UI Klassen
gemeinsame Funktionen enthält. Mit dem Anlegen des Assemblies als „WPF User Control
Library“ sind alle für WPF notwendigen Referenzen gesetzt. Jetzt muss man nur noch die für
PRISM notwendigen Referenzen setzen – siehe Abschnitt „Error! Reference source not
found.“.
In dem Module kann man jetzt eine Klasse „IRegionExtensions“ anlegen, mit der das IRegion
Interface um sinnvolle Extensionsmethoden erweitert wird. Der Code dazu sieht so aus:
using System.Windows;
using System.Windows.Controls;
using Microsoft.Practices.Composite.Regions;
namespace SharedFuncs.Extensions
{
public static class IRegionExtension
{
public static void HideAllOtherControlsInRegion(this IRegion r)
{
if (r != null)
{
IEnumerator<object> o = r.ActiveViews.GetEnumerator();
while (o.MoveNext())
{
if (o.Current is System.Windows.Controls.Control)
{
((Control)o.Current).Visibility =
Visibility.Collapsed;
}
}
}
}
public static void RemoveAllOtherControlsFromRegion(this IRegion r)
{
if (r != null)
{
List<Control> lstControls = new List<Control>();
IEnumerator<object> o = r.ActiveViews.GetEnumerator();
while (o.MoveNext())
{
if (o.Current is System.Windows.Controls.Control)
{
lstControls.Add(((Control)o.Current));
}
}
foreach (Control c in lstControls)
{
r.Remove(c);
}
}
}
}
}
15. Module Deployment
Das Projekt kompiliert das erzeugte Module nun schon, die Moduleinitialisierung ist
vorbereitet, die Registierung in Menu und Toolbar sind vorgesehen und es könnte auf
Benutzereingaben spezielle Funktionen ausgeführt werden.
Was fehlt noch??? - Nun, die ApplicationShell muss das neue Modul laden, damit die
Funktionalität ausführbar ist. Dazu muss man in der App.Config folgende Einträge haben:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="modules"
type="Microsoft.Practices.Composite.Modularity.ModulesConfigurationSection,
Microsoft.Practices.Composite"/>
</configSections>
<modules>
<module assemblyFile="ModuleCRM.dll" moduleType="ModuleCRM.ModuleInit"
moduleName="Module Customer Relationship Management"/>
</modules>
<system.diagnostics>
<trace autoflush ="true"></trace>
</system.diagnostics>
</configuration>
Dieser Eintrag in der Konfigurationsdatei wird von ConfigurationModuleLoader ausgewertet
und der Ladevorgang und die Initialiiserung für das Module ausgeführt.
Sehr wichtig ist der nächste Schritte  Das Assembly des neuen Modules muss in das
Directory des ApplicationShell (oder ein Subdirectory) kopiert werden, damit der Loader das
Assemblyfile findet.
Diesen Kopiervorgang kann man automatisieren, indem man auf das Post-Build Ereignis des
Modules diesen Eintrag setzt:
xcopy "$(TargetDir)*.*" "$(SolutionDir)ApplicationShell\$(OutDir)" /S /Y
Das Post-Build Ereignis kann man in den Properties des Projektes im Tab “Build Events”
setzen.
Abbildung 9: Konfigurieren des Post Build Ereignis für automatisches Kopieren
16. Die Ausgabe der Bootstrapper Klasse im Output Windows bzw. Debug Console
In dieser Ausgabe kann man gut nachverfolgen, welche Module geladen wurden usw.
ApplicationShell.vshost.exe Information: 0 : Creating Unity container
'ApplicationShell.vshost.exe' (Managed): Loaded
'C:\martinv\scratch\VS2008\PrismHowTo\ApplicationShell\bin\Debug\Microso
ft.Practices.ObjectBuilder2.dll'
ApplicationShell.vshost.exe Information: 0 : Configuring container
ApplicationShell.vshost.exe Information: 0 : Configuring region adapters
ApplicationShell.vshost.exe Information: 0 : Creating shell
'ApplicationShell.vshost.exe' (Managed): Loaded
'C:\Windows\assembly\GAC_MSIL\PresentationFramework.Aero\3.0.0.0__31bf38
56ad364e35\PresentationFramework.Aero.dll', Skipped loading symbols.
Module is optimized and the debugger option 'Just My Code' is enabled.
ApplicationShell.vshost.exe Information: 0 : Initializing modules
'ApplicationShell.vshost.exe' (Managed): Loaded
'C:\martinv\scratch\VS2008\PrismHowTo\ApplicationShell\bin\Debug\ModuleC
RM.dll', Symbols loaded.
ModuleCRM.ModuleInit.Initialize() ...
ApplicationShell.vshost.exe Information: 0 : Bootstrapper sequence
completed
17. Hinzufügen weiterer Module
Auf diese Weise kann man nun beliebige andere Module erzeugen und in der
ApplicationShell benutzbar machen.
Zusammenfassung & nächste Schritte
Mit diesen einfachen Umsetzungsschritten kann man eine generische modulare Anwendung
schreiben.
Sollte man die Anwendung später ausbauen, werden schnell weitere technische
Anforderungen hinzukommen. Um die gewonnen Modularität beizubehalten sollte man sich
mit:
 dem Konzept der WPFCommand Infrastructure und den Delegate/Composite
Commands im Prism  für globale, entkoppelte Kommandos zwischen der WPF
Bedienoberfläche/-Elementen und den Modulen
 dem Eventkonzept in PRISM für die fachliche, entkoppelte Kommunikation zwischen
Modulen
vertraut machen.
Eine architekturelle, konzeptionelle Frage, die PRISM explizit nicht mit dem Framework
beantwortet, ist die Frage nach der logischen Organisation des UI Codes. Zum Beispiel, setzt
man Model-View-Controller Pattern oder Model-View-Presenter Pattern in den Modulen
ein.
Um die Codestruktur in den fachlichen Modulen zu organisieren, ist es zwingend empfohlen
vor dem Start eines echten Projektes mit PRISM hier architekturellen Vorgaben zu entwerfen
und festzulegen.
Links
Beispiel Code 
http://code.msdn.microsoft.com/Project/Download/FileDownload.aspx?ProjectName=Prism
HowTo&DownloadId=3278
PRISM Community Seite auf Codeplex  http://www.codeplex.com/CompositeWPF
Enterpise Library Community Seite auf Codeplex  http://www.codeplex.com/entlib
WPF Windows Presentation Foundation  http://msdn.microsoft.com/enus/netframework/aa663326.aspx
Windows Client Informationen  http://windowsclient.net/