Entwicklung einer erweiterbaren, serviceorientierten ... · 1 Einleitung 1.1 Unternehmensprofil Das...

65
Fachbereich Elektrotechnik und Informatik Bachelorarbeit Entwicklung einer erweiterbaren, serviceorientierten Anwendung unter .NET Denis Mazuev Betreuer/Prüfer Prof. Dr. rer. nat. Nikolaus Wulff

Transcript of Entwicklung einer erweiterbaren, serviceorientierten ... · 1 Einleitung 1.1 Unternehmensprofil Das...

Fachbereich Elektrotechnik und Informatik

Bachelorarbeit

Entwicklung einer erweiterbaren, serviceorientierten Anwendung unter .NET

Denis Mazuev

Betreuer/Prüfer Prof. Dr. rer. nat. Nikolaus Wulff

II

Eidesstattliche Erklärung

Hiermit versichere ich die vorliegende Arbeit selbstständig verfasst und unter

ausschließlicher Verwendung der angegebenen Quellen und Hilfsmittel erstellt zu

haben.

Münster, den 29. August 2012

_________________________

Denis Mazuev

III

Danksagung

Mein erster Dank gilt meinen Eltern, meinem Bruder und meiner Frau, auf die ich

mich immer verlassen kann und die mich im Laufe der Erstellung dieser Arbeit

motiviert und unterstützt haben.

Des Weiteren bedanke ich mich ganz herzlich bei Herrn Prof. Dr. Nikolaus Wulff für

seine Unterstützung und Betreuung meiner Arbeit.

Weiterhin geht mein Dank an Alexander Serowy, meinen Betreuer in der Firma Betex,

der mir mit fachlicher Kritik zur Seite stand.

Darüber hinaus bedanke ich mich bei allen anderen Personen, die mich bei der

Anfertigung dieser Arbeit in unterschiedlicher Weise unterstützt haben.

IV

Inhaltsverzeichnis

1 EINLEITUNG ............................................................................................................................................. 1

1.1 UNTERNEHMENSPROFIL ................................................................................................................ 1 1.2 HINTERGRUND UND MOTIVATION .................................................................................................. 1 1.3 ZIELSETZUNG ............................................................................................................................... 1 1.4 GLIEDERUNG DER ARBEIT .............................................................................................................. 2

2 GRUNDLAGEN .......................................................................................................................................... 3

2.1 C# ............................................................................................................................................. 3 2.1.1 Eigenschaftsmethoden .................................................................................................... 3 2.1.2 Partielle Klassen ............................................................................................................... 4 2.1.3 Language Integrated Query ............................................................................................ 4

2.2 ENTITY FRAMEWORK .................................................................................................................... 5 2.2.1 Vorgehensweisen ............................................................................................................. 5 2.2.2 Abfragen und Kontext ...................................................................................................... 6

2.3 WINDOWS COMMUNICATION FOUNDATION .................................................................................... 7 2.3.1 Vertrag über ein Interface ............................................................................................... 7 2.3.2 Datenverträge ................................................................................................................. 8

2.4 MANAGED EXTENSIBILITY FRAMEWORK ........................................................................................... 9 2.4.1 Importe und Exporte ........................................................................................................ 9 2.4.2 Kompositionscontainer und Kataloge............................................................................ 10

2.5 WINDOWS PRESENTATION FOUNDATION ....................................................................................... 11 2.5.1 Geschichtliche Entwicklung der GUI-Frameworks für Windows.................................... 11 2.5.2 Wesentliche Merkmale .................................................................................................. 12 2.5.3 XAML ............................................................................................................................. 12

2.6 DAS MODEL-VIEW-VIEWMODEL-PATTERN .................................................................................... 13 2.6.1 Konzept .......................................................................................................................... 13 2.6.2 Verknüpfen der View mit ViewModel ............................................................................ 15 2.6.3 Data Binding und Commands ........................................................................................ 16 2.6.4 Die INotifyPropertyChanged-Schnittstelle ..................................................................... 17

2.7 SERVER..................................................................................................................................... 18 2.7.1 Active Directory ............................................................................................................. 18 2.7.2 Internet Information Services ........................................................................................ 20

3 ANFORDERUNGEN ................................................................................................................................. 21

3.1 ANALYSE ................................................................................................................................... 21 3.2 GROBKONZEPT .......................................................................................................................... 23

4 ENTWURF .............................................................................................................................................. 24

4.1 PLUG-IN-ARCHITEKTUR ............................................................................................................... 24 4.1.1 Laufzeitumgebung und die Module ............................................................................... 24 4.1.2 Projektstruktur und der Vertrag .................................................................................... 25 4.1.3 Benutzeroberfläche der Laufzeitumgebung .................................................................. 26

4.2 SERVICE-ARCHITEKTUR ............................................................................................................... 27 4.3 DATENHALTUNG IM CLIENT ......................................................................................................... 28 4.4 GESAMTARCHITEKTUR ................................................................................................................ 29

5 IMPLEMENTIERUNG ............................................................................................................................... 30

5.1 IMPLEMENTIERUNG DER SERVICESCHICHT ...................................................................................... 30 5.1.1 Vertrag ........................................................................................................................... 30

V

5.1.2 Zusammenspiel mit dem Entity Framework .................................................................. 31 5.1.3 Kommunikation mit dem Service ................................................................................... 36 5.1.4 Hosting........................................................................................................................... 41

5.2 GRUNDGERÜST FÜR MVVM ........................................................................................................ 42 5.2.1 ViewModelBase ............................................................................................................. 42 5.2.2 WorkspaceViewModel ................................................................................................... 44

5.3 UMSETZUNG DES PLUG-IN-KONZEPTS ........................................................................................... 45 5.3.1 Definieren des Vertrags ................................................................................................. 45 5.3.2 Implementierung des Vertrags ...................................................................................... 46 5.3.3 ModuleManager ............................................................................................................ 47 5.3.4 Bereitstellung der Module von der Laufzeitumgebung ................................................. 49

5.4 AUTHENTIFIZIERUNG .................................................................................................................. 53 5.5 DATENZUGRIFF IM CLIENT ........................................................................................................... 55

5.5.1 Repository ...................................................................................................................... 55 5.5.2 RepositoryManager ....................................................................................................... 56

6 FAZIT UND AUSBLICK ............................................................................................................................. 57 7 LISTINGVERZEICHNIS .............................................................................................................................. 58 8 ABBILDUNGSVERZEICHNIS ..................................................................................................................... 59 9 LITERATURVERZEICHNIS ......................................................................................................................... 60

1

1 Einleitung

1.1 Unternehmensprofil

Das Unternehmen Betex IT-Consulting & Solutions wurde im Jahr 2001 gegründet und

bietet ihren Geschäftskunden in ganz Deutschland Beratung und Unterstützung rund

um die IT an, von der Softwareentwicklung über Einrichtung von

Infrastrukturlösungen bis hin zu Telekommunikationslösungen.

In Bezug auf die Softwareentwicklung, liegt der Fokus der Firma auf den

Programmiersprachen PHP, Java und insbesondere .NET.

Betex hat ihren Standort in Münster und beschäftigt zurzeit 7 Mitarbeiter (Stand:

August 2012).

1.2 Hintergrund und Motivation

Bei den Leistungsnehmern des Unternehmens Betex, die ein Softwareprojekt in

Auftrag geben, ist ein steigendes Interesse an erweiterbaren Anwendungen zu

erkennen.

Aus der Sicht des Kunden lassen sich diese durch das Plug-In-Konzept leicht und

gezielt aktualisieren. Auch bei der Entwicklung von solchen Anwendungen ergeben

sich durch die modulare Struktur des Projekts Vorteile. Zu diesen zählen eine bessere

Übersichtlichkeit und Trennung.

1.3 Zielsetzung

Diese Arbeit beschäftigt sich mit der Erstellung einer Grundlage für erweiterbare

Anwendungen unter .NET, die sich leicht an unterschiedliche Anforderungen

anpassen lässt.

Basierend auf dieser Grundlage, wird die interne Zeiterfassung der Firma in Form von

mehreren Plug-Ins für die Anwendung realisiert, um diese unter realen Bedingungen

zu testen.

2

1.4 Gliederung der Arbeit

Im Folgenden wird im Kapitel Grundlagen dem Leser zunächst das notwendige

Grundwissen für das Verständnis dieser Arbeit vermittelt. Daraufhin folgt die

Beschreibung und Analyse der an das Projekt gestellten Anforderungen, worauf

basiert, die Anwendung anschließend entworfen wird. Das Kapitel Implementation

widmet sich der Umsetzung der Problemstellung. Abschließend wird im letzten

Kapitel ein Fazit gezogen und Ausblick auf die mögliche Zukunft des Projekts gegeben.

3

2 Grundlagen

2.1 C#

C# ist eine von Microsoft entwickelte Sprache für die .NET Common Language

Runtime (CLR). Im Folgenden werden Bestandteile der Sprache erläutert, die für das

Projekt eine wichtige Rolle spielen.

2.1.1 Eigenschaftsmethoden

In der objektorientierten Programmierung ist es üblich Zugriffsfunktionen, die s.g.

getter und setter, einzusetzen, um eine Eigenschaft eines Objekts abzufragen oder zu

ändern. Hinter dem Zugriff kann also eine Logik hinterlegt werden und somit werden

die Implementierungsdetails des Objekts verborgen (Black-Box-Prinzip).

Die Eigenschaftsmethoden in C#, auch genannt Properties/Eigenschaften, fassen die

Subroutinen für den Zugriff mit jeweils einem eigenen Anweisungsblock in einem

Container zusammen. Bei der Auswertung der Eigenschaft wird dann der get-Block

ausgeführt und bei einer Zuweisung entsprechend der set-Block. [1]

Anhand des Beispiels in Listing 2.1 ist zu erkennen, wie mithilfe einer Property der

Zugriff auf private Felder gesteuert werden kann. Die Verwendung von

Eigenschaftsmethoden macht den Code übersichtlicher, außerdem spielen sie eine

entscheidende Rolle in Verbindung mit Windows Presentation Foundation (s. 2.6.3).

private double seconds; public double Hours {

get { return seconds / 3600; } set { seconds = value * 3600; }

}

Listing 2.1 – Verwendung einer Eigenschaftsmethode

4

2.1.2 Partielle Klassen

Mithilfe von partiellen Klassen in .NET lassen sich die Klassendefinitionen über

mehrere Sourcedateien verteilen. Dadurch können mehrere Entwickler gleichzeitig an

der gleichen Klasse arbeiten. Auch lässt sich damit automatisch generierter Code sehr

leicht erweitern und sauber vom Code trennen, den der Entwickler schreibt

Bei der Verwendung von partiellen Typen muss lediglich eine Einschränkung beachtet

werden: Alle Klassenfragmente müssen sich in derselben Anwendung befinden.

Partielle Klassen werden durch den Modifizierer partial gekennzeichnet, der vor alle

Teildefinitionen gesetzt wird.

// in der Quellcodedatei 'Circle1.cs' partial class Circle { ... } // in der Quellcodedatei 'Circle2.cs' partial class Circle { ... }

Listing 2.2 – Beispiel: Verwendung von partiellen Klassen

2.1.3 Language Integrated Query

Language Integrated Query (LINQ) stellt eine Sprachergänzung von .NET dar und

wurde mit der Version 3.5 des Frameworks eingeführt, um den Zugriff auf die Daten

zu vereinheitlichen und zu vereinfachen.

Mithilfe einer LINQ Abfrage in Listing 2.3 wird beispielsweise eine Liste von Personen

zurückgegeben, die über 30 Jahre alt sind. Listing 2.4 stellt eine äquivalente Abfrage

ohne LINQ dar. Es ist zu erkennen, dass mithilfe von LINQ der Datenzugriff sehr

kompakt erfolgt, was die Lesbarkeit des Codes erhöht.

var pers = personen.Where(p => p.Alter > 30);

Listing 2.3 – Beispiel: Datenzugriff mithilfe von LINQ

var pers = new List<Person>(); foreach (var p in personen)

if (p.Alter > 30) pers.Add(p);

Listing 2.4 – Beispiel: Datenzugriff ohne LINQ

5

2.2 Entity Framework

Die meisten Anwendungen heutzutage müssen die in Objekten gespeicherten Daten

dauerhaft in Datenbanken sichern. Da die Datenbanken vorherrschend relational sind

und sie ihre Datenstrukturen entsprechend anders abbilden als Objektmodelle,

werden immer öfter objektrelationale Mapper (ORM) als eine zusätzliche Schicht

zwischen der Anwendung und der Datenbank eingesetzt, die sich um die Abbildung

von Objekten auf die Tabellenstruktur kümmert. [2]

Abb. 2.1 - Einsetzen eines O/R-Mappers

Entity Framework (EF) ist der Standard-ORM unter .NET und Teil des Frameworks.

2.2.1 Vorgehensweisen

Bei der Verwendung von EF gibt es folgende Vorgehensweisen:

Model-First:

Die Objekte, die s.g. Entitys, mit ihren Beziehungen werden zunächst in Visual Studio

modelliert und anschließend auf dieser Basis die entsprechende Tabellenstruktur für

die Datenbank generiert. Dieser Ansatz wird verwendet, wenn die Tabellenstruktur

zur Entwicklungszeit noch nicht existiert.

Database-First:

Das Objektmodell wird auf Basis einer bestehenden Datenbank von Visual Studio

automatisch generiert.

Client ORM DB

6

2.2.2 Abfragen und Kontext

Für die Kommunikation mit der Datenbank wird von Entity Framework automatisch

eine Proxy-Klasse, der s.g. Kontext, generiert. Der Kontext stellt Funktionen bereit,

mit denen die Entitätsdaten als Objekte abgefragt und bearbeitet werden können. [3]

In Listing 2.5 wird anhand eines Beispiels demonstriert, wie die Kommunikation mit

der Datenbank über den Kontext erfolgt. Dabei wird zunächst überprüft, ob ein

Mitarbeiter mit dem gegebenen Namen bereits in der Datenbank existiert. Ist dies

nicht der Fall, so wird den Mitarbeitern ein neuer Eintrag vom Typ EmployeeEntity

hinzugefügt und anschließend die Änderungen, durch den Aufruf der Methode

SaveChanges() an dem Kontext, gespeichert.

var c = new ObjectContext("..."); String name = "Koch"; if (c.Employees.FirstOrDefault(o => o.Name.Equals(name)) == null) {

c.Employees.AddObject(new EmployeeEntity { Name = name }); c.SaveChanges();

}

Listing 2.5 – Umgang mit dem Kontext

7

2.3 Windows Communication Foundation

Die Windows Communication Foundation (WCF) ist eine dienstorientierte

Kommunikationsplattform für verteilte Anwendungen in Microsoft Windows und

wurde mit .NET 3.0 eingeführt. Es führt viele Netzwerkfunktionen zusammen, um sie

den Entwicklern solcher Anwendungen standardisiert zur Verfügung zu stellen.

Hauptsächlich wird WCF bei der Entwicklung von Service-orientierten Architekturen

verwendet. [4]

Das Konzept des Endpunktes wird bei WCF durch eine Trennung in Address, Binding

und Contract abstrahiert (ABC Prinzip). [4]

Address (Adresse) beschreibt den Ort des Dienstes durch ein URI

Binding (Anbindung) definiert die Art der Kommunikation, unter anderem

durch die Angabe der Kodierung und des zu verwendenden Protokolls

Contract (Vertrag) stellt den Vertrag zwischen Service-Verwender und

Anbieter

2.3.1 Vertrag über ein Interface

Um eine Kommunikation zwischen dem WCF-Service und einem Client zu

ermöglichen, wird zwischen den beiden Seiten ein Vertrag in Form einer Schnittstelle

definiert. Diese beschreibt die Funktionen, die der Service zur Verfügung stellt.

Mithilfe von diesen Informationen wird vom WCF eine entsprechende Klasse

generiert, die im Client für den Zugriff auf den Service verwendet wird.

Wird unter Visual Studio ein neues WCF-Projekt angelegt, so befinden sich in diesem

bereits eine Schnittstelle und deren Implementierung. Die Schnittstelle ist dabei mit

einem ServiceContract-Attribut ausgestattet und wird dadurch von WCF als

Kommunikationsvertrag behandelt. Damit von der Schnittstelle definierte Methoden

automatisch Teil des Vertrags werden, müssen diese ebenfalls mit einem

entsprechenden Attribut versehen werden. Listing 2.6 demonstriert anhand eines

Beispiels die Definition eines WCF-Vertrags.

[ServiceContract] public interface ITimekeeping {

[OperationContract] bool Ping(); ...

Listing 2.6 – Definieren eines WCF-Vertrags

8

2.3.2 Datenverträge

Ein Datenvertrag ist eine formale Vereinbarung zwischen einem Dienst und seinem

Verwender, mit dem die auszutauschenden Daten abstrakt beschrieben werden. Für

die Kommunikation zwischen den beiden Seiten bedeutet dies also, dass sie nicht

denselben Typ verwenden, sondern nur dieselben Datenverträge. In einem

Datenvertrag wird für jeden Parameter oder Rückgabetyp genau definiert, welche

Daten für einen Austausch serialisiert werden. [5]

Zum Serialisieren und Deserialisieren von Daten verwendet WCF standardmäßig ein

Serialisierungsprogramm. Dabei können alle primitiven Typen von .NET Framework

wie Ganzzahlen und Zahlenfolgen, sowie bestimmte als Primitive behandelte Typen

wie DateTime und TimeSpan, ohne weitere Vorbereitung serialisiert werden, weil

diese Typen gewissermaßen mit Standardverträgen ausgestattet sind. [5]

Für neue komplexe Typen müssen entsprechende Datenverträge definiert werden,

damit sie serialisierbar sind. Mithilfe von Attributen wie DataContract und

DataMember kann ein Datenvertrag explizit erstellt werden. Dazu wird das

DataContract-Attribut auf den Typ angewendet, der eine Klasse, Struktur oder

Enumeration darstellt. Jeder Member des Datenvertragstyps wird zudem mit dem

DataMember-Attribut versehen. [5]

Listing 2.7 zeigt anhand eines Beispiels, wie ein Datenvertrag für den Typ Customer

definiert wird. Dieser Typ kann in einem Dienstvertrag für die Client/Service-

Kommunikation verwendet werden.

[DataContract] public class Customer {

[DataMember] public string Name { get; set; } [DataMember] public bool IsValid { get; set; }

}

Listing 2.7 – Definieren eines Datenvertrags

9

2.4 Managed Extensibility Framework

Managed Extensibility Framework (MEF) ist eine Bibliothek zur Lösung des Problems

der Erweiterbarkeit einer Anwendung zur Laufzeit und ist seit Version 4.0 Bestandteil

des .NET Frameworks.

2.4.1 Importe und Exporte

Im Wesentlichen geht es beim MEF um das Laden von Komponenten zur Laufzeit und

deren anschließende Bindung an die entsprechenden Variablen.

Mithilfe von folgenden Attributen werden dabei die Rollen definiert:

Export: Objekte, die mit diesem Attribut versehen sind, stellen Komponente

dar, die geladen und instanziiert werden sollen.

Import: die Importe sind die Variablen, an die die Komponenteninstanzen,

nachdem sie geladen sind, gebunden werden.

Listing 2.8 zeigt, wie die Rollenverteilung mithilfe von MEF-Attributen erfolgt: Die

Klasse Component1 wird als das zu exportierende Modul markiert, während die

Variable proxy die Instanz dieser Komponente nach dem Import enthalten soll. [6]

[Export] class Component1 : IComponent{} . . . [Import] IComponent proxy

Listing 2.8 – Definieren von Imports und Exports

10

2.4.2 Kompositionscontainer und Kataloge

Kompositionscontainer und Kataloge stellen die wichtigsten Module von MEF dar.

Ein Kompositionscontainer enthält alle verfügbare Komponente (Exporte) und führt

die Komposition aus, womit das Zuweisen von Importen zu Exporten gemeint ist. MEF

definiert mehrere Containertypen für unterschiedliche Problemstellungen.

CompositionContainer ist dabei der am häufigsten verwendete Typ und verwaltet die

Komposition von Exporten. [7]

Zur Ermittlung von verfügbaren Exporten wird von den Kompositionscontainern ein

Katalog verwendet. MEF stellt mehrere Katalogtypen bereit, um die Komponenten

beispielsweise in Assemblys oder in den Verzeichnissen ausfindig zu machen. Besteht

die Notwendigkeit, dass die Exporte aus anderen Quellen ermittelt werden sollen, so

können leicht neue Typen erstellt werden. [7]

Listing 2.9 demonstriert anhand eines Beispiels, wie mithilfe von MEF eine

Komposition erfolgt. Die Eigenschaft Component ist mit dem Import-Attribut

markiert. Zusätzlich wird der Typ der zu importierenden Komponente angegeben. In

der Funktion LoadComponent() erfolgt die Komposition. Dafür wird ein Katalog vom

Typ DirectoryCatalog definiert, welcher mit dem Pfad zu der zu importierenden

Komponente initialisiert wird. Anschließend wird ein CompositionContainer erstellt,

dem die Quelle des Exports in Form des Katalogs übergeben wird. Mit dem Aufruf der

Methode ComposeParts() am Container, initialisiert MEF die Komponente im

übergebenen Verzeichnis und speichert die Referenz auf diese in Component.

[Import(typeof(IExport))] private IExport Component { get; set; } public void LoadComponent() { var catalog = new DirectoryCatalog(path); var container = new CompositionContainer(catalog); container.ComposeParts(this);

} Listing 2.9 - Beispiel einer Komposition mit MEF

11

2.5 Windows Presentation Foundation

Windows Presentation Foundation (WPF) stellt das moderne Programmiermodell für

die Entwicklung von Benutzeroberflächen unter Windows dar und steht seit der

Einführung des .NET Frameworks 3.0 zu Verfügung.

2.5.1 Geschichtliche Entwicklung der GUI-Frameworks für Windows

Zu den Zeiten von Windows 1.0 gab es nur eine Möglichkeit Windows-Anwendungen

zu schreiben: Mit der Programmiersprache C und unter Verwendung der ebenfalls in

C geschriebenen Windows-Programmierschnittstelle (Windows-API). [8]

Die direkte Verwendung der Windows-API führte aufgrund des sehr niedrigen und

detaillierten Betriebssystem-Levels und sich daraus resultierenden vielen

Funktionsaufrufen zu Unmengen von Code, was die Übersichtlichkeit erheblich

beeinträchtigte. Es bestand also der Bedarf nach Programmbibliotheken, die den

Umgang mit der Windows-API vereinfachen, so dass bei der Entwicklung der Blick auf

das Wesentliche fällt. [8]

Microsoft Foundation Classes (MFC) war der erste Schritt in die richtige Richtung und

wurde von Microsoft für C++ als objektorientierte „Wrapper“-Bibliothek entwickelt.

MFC kapselte die Aufrufe der Windows-API und fasste diese zu logischen,

abstrakteren Einheiten zusammen. [8]

Mit der Einführung des .NET Frameworks kam mit Windows Forms ein neues

Programmiermodell zur Entwicklung von Windows-Anwendungen. Im Vergleich zu

dem Vorgänger MFC fiel der Einstieg in die neue Programmierschnittstelle leichter

und grafische Benutzeroberflächen ließen sich damit noch komfortabler erstellen.

Obwohl zwischen den Einführungen der beiden Programmiermodelle zehn Jahre

liegen, stellt auch Windows Forms lediglich einen Wrapper der existierenden

Windows API in Managed Code dar. [8]

12

2.5.2 Wesentliche Merkmale

WPF schlägt, anders als die bisherigen Programmiermodelle von Microsoft, die nur

dünne Wrapper um die Windows API darstellen, einen neuen, zeitgemäßen Weg ein

und stellt das erste Programmiermodell für Benutzeroberflächen dar, das fast

vollständig in .NET geschrieben ist.

Für die Darstellung setzt WPF DirectX anstelle von GDI ein und kann somit auf die

Leistung moderner Grafikkarten zurückgreifen, um höchst flexible und performante

Windows-Anwendungen zu erstellen.

Durch die Tatsache, dass DirectX für das Zeichnen der Komponente genutzt wird,

lässt sich mithilfe von Styles und Templates das Erscheinungsbild von Controls

komplett anpassen.

WPF zeichnet die Inhalte vektorbasiert und ermöglicht damit eine beliebige

Skalierung der Anwendung.

2.5.3 XAML

Die Extensible Application Markup Language (XAML) ist eine XML-basierte

Beschreibungssprache, die bei der WPF zu Erstellung von Benutzeroberflächen

eingesetzt wird. [8]

XAML bietet im Vergleich zu der alternativen Möglichkeit, die Oberflächen rein in C#

zu erstellen, folgende Vorteile:

Die Beschreibung einer GUI in XAML ist wesentlich kompakter.

Darstellung der Anwendung lässt sich besser von der Businesslogik trennen.

Bessere Aufgabenverteilung wird ermöglicht: Benutzeroberfläche kann von

einem Designer mithilfe von speziellen Tools wie Expression Blend komplett

entworfen werden, während ein Entwickler sich um die Implementierung der

Logik kümmert.

13

Listing 2.10 stellt einen gültigen XAML-Ausschnitt dar und beschreibt ein StackPanel,

welches ein Textfeld und ein Button beinhaltet. Das entsprechende Ergebnis ist in

Abb. 2.2 zu sehen.

<StackPanel>

<TextBlock Margin="20" HorizontalAlignment="Center">Hello XAML!</TextBlock> <Button Margin="10" HorizontalAlignment="Center">OK</Button>

</StackPanel>

Listing 2.10 – Beispiel: XAML-Ausschnitt

Abb. 2.2 – Beispiel: Mit XAML erstellte GUI

2.6 Das Model-View-ViewModel-Pattern

Das Model-View-ViewModel-Pattern (MVVM) stellt eine moderne Variante des

Model-View-Controller-Patterns (MVC) dar und erlaubt eine bessere Trennung von

UI-Design und UI-Logik. [8]

2.6.1 Konzept

MVVM-Pattern wurde im Zusammenhang mit der WPF eingeführt. Die Ziele des

Patterns sind eine lose Kopplung von Benutzeroberfläche und UI-Logik (Event Handler

& Co). Dies ermöglicht eine bessere Zusammenarbeit mit den Designern und eine

bessere Überprüfung der Logik mit Unit Tests. [8]

Das MVVM-Pattern basiert auf folgenden drei Komponenten:

View: die Benutzeroberfläche (XAML + Codebehind)

ViewModel: eine Klasse, die das Model kapselt und Properties (2.1.1)

bereitstellt, an die sich die View binden kann

Model: das Datenmodell; üblicherweise Klassen, die lediglich die Daten

enthalten

14

Abb. 2.3 zeigt die Abhängigkeiten der Komponenten im MVVM-Pattern. Die View

kennt das ViewModel. Das ViewModel kennt das Model, aber nicht die View (lose

Kopplung). Das Model kennt weder die View noch das ViewModel. [8]

Abb. 2.3 – Die Abhängigkeiten beim MVVM-Pattern

Das Model spielt im MVVM-Pattern dieselbe Rolle wie im MVC-Pattern und ist für die

Kapselung der Daten zuständig, die je nach Applikation in unterschiedlichen

Formaten vorliegen können. [8]

Die Aufgabe der View, das Darstellen von Daten, ist im MVVM- und MVC-Design

ebenfalls identisch. Die View enthält alle grafischen Elemente des User-Interfaces und

wird in der WPF typischerweise deklarativ in XAML definiert. [8]

Das ViewModel hat die Aufgabe, alle Informationen bereitzustellen, die für die

Aufbereitung der View benötigt werden. Dazu gehören sowohl die Daten des Models,

aber auch sehr UI-nahe Informationen, wie beispielsweise ob ein Button ausgegraut

(disabled) ist. Jegliche Logik für die Behandlung von Benutzereingaben ist im

ViewModel auch enthalten. Die Benutzereingaben werden über die View

entgegengenommen und direkt per Data Binding an das ViewModel weitergeleitet

und dort behandelt. Damit übernimmt das ViewModel auch einen großen Teil der

Funktionalität des Controllers im MVC-Pattern. [8]

View

ViewModel

Model

15

2.6.2 Verknüpfen der View mit ViewModel

Damit sich die UserControls in der View an die Properties des ViewModels binden

können, muss der View das entsprechende ViewModel bekannt gemacht werden.

Dies geschieht über das Setzen der DataContext-Property der View auf die

Bindungsquelle (ViewModel).

Prinzipiell gibt es dafür folgende Möglichkeiten:

Direktes Initialisieren des DataContexts der View, beispielsweise in der s.g.

Code-Behind-Datei

Definieren eines DataTemplates im Ressourcenbereich der View

In Listing 2.11 wird gezeigt, wie der DataContext von DayView direkt mit der

Bindungsquelle in der Code-Behind-Datei initialisiert wird.

// Interaktionslogik für DayView.xaml public partial class DayView : UserControl {

public DayView() { InitializeComponent(); this.DataContext = new DayViewModel(); }

}

Listing 2.11 – Direktes setzen des DataContexts der View im Codebehind

Listing 2.12 demonstriert, wie mithilfe eines DataTemplates in XAML für das

DayViewModel als View die DayView festgelegt wird. Damit wird WPF bekannt

gemacht, dass das DayViewModel als DayView gezeichnet wird. Außerdem wird der

DataContext der View automatisch auf das entsprechende ViewModel gesetzt und

somit die Bindung ermöglicht.

<DataTemplate DataType="{x:Type vm:DayViewModel}">

<vw:DayView /> </DataTemplate>

Listing 2.12 – Setzen des DataContexts der View per DataTemplate

Setzen des DataContexts per DataTemplate stellt die bessere der beiden

Möglichkeiten dar. Zum einen strebt MVVM an, dass die Code-Behind-Datei leer

bleibt und die View nur mittels XAML definiert wird, zum anderen lassen sich die

DateTemplates in einem Ressourcenwörterbuch ablegen, was eine bessere Übersicht

bietet und die Anpassungen zentral vorgenommen werden können.

16

2.6.3 Data Binding und Commands

Data Binding ermöglicht es, eine Dependency Property an eine andere Property zu

binden. Dependency Propertys stellen dabei gewöhnliche .NET Properties dar, die in

ihrer Funktionalität erweitert sind und durch Methoden wie Formatierung,

Datenbindung, Animation und Vererbung festgelegt werden können. [8]

Mithilfe von Data Binding können die Controls der View, wie bspw. ListView und

TextBlock, an die Eigenschaften des zugehörigen ViewModels gebunden werden.

Listing 2.13 zeigt, wie die Dependency Property Text von TextBlock an die Property

DayStatus des ViewModels gebunden wird, mit dem der DataContext der View

initialisiert ist.

<TextBlock Text="{Binding DayStatus}"/>

Listing 2.13 – Data Binding

Ein Command stellt unter WPF ein Objekt vom Typ ICommand dar und definiert eine

abstraktere, losgelöste Form eines Events. [8]

Einige Controls in WPF wie Button, bieten eine Dependency Property namens

Command an. An diese kann eine entsprechende Eigenschaft des ViewModels

gebunden werden, die das Ereignis behandelt.

Wie in Listing 2.14 zu sehen, enthält das Interface ICommand zwei Methoden und ein

Event.

public interface ICommand {

public bool CanExecute(object parameter); public void Execute(object parameter); public event EventHandler CanExecuteChanged;

}

Listing 2.14 – Das Interface ICommand

Mithilfe einer geeigneten Implementierung von ICommand können dem Command

Objekt als Parameter für die Methoden die s.g Delegaten übergeben werden, die

unter .NET Funktionszeiger darstellen.

17

2.6.4 Die INotifyPropertyChanged-Schnittstelle

Um der View bekannt zu geben, dass sich die Werte der Eigenschaften im ViewModel

geändert haben und die View die Bindungen entsprechend aktualisieren muss, muss

das ViewModel die Schnittstelle INotifyPropertyChanged implementieren.

Listing 2.15 zeigt die Deklaration der Schnittstelle, die lediglich ein Event namens

PropertyChanged definiert.

public interface INotifyPropertyChanged { event PropertyChangedEventHandler PropertyChanged; }

Listing 2.15 – Das Interface INotifyPropertyChanged

Beim setzen des DataContexts der View auf die Instanz eines ViewModels, registriert

sich die View automatisch an dem Event PropertyChanged, falls das Interface

INotifyPropertyChanged vom ViewModel implementiert ist. Um die View über die

Änderung an einer Property zu informieren, muss das Ereignis vom ViewModel

ausgelöst werden und als Ereignisparameter der Name der entsprechenden

Eigenschaft übergeben werden.

Listing 2.16 demonstriert die Verwendung des Ereignisses PropertyChanged. Nach der

Berechnung des Ergebnisses wird das Ereignis mit dem Namen der Property Result

ausgelöst. Der an diese Eigenschaft gebundene Control wird somit aktualisiert.

public class DemoViewModel : INotifyPropertyChanged { public int Result { get; set; } public event PropertyChangedEventHandler PropertyChanged; private void Calculate() { // Do something with result … // Actualize the binding if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs("Result")); } } }

Listing 2.16 – Aktualisierung der Bindung mit dem Event PropertyChanged

18

2.7 Server

2.7.1 Active Directory

Active Directory (AD) stellt den Verzeichnisdienst von Microsoft Windows Server, mit

dessen Hilfe verschiedene Objekte wie bspw. Benutzer, Gruppen, Computer und

andere Geräte wie Drucker und Scanner und deren Eigenschaften in einem Netzwerk

verwaltet werden können. Mit Hilfe von Active Directory können die Informationen

der Objekte durch einen Administrator organisiert, bereitgestellt und überwacht

werden. [9]

Abb. 2.4 – Active Directory in einem Windows Server Netzwerk (Quelle: Microsoft)

Active Directory ist hierarchisch gegliedert. Objekte werden in Containern

(Organisationseinheiten) abgelegt, auf die entsprechende Gruppenrichtlinien

angewandt werden können. Einige Organisationseinheiten sind vordefiniert,

beliebige weitere können als Subeinheiten erstellt werden. [9]

19

Gruppen

Mithilfe von Gruppen werden Benutzerkonten, Computerkonten und sonstige

Gruppenkonten zu leicht verwaltbaren Einheiten zusammengefasst. Durch die

Verwendung von Gruppen wird die Verwaltung vereinfacht, indem vielen Konten in

einem Schritt einheitliche Berechtigungen und Rechte zugewiesen werden können.

Active Directory kennt folgende zwei Typen von Gruppen: Verteilergruppen und

Sicherheitsgruppen. Mithilfe von Verteilergruppen können E-Mail-Verteilerlisten

erstellt, und mit Sicherheitsgruppen die Berechtigungen für freigegebene Ressourcen

zugewiesen werden. [10]

SID und deren Aufbau

Für die dauerhafte Identifizierung jeder Gruppe und jedes Benutzers wird von

Microsoft Windows NT automatisch ein Sicherheits-Identifikatior (Security Identifier,

SID) vergeben. An die SID werden festgelegte Zugriffsrechte gebunden. Sollten sich

die Namen von Systemen oder Gruppen ändern, so bleibt deren SID unverändert, was

eine problemlose Namensgebung ermöglicht. [11]

Eine SID kann wie folgt aussehen: S-1-5-21-7623811015-3361044348-030300820-1013

Aus folgenden Bestandteilen setzt sie sich zusammen:

S - Abkürzung für SID

1 - Revisionsnummer

5 - Identifier

21-7623811015-3361044348-030300820 - Domäne oder lokales System

1013 - Benutzernummer

Zugriff

Mithilfe von Lightweight Directory Access Protocol (LDAP), welches ein

Anwendungsprotokoll aus der Netzwerktechnik darstellt, lässt sich Active Directory

über ein IP-Netzwerk abfragen und modifizieren. [12] .NET stellt im Namespace

System.DirectoryServices verschiedene Klassen zur Verfügung für die Kommunikation

mit dem Verzeichnisdienst.

Authentifizierung in einer Anwendung mittels Active Directory

Durch das Herausfinden der SID des angemeldeten Benutzers in einer Anwendung

und die anschließende Überprüfung, zu welcher Sicherheitsgruppe in Active Directory

die SID gehört, kann leicht eine Authentifizierung realisiert werden.

20

2.7.2 Internet Information Services

Internet Information Services (IIS) stellt einen Webserver von Microsoft dar. IIS

unterstützt gängige Kommunikationsprotokolle wie HTTP, HTTPS, FTP, SMTP und

andere. Über IIS können ASP.NET – Anwendungen1 und WCF – Services (s. 2.3)

ausgeführt werden, sowie durch das Installieren von passenden Filtern – auch PHP2

und JSP3. [13]

1 Active Server Pages .NET ist eine serverseitige Technologie von Microsoft zum Erstellen dynamischer Webseiten, Webanwendungen und Webservices auf Basis des Microsoft-.NET-Frameworks 2 Hypertext Preprocessor ist eine Skriptsprache, die hauptsächlich zur Erstellung dynamischer Webseiten oder Webanwendungen verwendet wird 3 JavaServer Pages ist eine von Sun Microsystems entwickelte, auf JHTML basierende Web-Programmiersprache zur einfachen dynamischen Erzeugung von HTML- und XML-Ausgaben eines Webservers

21

3 Anforderungen

In erster Linie geht es bei diesem Projekt um die Entwicklung einer Anwendung, die

folgenden Anforderungen entspricht:

Funktionsumfang durch Plug-Ins erweiterbar

Service-orientierte Architektur

Benutzer- und Rollenverwaltung über Active Directory

WPF als GUI-Framework

Anschließend sollen für die Anwendung zu Testzwecken folgende Module erstellt

werden:

Verwaltung von Arbeitszeiten

Projekt und Kundenmanagement

Auswertung der Arbeitszeiten

3.1 Analyse

Pluginfähigkeit

Das Plug-In-Konzept bietet mehrere Vorteile:

Bessere Trennung des Codes, da jedes Modul als eigenständiges Projekt

entwickelt werden kann

Der Funktionsumfang der Anwendung lässt sich leicht zusammenstellen und

erweitern

Aktualisierung gestaltet sich leicht, denn es reicht aus, das entsprechende

Plug-In mit der neuen Version zu ersetzen

Benutzerverwaltung

In den meisten Firmen, die Windows einsetzen, geschieht die interne Verwaltung der

Mitarbeiter über Active Directory. Mithilfe von speziell für die Anwendung erstellten

Sicherheitsgruppen, denen die entsprechenden Mitarbeiter zugewiesen werden,

kann mit wenig Aufwand eine flexible und zentrale Verwaltung der Benutzer realisiert

werden.

22

Serviceorientierte Architektur

Da es sich im Falle der Arbeitszeiterfassung um verteilte Anwendungen handelt, die

alle die gleiche Funktionalität aufweisen und entsprechend identische Abfragen an

die Datenbank stellen, bietet es sich an, in Form eines Services eine zusätzliche

Schicht zwischen den Anwendungen (Clients) und der Datenbank einzuführen, die

sich um die Kommunikation kümmert.

Abb. 3.1 soll die Architektur verdeutlichen. Die Clients enthalten keinerlei Abfrage-

Logik, diese ist nämlich im Service untergebracht. Diese Architektur hat den Vorteil,

dass der Service unabhängig von seinen Konsumenten aktualisiert werden kann.

Beispielsweise kann dadurch leicht die Authentifizierung im Service angepasst

werden, ohne dass jeder einzelne Client dafür ein entsprechendes Update bekommt.

Abb. 3.1 – Service-orientierte Architektur

Grafik-Framework

Unter .NET stehen dem Entwickler zwei Programmierschnittstellen zu Erstellung

grafischer Oberflächen zur Verfügung: Windows Forms und Windows Presentation

Foundation.

Windows Forms stellt dabei das Auslaufmodel dar und wird von Microsoft nicht mehr

weiterentwickelt. WPF hingegen ist das aktuelle Programmiermodell und somit

zukunftssicher. Im Hinblick auf die Möglichkeiten ist WPF seinem Vorgänger zudem

deutlich überlegen und mit dem MVVM-Pattern lassen sich übersichtliche und gut

testbare Anwendungen erstellen.

DB

Service

Client Client Client

23

3.2 Grobkonzept

Der Funktionsumfang der Anwendung soll durch ihre Plug-Ins definiert werden. Der

Client stellt in diesem Fall die Laufzeitumgebung dar, die zur Laufzeit Module

einbindet und diese anschließend bereitstellt. Außerdem verfügt der Client über

grundlegende Funktionalitäten wie Einstellungen und Info.

Die Module sind eigenständige Klassenbibliothek-Projekte und liegen der

Laufzeitumgebung in Form von Assemblys vor. Das Laden von Komponenten wird

mithilfe von MEF realisiert.

Die Service-Schicht kümmert sich um alle Abfragen des Clients. Umgesetzt wird der

Service unter Verwendung von WCF. Für die Kommunikation mit der Datenbank wird

intern EF eingesetzt.

Abb. 3.2 – Grobentwurf

DB

Client

Entity Framework

bindet ein (MEF)

Service (WCF)

Modul (Assembly)

Abfragen

24

4 Entwurf

Basierend auf der vorhergehenden Analyse, wird die Anwendung im folgenden

Kapitel schrittweise entworfen.

4.1 Plug-In-Architektur

4.1.1 Laufzeitumgebung und die Module

Den Anforderungen nach soll als GUI-Framework WPF benutzt werden. Diese

Vorgabe hat einen entscheidenden Einfluss auf die Struktur der Module und die Art

und Weise, wie diese von der Laufzeitumgebung zur Verfügung gestellt werden.

Da als Architekturmuster das Model-View-View-Model-Pattern (s. 2.6) genutzt wird,

stellt ein Modul entsprechend ein Paket von ViewModels mit den dazugehörigen

Views und Models dar. Die Views lassen sich hierarchisch zusammenfassen mit einer

Haupt-View in der obersten Ebene. Durch das direkte Setzen von DataContext (s.

2.6.2) kann der Haupt-View im Modul das Haupt-ViewModel zugewiesen werden, das

die darunterliegenden ViewModels verwaltet. Somit muss der Laufzeitumgebung

unter anderem die oberste View des Moduls übergeben werden, um dieses im

Content-Bereich (s. 4.1.3) rendern zu können.

Abb. 4.1 zeigt den Aufbau eines Moduls aus der Architektursicht. Die Models sind aus

Übersichtlichkeitsgründen nicht in der Abbildung enthalten.

Abb. 4.1 – Hierarchische Struktur eines Moduls

MainView

MainViewModel

View

ViewModel

View

ViewModel

25

4.1.2 Projektstruktur und der Vertrag

Um eine direkte Kopplung zwischen den Modulen und der Laufzeitumgebung zu

vermeiden, wird ein Communication-Projekt angelegt, auf welches beide Seiten

verweisen. Communication stellt das zentrale Projekt in der Projektmappe dar und

beinhaltet alle Komponente, die beiden Seiten zur Verfügung stehen sollen.

Abb. 4.2 – Grundlegende Projektstruktur

Durch diese Trennung ergeben sich zudem folgende Vorteile:

Module können unabhängig vom Client entwickelt werden.

Durch die Anpassung des Projekts Communication kann die Anwendung leicht

an eine neue Problemstellung angepasst und somit universell eingesetzt

werden.

Zwischen der Laufzeitumgebung und den Modulen wird im Communication-Projekt

ein Vertrag in Form der IModule-Schnittstelle definiert. Diese wird von den Modulen

entsprechend implementiert, um vom Client mithilfe von MEF geladen werden zu

können.

Client Communication Modul <<access>> <<access>>

26

4.1.3 Benutzeroberfläche der Laufzeitumgebung

Wie in Abb. 4.3 zu sehen, gliedert sich die GUI des Clients in folgende Bereiche:

Plug-In Auswahl: hier erscheinen in Form einer dynamisch generierten Liste

von Schaltflächen, die Namen der eingebundenen Module, die ausgewählt

werden können.

Content: in diesem Bereich wird das aktive Modul bzw. Info/Einstellungen

gerendert.

Menü: von der Laufzeitumgebung fest definierte Menüpunkte.

Abb. 4.3 – GUI Entwurf der Laufzeitumgebung

Content

Einstellungen

Info

Plug-In #1

Plug-In #2

Plug-In Auswahl

Menü

27

4.2 Service-Architektur

Um eine bessere Trennung des Codes zu erreichen, soll die Implementierung des

Service-Vertrags nicht direkt mit dem Entity Framework-Kontext kommunizieren.

Stattdessen wird zwischen dem Service und dem ORM eine zusätzliche Schicht, in

Form der Klasse DataManager, eingeführt.

In Bezug auf die Wiederverwendbarkeit des Projekts, bedeutet die Einführung der

Datenzugriffschicht, dass für den Zugriff auf die Datenbank nicht unbedingt ein

Service notwendig ist. Durch die direkte Verwendung von DataManager für den

Datenzugriff kann das Projekt leicht für nicht verteilte Anwendungen angepasst

werden.

Abb. 4.4 verdeutlicht den Aufbau des Services und den alternativen Datenzugriff, im

Falle, dass das Projekt für nicht verteilte Anwendungen wiederverwendet wird.

Abb. 4.4 – Flexibler Datenzugriff durch den DataManager

Client

DB

Service

EF-Kontext

Data Manager

28

4.3 Datenhaltung im Client

Das Separation of Concerns (SoC) Prinzip gibt vor, dass die unterschiedlichen Bereiche

einer Anwendung klar getrennt und voneinander so unabhängig wie möglich sein

sollen. Die einzelnen Komponenten werden jeweils auf eine Aufgabe fokussiert und

lassen sich dadurch leicht verstehen. Darüber hinaus führt SoC auch zu gut testbaren

Komponenten, weil der Zweck einer Codeeinheit fokussiert wird und weniger breit

getestet werden muss. [14]

Dieses Prinzip soll auch beim Datenzugriff weitergeführt werden. Das ViewModel ist

zwar für die Domainlogik und damit Manipulation der Daten zuständig, der Code für

den Datenzugriff sollte aber nicht direkt im ViewModel erfolgen, sondern in

Repositories ausgelagert werden, die ihrerseits von einem RepositoryManager

verwaltet werden.

Da RepositoryManager allen ViewModels zur Verfügung stehen muss und somit eine

zentrale Einheit in der Anwendung darstellt, bietet es sich an, die Klasse entweder als

statisch oder als Singleton zu implementieren. Diese Vorgehensweisen bringen

jedoch gewisse Nachteile mit sich. Unter anderem kann dadurch das Testen

komplizierter sein. Aus diesem Grund wird RepositoryManager als eine normale

Klasse implementiert, die beim Start der Anwendung initialisiert und anschließend an

alle ViewModels, die mit den Daten arbeiten sollen, per Konstruktor übergeben wird.

Abb. 4.5 verdeutlicht das Konzept der Datenhaltung nach dem Repository-Pattern.

Abb. 4.5 – Datenhaltung im Client

ViewModel ViewModel ViewModel

Repository Typ A

Repository Typ B

RepositoryManager

29

4.4 Gesamtarchitektur

Abb. 4.6 repräsentiert die im Laufe der Entwurfsphase entstandene, grundlegende

Architektur der Anwendung.

Abb. 4.6 – Gesamtarchitektur

Client Modul

DB

IIS

Datenbank-Server

Communication

IModule lädt impl.

HTTP/SOAP

Service

EF-Kontext

Data Manager

30

5 Implementierung

Dieses Kapitel geht auf die Implementierungsdetails der wichtigen Komponenten der

Anwendung ein.

5.1 Implementierung der Serviceschicht

5.1.1 Vertrag

Um eine Kommunikation zwischen dem WCF-Service und dem Client zu ermöglichen,

wird zwischen den beiden Seiten ein Vertrag in Form einer Schnittstelle definiert.

Diese beschreibt die Funktionen, die der Service zur Verfügung stellt. Mithilfe von

diesen Informationen wird vom WCF eine entsprechende Klasse generiert, die im

Client für den Zugriff auf den Service verwendet wird.

Für den Service wird ein eigenes WCF-Projekt angelegt. Das Interface ITimekeeping

stellt den Servicevertrag dar, deren Umfang sich durch die Analyse der

Serviceanforderungen für die Zeiterfassung ergibt.

Abb. 5.1 – Diagramm: Servicevertrag

31

Listing 5.1 zeigt einen Ausschnitt mit einigen Methodensignaturen aus dem Vertrag.

[ServiceContract] public interface ITimekeeping {

[OperationContract] bool Ping();

[OperationContract] List<Employee> LoadAllEmployees();

... }

Listing 5.1 – Ausschnitt aus dem Servicevertrag

5.1.2 Zusammenspiel mit dem Entity Framework

Die Implementierung des Vertrags verwendet das Entity Framework für die

Kommunikation mit der Datenbank. Das Objektmodell wird nach dem Model-First-

Ansatz in Visual Studio erstellt und anschließend auf die Datenbank abgebildet.

Abb. 5.2 – Diagramm: Objektmodell

32

Serialisierung

Die vom EF aus dem Objektmodell generierten Entity-Objekte können zunächst nicht

direkt für die Client/Service-Kommunikation verwendet werden, da es sich um

komplexe Typen handelt und das Serialisierungsprogramm von .NET sie entsprechend

nicht automatisch serialisieren kann.

Um die Serialisierung von Entitys zu ermöglichen, wird für jedes Objekt ein

entsprechender Datenvertrag definiert und mithilfe von partiellen Klassen eine

Methode zur Konvertierung in den serialisierbaren Typ hinzugefügt.

Für die Datenverträge wird eine abstrakte Oberklasse DataObjectBase definiert, von

der sich alle Datenverträge ableiten. Anhand des Typs Employee in Abb. 5.3 ist zu

erkennen, dass alle wichtigen Eigenschaften von EmployeeEntity übernommen

werden. Um Employee als Authentifizierungsobjekt verwenden zu können, wird es

zusätzlich um die boolesche Eigenschaft IsAdmin ergänzt.

Abb. 5.3 – Diagramm: Datenverträge

33

Listing 5.2 zeigt die Definition eines Datenvertrags. Um die Objekte für WPF

bindungsfähig zu machen, sind alle Klassenattribute als Propertys deklariert.

[DataContract] public class Employee : DataObjectBase {

[DataMember] public string Name { get; set; } [DataMember] public string SID { get; set; } [DataMember] public bool IsInternal { get; set; } [DataMember] public bool IsAdmin { get; set; } [DataMember] public List<int> Projects { get; set; }

public override string ToString() { return Name; }

}

Listing 5.2 – Datenvertrag für Employee

Listing 5.3 zeigt, wie mittels einer partiellen Klasse EmployeeEntity um eine

Konvertierungsmethode erweitert wird, die alle Eigenschaften der Entity kopiert und

ein neues, serialisierbares Objekt zurückliefert.

public partial class EmployeeEntity {

public Employee ToDO() { List<int> projects = new List<int>(); foreach (var p in this.Projects) projects.Add(p.ID);

return new Employee { ID = this.ID, SID = this.SID, Name = this.Name, IsInternal = this.IsInternal, Projects = projects, ObjectStatus = DataObjectStatus.Normal }; }

}

Listing 5.3 – Erweitern eines EntityObjects um eine Konvertierungsmethode

34

Flexibler Umgang mit dem Entity Framework

Um die Möglichkeit zu haben, durch die Aktualisierung des Services die zu

verwendende Datenbank zu ändern (Test- und Produktivumgebung), muss die Instanz

der Kontext-Klasse anstatt mit dem in der Konfigurationsdatei festgelegten

ConnectionString mit änderbaren Verbindungsdaten erstellt werden.

Für die Verwaltung der Verbindungsdaten wird die Klasse DbConnectionData erstellt.

Um die Erstellung des Kontexts kümmert sich die abstrakte Klasse DataManagerBase.

Abb. 5.4 – Diagramm: DbConnectionData

Abb. 5.5 – Diagramm: DataManagerBase

35

Die Klasse DbConnectionData enthält keine Logik und dient nur dazu, die

Informationen für die Datenbankverbindung zusammenzufassen. Sie wird als

Parameter dem Konstruktor der Klasse DataManagerBase übergeben. Intern wird

mithilfe von Funktionen GetEntityConnectionString() und GetProviderString() ein

entsprechender ConnectionString generiert, mit dem eine Instanz des EF-Kontexts

initialisiert wird. Für den Zugriff auf den Kontext dient die öffentliche

Eigenschaftsmethode Context.

Implementierung

Listing 5.4 demonstriert die Implementierung der Methode LoadAllEmployees() des

Servicevertrags. Die Klasse DataManager stellt dabei eine Ableitung von

DataManagerBase dar und liefert mittels der gleichnamigen Methode eine Liste von

Mitarbeitern zurück. Schlägt der Zugriff fehl, so wird mithilfe der Klasse Logger die

entsprechende Fehlermeldung für die spätere Analysierung in einer Logdatei auf dem

Server abgelegt.

public List<Employee> LoadAllEmployees() {

try { var manager = new DataManager(dbc_data); return manager.LoadAllEmployees(); } catch (Exception e) { Debug.WriteLine("# Exc by Service-LoadAllEmployees(): " +

e.Message); Logger.Logger.WriteLine(path, "'LoadAllEmployees()'", e.Message);

return new List<Employee>(); }

}

Listing 5.4 – Implementierung des Servicevertrags

Listing 5.5 zeigt den entsprechenden Datenzugriff im DataManager.

public List<Employee> LoadAllEmployees() {

using (var c = this.Context) { List<Employee> employees = new List<Employee>();

foreach (var e in c.Employees) employees.Add(e.ToDO());

return employees; }

}

Listing 5.5 – Datenzugriff im DataManager

36

Wie in Listing 5.5 zu sehen, wird mithilfe des Kontexts innerhalb des using-Blocks eine

Liste von EntityObjects iteriert, wobei jeder Eintrag in den serialisierbaren Typ

konvertiert und zu einer neuen Liste hinzugefügt wird, die anschließend

zurückgegeben wird.

5.1.3 Kommunikation mit dem Service

RequestBase

Damit die Benutzeroberfläche jederzeit reaktionsfähig bleibt, müssen die Service-

Aufrufe asynchron zum Hauptthread ausgeführt werden. Aus diesem Grund wird eine

neue abstrakte Klasse RequestBase entwickelt, deren Aufgabe es ist, die Grundlage

für alle asynchronen Abfragen zu liefern und diesen intern den Service zur Verfügung

zu stellen.

.NET bietet mit BackgroundWorker eine Klasse an, die es einfach macht, einen

Vorgang auf einem separaten, dedizierten Thread auszuführen. Dazu bietet

BackgroundWorker zwei Ereignisse an:

DoWork: diesem kann in Form eines Ereignishandlers eine Operation

hinzugefügt werden, die im Hintergrund ausgeführt wird

RunWorkerCompleted: tritt ein, wenn die Operation abgeschlossen ist oder

während der Ausführung ein Fehler aufgetreten ist

Mit dem Aufruf der Methode RunWorkerAsync() lässt sich die Ausführung im

Hintergrund starten.

37

Abb. 5.6 – Diagramm: RequestBase

Anhand der Abb. 5.6 ist zu erkennen, dass RequestBase neben dem Konstruktor nur

eine öffentliche Methode Start() anbietet. ServiceCommunication stellt eine weitere

Klasse dar, die durch den Dienstverweis generierte Service-Proxy-Klasse mit der im

Client einzustellenden Service-Adresse initialisiert und anschließend anbietet. Somit

erhalten die Abfragen eine einfache Möglichkeit, auf den Service intern zuzugreifen.

Eine Instanz von RepositoryManager (s. 5.5.2) wird der Klasse im Konstruktor

übergeben, um die abgefragten Daten anwendungsweit ablegen zu können.

Außerdem enthält die Klasse das Ereignis RequestFinished, das die Fertigstellung

einer Abfrage signalisiert.

Im Folgenden werden wichtige Teile der Klasse genauer erläutert.

38

Listing 5.6 zeigt die Implementierung des Ereignishandlers DoWork(): Die

gleichnamige abstrakte Methode, die von einer konkreten Abfrage implementiert

werden soll, wird aufgerufen und im Falle, dass eine Ausnahme ausgelöst wird,

erfolgt eine entsprechende Ausgabe auf die Konsole. Durch die Einkapselung der

abstrakten Methode DoWork() in dem gleichnamigen Ereignishandler wird erreicht,

dass sie ohne, in diesem Fall, überflüssige Übergabeparameter vom Ereignis DoWork

auskommt. Außerdem ermöglicht dies eine interne Fehlerbehandlung.

protected abstract void DoWork(); private void DoWork(object sender, DoWorkEventArgs e) {

try { this.DoWork(); } catch (Exception exc) { Debug.WriteLine("# Exception by Request: " + exc.Message); }

}

Listing 5.6 – RequestBase: DoWork()

Wie in Listing 5.7 zu sehen, ähnelt das Prinzip der Implementierung des

Ereignishandlers WorkCompleted() dem von DoWork(), mit dem Unterschied, dass

hier keine Fehlerbehandlung erforderlich ist und nach der Abarbeitung der

abstrakten Methode FinishWork() das Ereignis RequestFinished ausgelöst wird.

protected abstract void FinishWork(); private void WorkCompleted(object sender, RunWorkerCompletedEventArgs e) {

this.FinishWork();

if (this.RequestFinished != null) this.RequestFinished(this, new EventArgs());

}

Listing 5.7 – RequestBase: FinishWork()

Listing 5.8 demonstriert die Implementierung der Methode Start(): Nach der

Erstellung einer neuen Instanz von BackgroundWorker, werden die Ereignishandler

den entsprechenden Ereignissen zugewiesen und die Ausführung gestartet.

public void Start() {

_repositoryManager.ProgressDescription = _progressDescription; _backgroundWorker = new BackgroundWorker(); _backgroundWorker.DoWork += new DoWorkEventHandler(DoWork); _backgroundWorker.RunWorkerCompleted += WorkCompleted; _backgroundWorker.RunWorkerAsync();

}

Listing 5.8 – RequestBase: Start()

39

Listing 5.9 zeigt anhand eines konkreten Beispiels die Implementierung einer Abfrage.

Die Klasse LoadAllEmployees leitet sich von RequestBase ab und überschreibt die

Methode DoWork() der Oberklasse mit dem Zugriff auf den Service, dessen

Rückgabewert in diesem Fall eine Liste von allen Mitarbeitern ist. In der ebenfalls

überschriebenen Methode FinishWork() werden anschließend, durch die Iteration der

vom Service gelieferten Liste, die Werte in dem RepositoryManager abgelegt.

public class LoadAllEmployees : RequestBase {

List<Employee> _employees;

public LoadAllEmployees(RepositoryManager rm) : base(rm) { this.ProgressDescription = "Mitarbeiter werden geladen..."; }

protected override void DoWork() { _employees = this.ServiceCommunication.Client.LoadAllEmployees(); }

protected override void FinishWork() { foreach (var e in _employees) this.RepositoryManager.GetRepository<Employee>().Add(e); }

}

Listing 5.9 – Beispiel einer Abfrage

RequestManager

In einigen Fällen muss eine bestimmte Reihenfolge von Abfragen ausgeführt werden,

wie beispielsweise beim Start der Anwendung:

Prüfen der Verbindung zum Service

Authentifizierung

Abfragen, die von einzelnen Modulen für den Start definiert werden

Dies allein mit RequestBase realisiert, würde eine unübersichtliche Kette von

Ereignishändlern ergeben. Außerdem wäre die Codemenge dadurch unnötig

vergrößert.

Aus diesem Grund wird zusätzlich die Klasse RequestManager entwickelt, deren

Aufgabe es ist, eine komfortable Möglichkeit zu bieten eine Reihe von Abfragen

nacheinander auszuführen.

40

RequestManager stellt alle notwendige Methoden zu Verfügung für die Ausführung

mehrerer Abfragen nacheinander. Die Verwaltung der Abfragen übernimmt die

Queue Requests (First In - First Out - Prinzip). Das Ereignis RequestChainFinished

informiert über das Ende der Abarbeitung. AddRequest() und OnRequestFinished()

stellen die wichtigsten Methoden der Klasse dar.

Abb. 5.7 – Diagramm: RequestManager

Listing 5.10 demonstriert das Hinzufügen einer neuen Abfrage. Um auf die

Fertigstellung einer asyncrhonen Operation reagieren zu können, wird am Ereignis

RequestFinished das private Ereignishändler OnRequestFinished() registriert.

Anschließend wird die Abfrage der Queue hinzugefügt.

request.RequestFinished += new EventHandler<EventArgs>(OnRequestFinished); this.Requests.Enqueue(request);

Listing 5.10 – RequestManager: AddRequest()

Die Methode OnRequestFinished() in Listing 5.11 ist für die Abarbeitung der Abfragen

zuständig und führt dafür folgende Schritte durch:

Abmelden vom Ereignis RequestFinished jeder Abfrage, die fertiggestellt

wurde, damit diese vom GarbageCollector freigegeben werden kann.

Befinden sich in der Queue noch weitere Abfragen, so wird die erste aus der

Warteschlange entnommen und gestartet, ansonsten wird das Ereignis

RequestChainFinished ausgelöst, um die Fertigstellung bekanntzugeben.

41

void OnRequestFinished(object sender, EventArgs e) {

(sender as RequestBase).RequestFinished -= OnRequestFinished;

if (this.Requests.Count != 0) Requests.Dequeue().Start(); else if (this.RequestChainFinished != null) this.RequestChainFinished(this, new EventArgs());

}

Listing 5.11 – RequestManager: OnRequestFinished()

Listing 5.12 zeigt, wie mithilfe von RequestManager eine Reihe von Abfragen

ausgeführt und auf deren Fertigstellung mittels einer Ereignisbehandlung reagiert

werden kann.

requestManager.AddRequest(new Authentication(_repositoryManager)); requestManager.AddRequest(new LoadAllEmployees(_repository)); requestManager.RequestChainFinished += ChainFinished; requestManager.Start();

Listing 5.12 – Beispiel: Verwendung von RequestManager

5.1.4 Hosting

Während des Entwicklungsprozesses kann der WCF-Service von Visual Studio lokal

gehostet werden. Um ihn anschließend in einer Produktivumgebung zu

veröffentlichen, existieren folgende Möglichkeiten:

Veröffentlichen auf IIS (s. 2.7.2)

Installieren als Windows-Dienst

Ausführen des Services in einer Konsolenanwendung

Die Veröffentlichung auf IIS stellt dabei die beste Variante dar, denn sie bietet im

Vergleich zu den anderen Optionen folgende Vorteile:

Unkomplizierte Einbindung des Services: keine Installationsroutine nötig

Aktualisierung gestaltet sich sehr einfach: lediglich die Assemblys müssen

ersetzt werden. Dafür ist kein Neustart der gehosteten Anwendung nötig

Änderung von relevanten Parametern (Port, Protokoll) jederzeit möglich

42

5.2 Grundgerüst für MVVM

5.2.1 ViewModelBase

Aus dem Grund, dass die meisten ViewModel-Klassen dieselben Features benötigen,

wie beispielsweise die Implementierung der INotifyPropertyChanged-Schnittstelle (s.

2.6.4), wird eine abstrakte ViewModelBase-Klasse entwickelt, die die Grundlage für

alle ViewModels bildet.

Abb. 5.8 - Diagramm: ViewModelBase

Wie in Abb. 5.8 zu sehen, implementiert ViewModelBase folgende Schnittstellen:

INotifyPropertyChanged: dient zur Benachrichtigung des Bindungssystem über

einen neuen Wert

IDisposable: ermöglicht das Freigeben von Fremdressourcen, damit der

belegte Speicher vom nicht mehr benötigten Objekt durch den Garbage

Collector freigegeben werden kann

43

Die Implementierung der INotifyPropertyChanged-Schnittstelle stellt den wichtigsten

Teil der Basisklasse dar und definiert ein PropertyChanged-Ereignis, das ausgelöst

werden kann, um das WPF-Bindungssystem zu aktualisieren.

Die PropertyChangedEventArgs-Klasse macht eine PropertyName-Eigenschaft vom

Typ String verfügbar, um den Eigenschaftsnamen angeben zu können. Da das

Auslösen des PropertyChanged-Ereignisses mit einem falschen Eigenschaftsnamen im

Ereignisargument zu Fehlern führen kann, die sich nur schwer aufspüren lassen, wird

die Routine für die Auslösung des Ereignisses um eine Ermittlung der Existenz der

Property ergänzt.

Wie in Listing 5.13 zu sehen, übernimmt die Methode VerifyPropertyName() die

Überprüfung, ob der übergebene Eigenschaftsname in einem ViewModel-Objekt

wirklich vorhanden ist. Weil die Methode mit einem Conditional-Attribut versehen

und somit nur im Debug-Modus verfügbar ist, ergeben sich bei deren Verwendung

zur Laufzeit der Anwendung keine Performance-Beeinträchtigungen.

public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) {

this.VerifyPropertyName(propertyName);

PropertyChangedEventHandler handler = this.PropertyChanged; if (handler != null) { var e = new PropertyChangedEventArgs(propertyName); handler(this, e); }

} [Conditional("DEBUG")] [DebuggerStepThrough] public void VerifyPropertyName(string propertyName) {

if (TypeDescriptor.GetProperties(this)[propertyName] == null) { string msg = "Invalid property name: " + propertyName;

if (this.ThrowOnInvalidPropertyName) throw new Exception(msg); else Debug.Fail(msg); }

}

Listing 5.13 – ViewModelBase: OnPropertyChanged()

44

Die Methode OnPropertyChanged() erwartet als Übergabeparameter einen

Eigenschaftsnamen in Form eines Strings, überprüft zunächst die Zeichenkette auf

ihre Gültigkeit, erstellt eine neue Instanz vom Ereignisargument

PropertyChangedEventArgs und löst das Ereignis anschließend damit aus.

Listing 5.14 zeigt, wie mithilfe der von ViewModelBase vererbten Methode

OnPropertyChanged() ein ViewModel die Bindung aktualisieren kann.

public DayStatus DayStatus {

get { return _dayStatus; } private set { _dayStatus = value; base.OnPropertyChanged("DayStatus"); }

}

Listing 5.14 – Verwendung von OnPropertyChanged()

5.2.2 WorkspaceViewModel

Die WorkspaceViewModel-Klasse erweitert ViewModelBase um eine Eigenschaft vom

Typ RepositoryManager (s. 5.5.2) und stellt die Basisklasse für alle ViewModels dar,

die mit den Daten arbeiten müssen.

Abb. 5.9 – Diagramm: WorkspaceViewModel

45

5.3 Umsetzung des Plug-in-Konzepts

5.3.1 Definieren des Vertrags

Basierend auf den Erkenntnissen aus der Entwurfsphase und an die Anwendung

gestellten Anforderungen, wird zwischen dem Client und den Modulen ein Vertrag in

Form der Schnittstelle IModule definiert.

Abb. 5.10 – Diagramm: IModule

Wie in Abb. 5.10 zu sehen, definiert IModule folgende Eigenschaften:

ForAdminOnly: kennzeichnet ein Modul nur für die Verwendung von

Administratoren.

Name: Name des Moduls.

StartRequests: definiert eine Liste von Abfragen, die beim Start der

Anwendung ausgeführt werden sollen.

View: Haupt-View in Form eines UserControls.

46

5.3.2 Implementierung des Vertrags

Der Vertrag muss von einem Modul implementiert werden, um von der

Laufzeitumgebung eingebunden werden zu können. Dies geschieht mithilfe der

Klasse Module, die im obersten Verzeichnis eines Modul-Projekts definiert wird.

Listing 5.15 zeigt die Implementierung der Schnittstelle IModule von einem Modul für

die Zeiterfassung.

[Export(typeof(IModule))] public class Module : IModule {

public UserControl View { get; set; } public string Name { get; set; } public bool ForAdminOnly { get; set; } public List<RequestBase> StartRequests { get; set; }

[ImportingConstructor] public Module([Import(typeof(RepositoryManager))]RepositoryManager rm) { this.Name = "Projekte/Kunden"; this.ForAdminOnly = true; this.View = new MainView(); this.View.DataContext = new MainViewModel(rm); this.StartRequests = new List<RequestBase>() { new LoadAllEmployees(rm) }; }

}

Listing 5.15 – Implementierung des Vertrags

Für die Instanziierung der Klasse MainViewModel wird eine Instanz von

RepositoryManager benötigt. Damit diese beim Importieren eines Moduls von der

Laufzeitumgebung übergeben werden kann, wird der Konstruktor der Klasse Modul

mit dem ImportingConstructor-Attribut markiert und der Typ des

Übergabeparameters mithilfe des Attributs Import festgelegt.

Da die Verwaltung, in diesem Fall von Projekten und Kunden, nur von

Administratoren durchgeführt werden darf, ist die Eigenschaft ForAdminOnly

entsprechend auf true gesetzt. In diesem Modul erfolgt auch die Zuweisung von

Projekten an entsprechende Mitarbeiter, also wird die Abfrage LoadAllEmployees der

Liste StartRequests hinzugefügt. Somit wird sichergestellt, dass nach dem Start der

Laufzeitumgebung im RepositoryManager sich eine aktuelle Liste von allen

Mitarbeitern befindet, auf die dieses Modul zugreifen kann.

47

5.3.3 ModuleManager

Um das Einbinden von Modulen im Client kümmert sich die Klasse ModuleManager.

Abb. 5.11 – Diagramm: ModuleManager

Die Eigenschaft LoadedModules stellt ein Array vom Type IModule dar. Damit beim

Import der Module diese in LoadedModules abgelegt werden, wird die Property

entsprechend mit dem Attribut ImportMany markiert und der Typ der zu

importierenden Schnittstelle angegeben (s. Listing 5.16).

[ImportMany(typeof(IModule))] private IModule[] LoadedModules { get; set; }

Listing 5.16 – Definieren einer Import-Eigenschaft (Modul-Container)

Um beim Import an die Implementierung der Schnittstelle eine Instanz von

RepositoryManager übergeben zu können, wird die private Eigenschaft

RepositoryManager mit dem Export-Attribute markiert (s. Listing 5.17). Diese

Eigenschaft wird im Konstruktor von ModuleManager mit dem Übergabeparameter

(s. Abb. 5.11) initialisiert.

[Export(typeof(RepositoryManager))] private RepositoryManager RepositoryManager { get; set; }

Listing 5.17 – Definieren einer Export-Eigenschaft

48

Listing 5.18 demonstriert das Laden von Modulen, was mithilfe der Funktion

LoadModules() geschieht. In dieser wird zunächst ein DirectoryCatalog erstellt, der

mit dem Pfad zu dem Ordner in dem die Module in Form von Assemblys vorliegen aus

den Einstellungen initialisiert wird. Mit diesem Katalog wird eine neue Instanz von

CompositionContainer erstellt. Anschließend wird der Import mithilfe der Funktion

ComposeParts() gestartet.

public void LoadModules() {

var catalog = new DirectoryCatalog(...Settings.Default.ExtensionsPath); var container = new CompositionContainer(catalog); container.ComposeParts(this);

}

Listing 5.18 – ModuleManager: LoadModules()

In Listing 5.19 ist die Methode PrepareModules() zu sehen. Diese führt eine Filterung

der importierten Module anhand der Rolle des authentifizierten Benutzers durch und

legt diese im RepositoryManager ab.

public void PrepareModules() {

if (RepositoryManager.LoggedInAs.IsAdmin) RepositoryManager.Modules = LoadedModules.ToList<IModule>(); else RepositoryManager.Modules = LoadedModules.Where(o =>

!o.ForAdminOnly).ToList<IModule>(); }

Listing 5.19 – ModuleManager: PrepareModules()

49

5.3.4 Bereitstellung der Module von der Laufzeitumgebung

Nachdem die Plug-Ins geladen und alle für den Start der Anwendung definierte

Abfragen ausgeführt sind, werden die Module vom Client dem Anwender zur

Verfügung gestellt. Dies geschieht in der MainWindowViewModel-Klasse, die das

zentrale ViewModel der Laufzeitumgebung darstellt und deren View das

Hauptfenster ist.

Abb. 5.12 – Diagramm: MainWindowViewModel

Zu den Aufgaben von MainWindowViewModel zählen in Bezug auf Module folgende:

Generieren der Plug-In Auswahl

Aktivierung eines Plug-Ins

50

Generieren der Plug-In Auswahl

Generierung der Auswahl wird durch die Funktion CreateCommands() bewerkstelligt,

deren Rückgabewert eine Liste vom Typ CommandViewModel ist.

CommandViewModel stellt eine Ableitung von ViewModelBase dar und erweitert

diese um folgende Eigenschaften:

Command: eine Eigenschaft vom Typ ICommand (s. 2.6.3). Dieser wird eine

Instanz des Typs RelayCommand, einer gängigen Implementierung der

ICommand-Schnittstelle, übergeben. Wird benötigt, um auf das Ereignis beim

Klicken auf eine Schaltfläche reagieren zu können.

IsActive: eine boolesche Eigenschaft, um die aktuelle Auswahl mithilfe eines

entsprechenden DataTemplates visuell hervorzuheben.

Abb. 5.13 – Diagramm: CommandViewModel

Wie in Listing 5.20 zu sehen, wird in CreateCommands() die Liste mit den Modulen

iteriert und für jeden Eintrag eine neue Instanz von CommandViewModel erstellt, die

zu der Rückgabeliste hinzugefügt wird.

List<CommandViewModel> CreateCommands() {

List<CommandViewModel> commands = new List<CommandViewModel>();

foreach (IModule module in _repositoryManager.Modules) { commands.Add(new CommandViewModel( module.Name, new RelayCommand(SetActiveWorkspace), false)); }

return commands;

}

Listing 5.20 – MainWindowViewModel: CreateCommands()

51

Listing 5.21 zeigt die Definiton der Commands-Property, die der Haupt-View der

Laufzeitumgebung die CommandViewModels zur Verfügung stellt. Diese besteht aus

einem get-Block, in dem bei einem Zugriff der View zunächst überprüft wird, ob die

Liste bereits erstellt wurde und bei Bedarf CreateCommands() aufgerufen.

Anschließend wird die Liste zurückgegeben.

public ReadOnlyCollection<CommandViewModel> Commands {

get { if (_commands == null) { List<CommandViewModel> cmds = this.CreateCommands();

_commands = new ReadOnlyCollection<CommandViewModel>(cmds); } return _commands; }

}

Listing 5.21 – MainWindowViewModel: Commands

Im Navigationsbereich der Haupt-View befindet sich ein ContentControl (s. Listing

5.22), der sich um die Darstellung der Plug-In Auswahl kümmert. Dessen Eigenschaft

Content ist an die Liste mit CommandViewModels des MainWindowViewModels

gebunden. Durch das Definieren eines DataTemplates in Form von

CommandsTemplate wird das Aussehen der Schaltflächen angepasst. Abb. 5.14 zeigt

die gerenderte Auswahl mit dem aktivierten Modul für die Verwaltung.

<ContentControl Grid.Row="0" Content="{Binding Path=Commands}" ContentTemplate="{StaticResource CommandsTemplate}" Style="{StaticResource MainHCCStyle}" />

Listing 5.22 – MainWindow: ContentControl zur Darstellung der Plug-In Auswahl

Abb. 5.14 – Plug-In Auswahl

52

Aktivierung eines Plug-Ins

Die Aktivierung eines Moduls beschränkt sich auf das Wechseln der aktuellen View im

Content-Bereich der Laufzeitumgebung (s. Abb. 4.3). Dieser ist als ContentControl

definiert, dessen Eigenschaft Content an die Workspace-Property vom Typ

UserControl gebunden ist.

<ContentControl Content="{Binding Path=Workspace}"/>

Listing 5.23 – MainWindow: ContentControl zur Darstellung vom Plug-In-Inhalt

Um die Aktivierung der Module kümmert sich die Funktion SetActiveWorkspace() (s.

Listing 5.24), die als Übergabeparameter den Namen des zu aktivierenden Plug-Ins

erwartet.

void SetActiveWorkspace(object parameter) {

// Get the first module by name var module = _repositoryManager.Modules.First(m =>

m.Name.Equals(parameter.ToString()));

// Set the view Workspace = module.View;

// Set the selected module as active and reset the rest foreach (var cvm in Commands) { if (cvm.DisplayName.Equals(parameter.ToString())) cvm.IsActive = true; else cvm.IsActive = false; }

}

Listing 5.24 – MainWindowViewModel: SetActiveWorkspace()

Zum Aktivieren eines Moduls führt SetActiveWorkspace() folgende Schritte durch:

Anhand des Übergabeparameters wird mithilfe einer LINQ-Abfrage das

entsprechende Modul in der Modul-Liste gefunden und zwischengespeichert.

Die Eigenschaft Workspace wird auf die View des aktivierten Moduls gesetzt

und wechselt somit den Inhalt des Content-Bereichs der Laufzeitumgebung.

Anschließend wird der Status der Schaltflächen aktualisiert.

53

5.4 Authentifizierung

Die Authentifizierung erfolgt in Form der Abfrage Authenticate, die eine Ableitung

von RequestBase (s. 5.1.3) darstellt. Authenticate greift intern auf den Service zu, der

seinerseits mit Active Directory kommuniziert und den Benutzer anhand seiner SID

authentifiziert.

Listing 5.25 zeigt die Implementierung der Abfrage Authenticate, die folgende

Schritte durchführt:

Zunächst wird die SID des aktuell angemeldeten Benutzers mithilfe der Klasse

WindowsIdentity ermittelt.

Die SID wird zu einem String konvertiert und anschließend an den Service

weitergegeben.

Ist die Authentifizierung erfolgt, so wird das vom Service gelieferte

Authentifizierungsobjekt im RepositoryManager abgelegt.

public class Authenticate : RequestBase { Employee _user; public Authenticate(RepositoryManager repositoryManager) : base(repositoryManager) { ProgressDescription = "Authentifizierung..."; } protected override void DoWork() { // Get the identity from user WindowsIdentity id = new WindowsIdentity(Environment.UserName); _user = ServiceCommunication.Client.Authentication(id.User.ToString()); } protected override void FinishWork() { _user.IsAdmin = true; RepositoryManager.LoggedInAs = _user; } }

Listing 5.25 – Authentifizierung

In Active Directory sind für die Anwendung zwei Sicherheitsgruppen angelegt, mit

deren Hilfe die Rollenverwaltung (Admin/Benutzer) realisiert wird. Diesen Gruppen

können einzelne Mitarbeiter zugewiesen werden oder auch weitere

Sicherheitsgruppen, die ebenfalls weitere Gruppen enthalten können.

54

Um die Feststellung, ob ein Benutzer sich in einer bestimmten Sicherheitsgruppe

befindet, kümmert sich die statische Funktion IsMemberOf(), die rekursiv arbeitet

und sich in der Helferklasse Authentication befindet.

Wie in Listing 5.26 zu sehen, benötigt die Funktion als Übergabeparameter die

abzufragende Gruppe in Form eines Objekts vom Typ GroupPrincipal und die SID des

Benutzers, nach der in der Gruppe gesucht werden soll.

Die Funktion GetMembers() von groupPrincipal liefert eine Liste mit den Mitgliedern

dieser Gruppe zurück, die iteriert und dabei jeder Eintrag auf seinen Typ überprüft

wird. Handelt es sich bei dem Eintrag um eine Gruppe, so wird IsMemberOf() auf

diese rekursiv angewandt. Ist der Eintrag hingegen keine Gruppe, erfolgt ein

Vergleich zwischen den SID’s.

public static bool IsMemberOf(GroupPrincipal groupPrincipal, string userSID) {

PrincipalSearchResult<Principal> results = groupPrincipal.GetMembers();

if (results != null) { foreach (var member in results) { // Member is a group? -> iterate it

if (member.GetType().Equals(typeof(GroupPrincipal))) { if (IsMemberOf(member as GroupPrincipal, userSID) ==

true) return true; } else { if (member.Sid.ToString().Equals(userSID)) return true; } } } return false;

} Listing 5.26 – Authentication: IsMemberOf()

Auf der Service-Seite werden bei der Authentifizierung zunächst zwei

Sicherheitsgruppen mit den von Active Directory vorgegebenen SID’s definiert. Auf

diese Gruppen wird die Funktion IsMemberOf() angewandt und das Ergebnis

zwischengespeichert. Liefert die Funktion in beiden Fällen ein false zurück, so schlägt

die Authentifizierung fehl und liefert ein ungültiges Authentifizierungsobjekt zurück.

Bei der erfolgreichen Ermittlung der Rolle wird zudem überprüft, ob der Benutzer

bereits in der Datenbank existiert, und bei Bedarf angelegt. Anschließend wird die

Rolle des Authentifizierungsobjekts gesetzt und das Objekt zurückgegeben.

55

5.5 Datenzugriff im Client

5.5.1 Repository

Die generische Klasse Repository stellt den Container für die Daten (Models) der

Anwendung dar und arbeitet intern mit einer Liste vom Typ ObservableCollection.

Diese Liste bietet ein Ereignis namens CollectionChanged an, mit dessen Hilfe die

ViewModels auf die Änderungen in der Datenauflistung reagieren können.

Abb. 5.15 - Diagramm: Repository

Repository vereinfacht den Umgang mit der Liste, indem sie für die meistgenutzten

Aufgaben die s.g. Wrapper-Funktionen bereitstellt. In der Funktion Add() wird

beispielsweise zunächst überprüft, ob es sich bei dem übergebenen Objekt um eine

gültige Instanz handelt, und bei Bedarf eine entsprechende Exception geworfen.

Enthält die Liste das Objekt bereits, wird ein false zurückgegeben, um das Ereignis

CollectionChanged nicht unnötig auszulösen.

Um den Datentyp, den die Repository-Klasse aufnimmt, auf die Ableitungen der

Klasse DataObjectBase zu beschränken, die die Oberklasse für alle Models darstellt,

wird bei der Deklaration der Klasse eine entsprechende where-Anweisung angegeben

(s. Listing 5.27).

public class Repository<T> : IDisposable where T : DataObjectBase

Listing 5.27 – Deklaration der Repository-Klasse

56

5.5.2 RepositoryManager

Die Klasse RepositoryManager verwaltet die Repositorys und stellt den zentralen

Zugriffspunkt auf die Daten der Anwendung dar.

Abb. 5.16 – Diagramm: RepositoryManager

Durch das Definieren von Funktionen für den Zugriff auf die Repositorys gestaltet sich

der Umgang mit den Daten in den ViewModels, wie in Listing 5.28 zu sehen, sehr

einfach.

this.RepositoryManager.GetRepository<Workday>().Add(workday);

Listing 5.28 – Beispiel: Verwendung des RepositoryManagers

Wegen der zentralen Stelle der Klasse, wird diese um einige zusätzliche Eigenschaften

erweitert, auf die jedes ViewModel Zugriff haben muss, wie beispielsweise der

RequestManager für eine komfortable Möglichkeit eine Reihe von Abfragen

ausführen zu können oder das Authentifizierungsobjekt in Form der Eigenschaft

LoggedInAs.

57

6 Fazit und Ausblick

Das Ziel dieser Arbeit war das Entwickeln einer zur Laufzeit erweiterbaren,

serviceorientierten und möglichst generischen Anwendung, die mit wenig Aufwand

an unterschiedlichste Anforderungen angepasst werden kann.

Letztendlich ließ sich das Plug-In-Konzept mit MEF leichter als gedacht umsetzen. Das

Framework für erweiterbare Anwendungen ist sehr mächtig und zudem logisch

aufgebaut. Mit MEF stellt .NET eine elegante Lösung zum Problem der

Erweiterbarkeit einer Anwendung zur Laufzeit bereit.

Der Datenzugriff und die Entwicklung des Datenmodells wurden durch das Einsetzen

des Entity Frameworks erheblich erleichtert. Die vom ORM generierten Entitys lassen

sich mithilfe von Datenverträgen serialisieren, was eine problemlose Nutzung des

Frameworks für den Datenzugriff im WCF-Service ermöglicht.

Die Entwicklung mit WPF nach dem MVVM-Muster hat zur Folge, dass durch die

weitestgehende Trennung der Logik von der Benutzeroberfläche, die Anwendung sich

gut überblicken und testen lässt. Die saubere Umsetzung des Musters ist jedoch nicht

immer problemlos möglich. WPF definiert eine Reihe von Ereignissen für die

Steuerelemente, deren Behandlung in der Anwendung manchmal notwendig ist. An

die Ereignisse lässt sich das ViewModel aber nicht direkt binden und deren

Behandlung in der CodeBehind-Datei würde den Vorgaben des Entwurfsmusters

widersprechen. Es existiert aber eine Reihe von Bibliotheken, die sich dieser

Problemstellung widmen und MVVM-konforme Lösungen anbieten.

Insgesamt lässt sich sagen, dass dieses Projekt die gesetzten Ziele erfüllt und für

erweiterbare Anwendungen unter .NET als Grundlage verwendet werden kann. Die

auf dieser Basis entwickelte Zeiterfassung der Firma Betex wird intern derzeit

erfolgreich eingesetzt.

Momentan ist für die Zeiterfassung die Entwicklung von zusätzlichen Plug-Ins geplant.

Über eine spätere Nutzung des Projekts in kommerziellen Zwecken wird ebenfalls

nachgedacht.

58

7 Listingverzeichnis

Listing 2.1 – Verwendung einer Eigenschaftsmethode .............................. 3 Listing 2.2 – Beispiel: Verwendung von partiellen Klassen ....................... 4 Listing 2.3 – Beispiel: Datenzugriff mithilfe von LINQ .......................... 4 Listing 2.4 – Beispiel: Datenzugriff ohne LINQ .................................. 4 Listing 2.5 – Umgang mit dem Kontext ............................................ 6 Listing 2.6 – Definieren eines WCF-Vertrags ..................................... 7 Listing 2.7 – Definieren eines Datenvertrags .................................... 8 Listing 2.8 – Definieren von Imports und Exports ................................ 9 Listing 2.9 - Beispiel einer Komposition mit MEF ............................... 10 Listing 2.10 – Beispiel: XAML-Ausschnitt ....................................... 13 Listing 2.11 – Direktes setzen des DataContexts der View im Codebehind ......... 15 Listing 2.12 – Setzen des DataContexts der View per DataTemplate ............... 15 Listing 2.13 – Data Binding .................................................... 16 Listing 2.14 – Das Interface ICommand .......................................... 16 Listing 2.15 – Das Interface INotifyPropertyChanged ............................ 17 Listing 2.16 – Aktualisierung der Bindung mit dem Event PropertyChanged ........ 17 Listing 5.1 – Ausschnitt aus dem Servicevertrag ................................ 31 Listing 5.2 – Datenvertrag für Employee ........................................ 33 Listing 5.3 – Erweitern eines EntityObjects um eine Konvertierungsmethode ...... 33 Listing 5.4 – Implementierung des Servicevertrags .............................. 35 Listing 5.5 – Datenzugriff im DataManager ...................................... 35 Listing 5.6 – RequestBase: DoWork() ............................................ 38 Listing 5.7 – RequestBase: FinishWork() ........................................ 38 Listing 5.8 – RequestBase: Start() ............................................. 38 Listing 5.9 – Beispiel einer Abfrage ........................................... 39 Listing 5.10 – RequestManager: AddRequest() .................................... 40 Listing 5.11 – RequestManager: OnRequestFinished() ............................. 41 Listing 5.12 – Beispiel: Verwendung von RequestManager ......................... 41 Listing 5.13 – ViewModelBase: OnPropertyChanged() .............................. 43 Listing 5.14 – Verwendung von OnPropertyChanged() .............................. 44 Listing 5.15 – Implementierung des Vertrags .................................... 46 Listing 5.16 – Definieren einer Import-Eigenschaft (Modul-Container) ........... 47 Listing 5.17 – Definieren einer Export-Eigenschaft ............................. 47 Listing 5.18 – ModuleManager: LoadModules() .................................... 48 Listing 5.19 – ModuleManager: PrepareModules() ................................. 48 Listing 5.20 – MainWindowViewModel: CreateCommands() ........................... 50 Listing 5.21 – MainWindowViewModel: Commands ................................... 51 Listing 5.22 – MainWindow: ContentControl zur Darstellung der Plug-In Auswahl .. 51 Listing 5.23 – MainWindow: ContentControl zur Darstellung vom Plug-In-Inhalt ... 52 Listing 5.24 – MainWindowViewModel: SetActiveWorkspace() ....................... 52 Listing 5.25 – Authentifizierung ............................................... 53 Listing 5.26 – Authentication: IsMemberOf() .................................... 54 Listing 5.27 – Deklaration der Repository-Klasse ............................... 55 Listing 5.28 – Beispiel: Verwendung des RepositoryManagers ..................... 56

59

8 Abbildungsverzeichnis

Abb. 2.1 - Einsetzen eines O/R-Mappers .......................................... 5 Abb. 2.2 – Beispiel: Mit XAML erstellte GUI .................................... 13 Abb. 2.3 – Die Abhängigkeiten beim MVVM-Pattern ................................ 14 Abb. 2.4 – Active Directory in einem Windows Server Netzwerk (Quelle: Microsoft) 18 Abb. 3.1 – Service-orientierte Architektur ..................................... 22 Abb. 3.2 – Grobentwurf ......................................................... 23 Abb. 4.1 – Hierarchische Struktur eines Moduls ................................. 24 Abb. 4.2 – Grundlegende Projektstruktur ........................................ 25 Abb. 4.3 – GUI Entwurf der Laufzeitumgebung .................................... 26 Abb. 4.4 – Flexibler Datenzugriff durch den DataManager ........................ 27 Abb. 4.5 – Datenhaltung im Client .............................................. 28 Abb. 4.6 – Gesamtarchitektur ................................................... 29 Abb. 5.1 – Diagramm: Servicevertrag ............................................ 30 Abb. 5.2 – Diagramm: Objektmodell .............................................. 31 Abb. 5.3 – Diagramm: Datenverträge ............................................. 32 Abb. 5.4 – Diagramm: DbConnectionData .......................................... 34 Abb. 5.5 – Diagramm: DataManagerBase ........................................... 34 Abb. 5.6 – Diagramm: RequestBase ............................................... 37 Abb. 5.7 – Diagramm: RequestManager ............................................ 40 Abb. 5.8 - Diagramm: ViewModelBase ............................................. 42 Abb. 5.9 – Diagramm: WorkspaceViewModel ........................................ 44 Abb. 5.10 – Diagramm: IModule .................................................. 45 Abb. 5.11 – Diagramm: ModuleManager ............................................ 47 Abb. 5.12 – Diagramm: MainWindowViewModel ...................................... 49 Abb. 5.13 – Diagramm: CommandViewModel ......................................... 50 Abb. 5.14 – Plug-In Auswahl .................................................... 51 Abb. 5.15 - Diagramm: Repository ............................................... 55 Abb. 5.16 – Diagramm: RepositoryManager ........................................ 56

60

9 Literaturverzeichnis

[1] A. Kühnel, Visual C# 2010: Das umfassende Handbuch (Galileo Computing), 2010. [2] D. H. Schwichtenberg,

„http://www.dotnetpro.de/Grafix/OnlineArticles/ormapper.pdf,“ dotnetpro, 2008. [Online].

[3] Microsoft, „http://msdn.microsoft.com/de-de/library/system.data.objects.objectcontext.aspx,“ [Online].

[4] „WCF - Wikipedia Enzyklopädie,“ [Online]. Available: http://de.wikipedia.org/wiki/Windows_Communication_Foundation.

[5] Microsoft, „Verwenden von Datenverträgen,“ [Online]. Available: http://msdn.microsoft.com/de-de/library/ms733127.aspx.

[6] „MEF - MSDN,“ [Online]. Available: http://msdn.microsoft.com/de-de/library/ee332203.aspx.

[7] „Übersicht über Managed Extensibility Framework - MSDN,“ [Online]. Available: http://msdn.microsoft.com/de-de/library/dd460648.aspx.

[8] T. C. Huber, Windows Presentation Foundation, 2010.

[9] „Active Directory – Wikipedia Enzyklopädie,“ Juni 2012. [Online]. Available: http://de.wikipedia.org/wiki/Active_Directory.

[10] Microsoft, „Active Directory - Gruppentypen,“ [Online]. Available: http://technet.microsoft.com/de-de/library/cc781446(v=ws.10).

[11] „SID - Wikipedia Enzyklopädie,“ April 2012. [Online]. Available: http://de.wikipedia.org/wiki/Security_Identifier.

[12] „LDAP – Wikipedia Enzyklopädie,“ August 2012. [Online]. Available: http://de.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol.

[13] „IIS - Wikipedia Enzyklopädie,“ [Online]. Available: http://de.wikipedia.org/wiki/Microsoft_Internet_Information_Services.

[14] „Clean Code Developer,“ [Online]. Available: http://www.clean-code-developer.de/Separation-of-Concerns-SoC.ashx.