Skript: WPF Net

Transcription

Skript: WPF Net
.Net Framework und C#
SS 2010
Dr. Ute Blechschmidt-Trapp
Darmstadt, 20. März 2010
Inhaltsverzeichnis
1 Einleitung
4
1.1
Motivation
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2
Literatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
1.3
Aufbau
5
1.4
Schnellstart Framework
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
6
2 Windows Forms
7
3 C# für Entwicklerinnen
9
3.1
3.2
3.3
3.4
3.5
3.6
Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
3.1.1
Operatoren
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
3.1.2
Partial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
3.1.3
Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
Typen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
3.3.1
Strukturen
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
3.3.2
Generika . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
3.3.3
Listen
16
3.3.4
Enumerationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Sprachbesonderheiten
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
18
3.4.1
Log/Trace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
3.4.2
Ausnahmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
3.4.3
Indexer
20
3.4.4
Iteratoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Delegaten
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
20
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21
3.5.1
Mulitcastdelegaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
3.5.2
Schnittstelle vs. Delegate
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
24
3.5.3
Ereignisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25
Attribute und Reektion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
28
3.6.1
Attribute
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
28
3.6.2
Reektion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29
3.6.3
Zugri auf Attribute mit Reektion
29
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.7
Lambda-Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30
3.8
IDisposable
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30
3.9
using . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
32
3.10 Serialisierung
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.11 XML-Dokumentationskommentare
33
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
34
3.12 Namenskonventionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
34
3.13 Aufgaben
34
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4 Komponenten
37
4.1
Eigene GUI Steuerelemente
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2
Managed Extensibility Framework (MEF) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
39
4.3
Managed Add-in Framework (MAF)
40
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
39
4.3.1
Laden eines Add-Ins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
4.3.2
Modell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
5 Windows Presentation Foundation
45
5.1
Architektur
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
46
5.2
Ereignisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
48
5.3
WinForm und WPF
48
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6 Datenbankanbindung
6.0.1
.NET Framework-Datenanbieter
49
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
49
Inhaltsverzeichnis
6.1
6.0.2
DataSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
51
6.0.3
DataSet oder DataReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52
6.1.1
Entity Framework
Mapping von Objekten zu Daten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
53
6.1.2
Zugreifen auf und Ändern von Entitätsdaten
6.1.3
Datenmodellierung im Entity Framework
. . . . . . . . . . . . . . . . . . . . . . . . .
53
. . . . . . . . . . . . . . . . . . . . . . . . . . .
54
6.2
LINQ
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
54
6.3
Datenbindung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
56
6.4
6.3.1
Datenbindung in WinForm
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
56
6.3.2
Datenbindung in WPF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
6.3.3
Datenbindung in Asp.Net
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
58
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
58
Aufgaben
7 Webentwicklung
7.1
7.2
Asp.Net
59
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Masterseiten
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
60
7.1.2
Lebenszyklus einer Seite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
60
7.1.3
Ereignismodell für ASP.NET-Webserversteuerelemente . . . . . . . . . . . . . . . . . . . .
61
7.1.4
Zustandsverwaltung
62
7.1.5
Konguration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63
7.1.6
Ajax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
64
Asp.Net MVC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
64
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8 Windows Communication Foundation
67
8.1
Problembeispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
67
8.2
Architektur
69
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.2.1
Dienstlaufzeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
69
8.2.2
Messaging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
69
8.2.3
Hosting und Aktivierung
70
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9 Windows Workow Foundation
71
9.1
Statuscomputerworkows
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71
9.2
Sequenzielle Workows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
72
10 .Net Framework
3
60
7.1.1
73
10.1 Vokabeln
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
74
10.2 Assembly
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
78
10.3 Anwendungsdomäne (AppDomain) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
80
10.4 Basisklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
81
10.5 Typsystem von C# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
81
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
1 Einleitung
1.1 Motivation
Warum biete ich dieses Wahlfach an? Ich habe 10 Jahre bei einem zertizierten Partner von Microsoft gearbeitet
und dabei die Microsoft Entwicklungsumgebungen und Möglichkeiten kennen und auch zu schätzen gelernt. Neben Java ist C# die Programmiersprache, die in Stellenanzeigen und Artikeln am Häugsten genannt wird. Auf
Client-Maschinen ist Windows immer noch das meist verbreitete Betriebssystem (93stellt Microsoft immerhin
24% der Server - Apache 54%. Sicher, Java und plattformunabhängige Technologien sind primär zu erlernen.
Der Markt wünscht aber auch Entwicklerinnen, die sich im Microsoftumfeld auskennen. Dies zeigt eine Statistik
1
des Freelancerportals Gulp
2
oder auch ein Artikel bei heise .
Die verschiedenen aktuellen Technologien von Microsoft sind mächtig und spannend und es macht Spaÿ, mit
einer komfortablen IDE zu arbeiten. Wer sich mit seiner geplanten Anwendung im Microsoftumfeld bewegt,
sollte die Möglichkeiten von .Net nicht verzichten. Selbst, wer plattformübergreifend denkt und arbeitet, kann
von .Net protieren, denn es gibt zunehmend mehr Projekte, in denen Java und C# zusammen eingesetzt
werden.
Leider oder zum Glück gibt es im .Net Umfeld ca. alle 2 Jahre eine neue Version. Glück für die Entwicklerinnen,
die stumpfsinniges Arbeiten durch mächtige Werkzeuge und Bibliotheken weiter abgenommen bekommen und
leider, da sich die Prinzipien dadurch auch grundlegend ändern können. Was ist also das bleibende Wissen,
das in einer Hochschulveranstaltung vermittelt werden soll? Für Entwicklerinnen, die bereits eine OO-Sprache
beherrschen, kann dies nicht das Erlernen der Sprache C# sein. Das Erlernen einer weiteren OO-Sprache ist
keine Herausforderung. C# und Java sind sehr ähnlich. In dieser Veranstaltung werden wir uns also auf die
Unterschiede konzentrieren. Der Fokus dieser Vorlesung liegt auf dem Arbeiten mit .Net, wo bekomme ich Hilfe,
was sind momentan die empfohlenen Vorgehensweisen und wesentlichen Technologien?
Dies ist also kein Anfängerprogrammierkurs für C#, sondern ich versuche, Hochsprachkonzepte am Beispiel von
C# darzustellen, z.B. was ist Delegation? Sie erarbeiten durch geeignete Aufgaben die konzeptuellen Unterschiede zu Java.
Jedes Kapitel enthält eine Fülle von Links, die weiterführende Informationen enthalten. Das Skript ist eine
Sammlung vieler Quellen, vorwiegend aus Microsofts Feder die Ernder können am Besten ausdrücken, was
sie womit meinen und wollen. Marketing-Sprüche habe ich weitestgehend entfernt. .
Zeitreise: 1980 startete Microsoft mit 40 Mitarbeitern. In den 80ern und 90ern verfolgte das Unternehmen eine
abschottende Politik, d.h. Betriebssystem und Anwendungen waren gegenüber Erweiterungen und Austausch
von Anwendungsdaten auf Microsoft-Produkte beschränkt. In dieser Zeit entwickelte sich insbesondere in den
Hochschulen und einer aktiven Entwicklergemeinde ein relativ schlechtes Image von Microsoft. Das Unternehmen hat seine Strategie geändert, Quellcodes werden veröentlicht, Microsoft arbeitet in vielen Gremien an
Standards mit, das .Net Framework kann von beliebigen Sprachen genutzt werden, sofern ein entsprechender
Vorcompiler existiert und auch der Datenaustausch zwischen Oceanwendungen und Drittanbietern ist Dank
XML möglich.
1.2 Literatur
Es gibt viele dicke Bücher zu C#, Asp.Net und anderen Themen dieser Vorlesung. Es gibt meiner Meinung
nach wenige gute Bücher, die für Quereinsteiger von anderen OO-Sprachen geschrieben werden, so dass Sie viel
Geld für Gedrucktes, dass Sie aus anderen Veranstaltungen kennen, ausgeben. Dies, die Schnelllebigkeit von
Microsoft-Technologien und die guten Online-Materialien führen zu folgender Linkliste:
•
Microsoft
MSDN Das Microsoft Developer Network
http://msdn.microsoft.com
1 Marktstudie: Begehrte Programmiersprachen
2 Experten langfristig gefragt
4
1.3 Aufbau
Channel9 Videos und anderes Lernmatieral
patterns & practices:
Microsoft
Magazin
•
http://channel9.msdn.com/learn/
http://msdn.microsoft.com/en-us/practices/default.aspx
Application
Architecture
library/dd673617.aspx
Guide,
2nd
Edition
http://msdn.microsoft.com/en-us/
http://msdn.microsoft.com/en-us/magazine/default.aspx
Visual Studio 2010 and .NET Framework 4 Training Kit http://www.microsoft.com/downloads/
details.aspx?familyid=752CB725-969B-4732-A383-ED5740F02E93&displaylang=en
Developer Blogs
http://blogs.msdn.com/
http://weblogs.asp.net/
•
Andere
Das war das .NET-Jahr 2009 - ein Rückblick http://www.heise.de/developer/artikel/
Das-war-das-NET-Jahr-2009-ein-Rueckblick-908768.html
Visual Studio Magazine
BlogBook
Vorlesung in die Tiefen von .Net
http://visualstudiomagazine.com
http://www.dotnet-blogbook.com/Download.aspx
http://svn.ipd.uka.de/lehre/vorlesung/NET/download/
(mit
anonymous anmelden)
•
Code
Codeproject
Codeplex
Design Patterns
http://www.codeproject.com/
http://cfx.codeplex.com
http://www.dofactory.com
1.3 Aufbau
Ein Kurzeinblick in die Arbeitsweise des Frameworks im folgenden Abschnitt gibt Ihnen den nötigen Hintergrund
und die Basis zum Verständnis der folgenden Kapitel. Mit WinForm wurden früher Client-GUI-Applikationen
entwickelt. Diese Technologie wird zunehmend von Windows Presentation Foundation abgelöst. Dennoch starten wir in Kapitel 2 mit WinForm, so dass die weiteren Applikationen im Praktikum nicht nur auf der Console
laufen müssen. In Kapitel 3 lernen Sie die grundlegende Syntax und die Besonderheiten von C# kennen. Das
Framework zeichnet sich durch das Zusammenspiel von verschiedenen Komponenten aus. Wie Sie selbst komponentenbasierte und durch Komponenten zur Laufzeit erweiterbare Applikationen entwickeln können, erfahren
Sie in Kapitel 4. Die Basis ansprechender Benutzeroberächen in .Net bildet Windows Presentation Foundation. In diese Technologie führt Kapitel 5 ein. Kapitel 6 beschäftigt sich mit allen Fragen der Anbindung an
Datenbanken. Die Grundlagen der Webentwicklung stellt Kapitel 7 vor. In Kapitel 8 erhalten Sie eine kurze
Einführung in Windows Communication Foundation und in Kapitel 9 eine kurze Einführung in Windows Workow Foundation. Abschlieÿend schauen wir uns das .Net Framework in Kapitel 10 noch einmal genauer an wobei ich aufgrund des Umfangs der Vorlesung Sicherheitsfragen leider nicht genauer betrachten kann.
Dies ist eine Alpha-Version des Skripts. Informieren Sie mich bitte über Fehler, Unklarheiten, Lücken, machen
Sie Vorschläge für eine bessere Struktur, verständlichere Formulierungen, Übungsaufgaben, Schaubilder. Ich
freue mich über jede konstruktive Kritik.
Obwohl ich viele Jahre im Microsoftumfeld entwickelt habe, sind mir einige der hier vorgestellten Technologien
auch nur aus der Theorie oder in älterer Version bekannt. In der Produktentwicklung hat man immer auch mit
Altlasten zu kämpfen, Kundenserver werden nicht leichtfertig auf neue Versionen umgestellt, so dass aktuelle
Technologien nur bedingt zeitnah in den Entwicklungsabteilungen ankommen. Wenn Sie Erfahrungen mit .Net
haben, bringen Sie diese bitte ein!
5
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
1.4 Schnellstart Framework
Abbildung 1.1: Ausführungsmodell
1.4 Schnellstart Framework
In Abschnitt 10 auf Seite 73 schauen wir uns das .Net Framework genauer an. Für das Verständnis einiger
Eigenschaften von C# und die ersten Aufgaben im Praktikum, ist jedoch ein grobes Verständnis hilfreich. .Net
Framework hat ähnlich wie Java eine Laufzeitumgebung, die vorcompilierten Code ausführt.
Mit einem Vorcompiler wird Programmcode in die Common Intermediate Language (CIL) (teilweise auch nur
Intermediate Language (IL) genannt) übersetzt. CIL ist eine objektorientierte Assemblersprache. Das physikalische Ergebnis dieses Compilevorgangs sind die sogenannten Assemblies (siehe Abschnitt 10.2 auf Seite 78). Der
Just in time compiler (JIT) erzeugt daraus sogenannten Managed Code, der in der Common Language Runtime
(CLR) für das jeweilige Betriebssystem umgesetzt wird. Managed Code ist per Denition Code, der von der
CLR interpretiert wird. Jeder andere Code ist unmanaged.
Abbildung 1.2: Common Type System
In .Net sind alle Sprachen gleichwertig. Ein einheitliches Typsystem ermöglicht das direkte Zusammenspiel einzelner Assemblies, die in unterschiedlichen Sprachen implementiert sein können. Dabei ist zu beachten, dass
alles ein Objekt ist. Werttypen landen auf dem Stack und alle anderen Typen auf dem Heap, der vom Garbage
Collector der CLR gegebenenfalls aufgeräumt wird. Entwickler müssen sich im Allgemeinen nicht um die Speicherverwaltung kümmern, sollten aber im Hinterkopf behalten, dass alles auf dem Heap mit Adressen zu tun
hat.
6
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
2 Windows Forms
Abbildung 2.1: Windowsentwicklung
Microsoft startete mit einem erfolgreichen Betriebssystem und Software (Oce) für Client-Rechner. Daher haben
sich Entwicklerinnen, die für Microsoft-Betriebssysteme entwickelt haben, auf Client-Entwicklung fokusiert.
Hierin liegt der Ursprung und die Stärke von VB6, schnell Produkte oder Ergänzungen (damals als ActiveX)
zur Oce-Palette zu entwickeln, die Aufgaben von Anwendern automatisieren oder unterstützen.
1 Windows
Forms (WinForm) war lange Zeit die Basis zur Erstellung von fensterbasierten Windows-Desktop-
Anwendungen. War, denn mit Windows Presentation Foundation (WPF) ist Microsoft den nächsten Schritt
gegangen. Das Ergebnis sehen Sie auch in der IDE VS2010, die komplett auf WPF basiert. Mehr dazu in
Abschnitt 5 auf Seite 45.
Dennoch möchte ich mit WinForm beginnen. WinForm ist eine ereignisbasierte Technologie für Fensteroberächen, wie viele andere GUI-Technologien auch.
Die Erstellung der Oberäche gestaltet sich sehr einfach, mit dem Designer im VS die entsprechenden Elemente
auf die Oberäche ziehen, possitionieren, Eigenschaften setzen und implementieren, wie auf welche Eigenschaften
reagiert werden soll. Es gibt also immer eine Design-Ansicht und eine Implementationssicht - die Designansicht
lässt sich auch im Programmcode ansehen und ggf. verändern. WinForms sind prinzipiell pixelbasierte, absolut
possitionierte Fenster.
Windows Forms enthält eine Vielzahl von Steuerelementen, die Sie Formularen hinzufügen können: Steuerelemente zur Anzeige von Textfeldern, Schaltächen, Dropdownfeldern, Optionsfeldern und sogar Webseiten.
Wenn ein vorhandenes Steuerelement Ihre Anforderungen nicht erfüllt, unterstützen Windows Forms auch die
Erstellung eigener benutzerdenierter Steuerelemente mithilfe der UserControl-Klasse.
Ein Doppelklick im Forms-Designer bewirkt, dass das Visual Studio eine Methode für das Standardereignis der
angeklickten Komponente erstellt. Um abweichend vom Standardereignis ein anderes zu behandeln, können Sie
das Eigenschaftsfenster einsetzen, nachdem Sie in der Symbolleiste auf die Ereignisliste umgeschaltet haben.
Wenn Sie auf ein Ereignis in der Ereignisliste, das Sie behandeln wollen, doppelklicken, wird automatisch ein
Ereignishandler erstellt, dessen Bezeichner sich aus dem Objektnamen und dem Ereignis zusammensetzt. Die
Bindung des Ereignishandlers nden Sie in InitializeComponent. Sie können dem Ereignishandler auch eine
Bezeichnung geben, die von der üblichen Vorgabe abweicht. Dazu tragen Sie nur den von Ihnen bevorzugten
Methodennamen in der Wertespalte neben dem Ereignis manuell ein.
Möchten Sie einen Ereignishandler gleichzeitig für mehrere Ereignisse registrieren, ist das auch sehr einfach
zu lösen. Aktivieren Sie die Wertespalte zu einem Ereignis in der Ereignisliste, sehen Sie eine Schaltäche mit
einem Pfeilsymbol. Sie können darüber eine Liste önen, in der alle Ereignishandler aufgeführt sind, die im
1 Übersicht
über Windows Forms
7
2 Windows Forms
Args-Parameter denselben Typ haben. Wählen Sie daraus den Ereignishandler aus, der das markierte Ereignis
verarbeiten soll.
Wenn Sie Fenster innerhalb anderer Fenster anordnen möchten, dann setzten sie die Eigenschaft IsMdiContainer
auf wahr.
Aufgabe 2.1
Analysieren Sie die vorhandenen Steuerelemente und beschreiben Sie in einem Satz, wofür wel-
ches Steuerelement verwendet wird.
8
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3 C# für Entwicklerinnen
Die folgenden Beispiele zeigen die Syntax von C# exemplarisch. In den Unterabschnitten werden Besonderheiten der Sprache dargestellt. Der dazugehörige Text und die Beispiele stammen vorwiegend aus dem C#Programmierhandbuch von Microsoft. Die Besonderheiten von C# im Vergleich zu Java listet Microsoft selbst
auf: C# für Java-Entwickler
3.1 Syntax
Listing 3.1: C# Grundlagen
//Klasse − Konstruktoren
using System;
/∗ Blockkommentar ∗/
abstract class Shape
{
public const double pi = Math.PI;
protected double x, y, z;
public Shape (double x, double y, double z)
{
this.x = x;
this.y = y;
this.z = z;
}
}
public abstract double Area();
class Circle: Shape
{
public Circle(): this(100) //default radius is 100
{
}
public Circle(double radius): base(radius,0,0)
{
}
}
public override double Area()
{
return pi*x*x;
}
class Cylinder: Circle
{
public Cylinder(double radius, double height): base(radius)
{
y = height;
}
public override double Area()
{
return 2*(base.Area()) + 2*pi*x*y;
}
}
class TestClass
{
public static void Main()
{
double radius = 2.5, height = 3.0;
Circle myCircle = new Circle(radius);
Cylinder myCylinder = new Cylinder(radius, height);
Console.WriteLine("Area of the circle = {0:F2}",
myCircle.Area());
9
3.1 Syntax
}
}
Console.WriteLine("Area of the cylinder = {0:F2}",
myCylinder.Area());
// versiegelte Klassen = nal in Java
public sealed class D
{
}
// Class members here.
//statische Klassen
public static class TemperatureConverter
{
public static double CelsiusToFahrenheit(string temperatureCelsius)
{
...
}
}
double F, C = 0;
F = TemperatureConverter.CelsiusToFahrenheit(Console.ReadLine());
// Schnittstellen
interface IEquatable<T>
{
bool Equals(T obj);
}
public class Car : IEquatable<Car> //weitere implementierte Interfaces durch , abtrennen
{
private string make;
private string model;
public string Make
{
get {return make;}
set {make = value;}
}
public string Model
{
get {return model;}
set {model = value;}
}
// Implementation of IEquatable<T> interface
}
public bool Equals(Car car)
{
if (this.Make == car.Make && this.Model == car.Model)
{
return true;
}
else
return false;
}
//If Statement
if (condition)
{
/∗ code ∗/
}
else if (otherCondition)
{
}
else
{
}
/∗ code ∗/
/∗ code ∗/
//switch − geht auch mit String
int cost = 0;
switch(n)
{
case 1:
cost += 25;
10
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.1 Syntax
break;
case 2:
cost += 25;
goto case 1;
case 3:
cost += 50;
goto case 1;
default:
Console.WriteLine("Invalid selection. Please select 1, 2, or 3.");
break;
}
//For Schleife
int[] myArray;
for (int i = 0; myArray.Length; i++)
{
Console.WriteLine(myArray[i]);
}
//Foreach Schleife
int[] myArray;
foreach (int element in myArray)
{
Console.WriteLine(element);
}
//Using Statement
using (StreamWriter sw = new StreamWriter(@"C:\MyFile.txt")
{
sw.Write("Hello, File");
}
//Arrays
// Declare a single −dimensional array
int[] array1 = new int[5];
// Declare and set array element values
int[] array2 = new int[] { 1, 3, 5, 7, 9 };
// Alternative syntax
int[] array3 = { 1, 2, 3, 4, 5, 6 };
// Declare a two dimensional array
int[,] multiDimensionalArray1 = new int[2, 3];
// Declare and set array element values
int[,] multiDimensionalArray2 = { { 1, 2, 3 }, { 4, 5, 6 } };
// Declare a jagged array
int[][] jaggedArray = new int[6][];
// Set the values of the rst array in the jagged array structure
jaggedArray[0] = new int[4] { 1, 2, 3, 4 };
Gleichheit von Objekten:
Zwei Objekte sind gleich, wenn deren Inhalte gleich sind Zwei Objekte sind identisch, wenn sie die gleiche
Instanz referenzieren Gleichheit deniert sich über die virtuelle Methode System.Object.Equals identisch: System.Object.Equals = true gleich: System.Object.Equals.Value = true.
Ähnlich wie in anderen OO-Sprachen gibt es die Zugrismodizierer private, public und protected. C# unter-
1
stützt auch internal.
public
Auf den Typ oder Member kann von jedem Code in der gleichen Assembly siehe Abschnitt 10.2 auf Seite
78 oder einer anderen Assembly, die darauf verweist, zugegrien werden.
private Auf den Typ oder Member kann nur von Code in der gleichen Klasse oder Struktur zugegrien werden.
protected Auf den Typ oder Member kann nur von Code in der gleichen Klasse oder Struktur oder in einer
abgeleiteten Klasse zugegrien werden.
1 Zugrismodizierer
11
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.1 Syntax
internal
Auf den Typ oder Member kann von jedem Code in der gleichen Assembly zugegrien werden, jedoch
nicht von Code in einer anderen Assembly.
protected internal
Auf den Typ oder Member kann von jedem Code in der gleichen Assembly oder von jeder
abgeleiteten Klasse in einer anderen Assembly zugegrien werden.
Methoden und Konstruktoren können überladen werden, ebenso können die meisten Operatoren überladen
werden: Arithmetische, Vergleichs-, Logik- und Bedingungsoperatoren. Ausnahmen sind Zuweisungsoperatoren,
Spezialoperatoren wie sizeof, new, is und typeof.
3.1.1 Operatoren
In Bezug auf Operatoren möchte ich auch noch auf den tertiären Operator ? mit : hinweisen, den es auch
in anderen Programmiersprachen wie z.B. PHP gibt, der jedoch seltener als wünschenswert eingesetzt wird.
Ähnlich dazu, gibt es den ?? Operator. Dieser ist äuÿerst hilfreich, wenn Objekte auf null hin überprüft werden
sollen. Es wird die Linke Seite evaluiert. Ist diese nicht null, wird der linke Wert zurückgegeben, andernfalls der
rechte.
Listing 3.2: Operatoren
//Variante mit Bedingung?Wahrfall:Falschfall;
string r = ( v == null ) ? "null" : v;
//Variante mit dem ??−Operator:
string r = v ?? "null";
//+ einmal anders
class Matrix {
...
Matrix operator +(Matrix l, Matrix r)
{
Matrix e = new Matrix();
e = l mit r verknüpfen
return e;
}
}
...
Matrix a = new Matrix(...), b = new Matrix(...);
Matrix c = a + b;
3.1.2 Partial
Die Denitionen partieller Typen ermöglichen es, die Denition einer Klasse, einer Struktur oder einer Schnittstelle auf mehrere Dateien aufzuteilen. Einen Klassen-, Struktur- oder Schnittstellentyp auf mehrere Dateien
aufzuteilen kann hilfreich sein, wenn Sie mit groÿen Projekten oder mit automatisch generiertem Code wie
z.B. von Der Windows Forms-Designer bereitgestellt. Verwenden Sie hierfür das Schlüsselwort partial, also z.B.
partial class A
in zwei Dateien.
3.1.3 Strings
Eine Zeichenfolge ist ein Objekt vom Typ String, dessen Wert Text ist. Intern wird der Text als schreibgeschützte Auistung von Char-Objekten gespeichert, von denen jedes ein in UTF-16 codiertes Unicode-Zeichen
darstellt. Eine C#-Zeichenfolge wird nicht mit einem NULL-Zeichen am Ende terminiert (im Gegensatz zu C
und C++). Deshalb kann eine C#-Zeichenfolge eine beliebige Anzahl eingebetteter NULL-Zeichen ('
0') enthalten. Die Länge einer Zeichenfolge stellt die Anzahl der Zeichen unabhängig davon dar, ob die Zeichen aus Unicode-Ersatzzeichenpaaren gebildet werden oder nicht. Um in einer Zeichenfolge auf die einzelnen
Unicode-Codepunkte zuzugreifen, verwenden Sie das StringInfo-Objekt.
In C# ist das string-Schlüsselwort ein Alias für String. Deshalb entsprechen sich String und string, und Sie können eine beliebige Namenskonvention verwenden. Die String-Klasse stellt viele Methoden zum sicheren Erstellen,
Bearbeiten und Vergleichen von Zeichenfolgen bereit.
Initialisieren Sie eine Zeichenfolge mit dem konstanten Empty-Wert, um ein neues String-Objekt zu erstellen,
dessen Zeichenfolge die Länge NULL aufweist. Durch die Initialisierung von Zeichenfolgen mit dem Wert Empty
12
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.2 Performance
anstelle von null können Sie die Chancen einer NullReferenceException reduzieren. Verwenden Sie die statische
IsNullOrEmpty(String)-Methode, um den Wert einer Zeichenfolge zu prüfen, bevor Sie versuchen, darauf zuzugreifen.
Da eine Zeichenfolgenänderung einer neuen Zeichenfolgenerstellung gleichkommt, müssen Sie beim Erstellen von
Verweisen auf Zeichenfolgen vorsichtig sein. Wenn Sie einen Verweis auf eine Zeichenfolge erstellen und dann
die ursprüngliche Zeichenfolge ändern, zeigt der Verweis weiterhin auf das ursprüngliche Objekt und nicht auf
das neue Objekt, das beim Ändern der Zeichenfolge erstellt wurde. Der folgende Code veranschaulicht dieses
Verhalten:
Listing 3.3: Stringreferenz
string s1 = "Hello ";
string s2 = s1;
s1 += "World";
System.Console.WriteLine(s2);
//Output: Hello
Listing 3.4: Stringreferenz
string s1 = "Hello ";
string s2 = s1;
s1 = "Hello World";
System.Console.WriteLine(s2);
Aufgabe 3.1
Was wird in Listing 3.4 ausgegeben?
Längere Strings oder Pfade können Sie in C# mit einem vorangestellten @ denieren.
Listing 3.5: Strings mit @ - ähnlich Heredoc von PHP
string path = @"c:\Docs\Source\a.txt";
string quote = @"Her name was ""Sara.""";
string miniTemplate = @"Hello {0},
Your friend {1} sent you this message:
{2}
That's all!";
string populatedTemplate = String.Format(miniTemplate, "Fred", "Jack", "HelloWorld!");
System.Console.WriteLine(populatedTemplate);
3.2 Performance
Zeichenfolgenobjekte sind unveränderlich. Das bedeutet, dass sie nicht mehr geändert werden können, nachdem
sie erstellt wurden. Alle String-Methoden und C#-Operatoren, mit denen eine Zeichenfolge geändert werden
könnte, geben diese Ergebnisse in einem neuen Zeichenfolgenobjekt zurück. Im folgenden Beispiel bleiben beim
Verketten des Inhalts von s1 und s2 zu einer einzelnen Zeichenfolge die beiden ursprünglichen Zeichenfolgen
unverändert. Der Operator += erstellt eine neue Zeichenfolge, die die kombinierten Inhalte enthält. Dieses neue
Objekt wird der Variablen s1 zugewiesen. Das ursprüngliche Objekt, das s1 zugewiesen wurde, wird für die
Garbage Collection freigegeben, da keine andere Variable einen Verweis darauf enthält.
Mit der Klasse StringBuilder können Sie ohne das Zerstören und Neuerstellen von Objekten speicherezient
Zeichenketten zusammenführen. Die Klasse enthält auch weitere nützliche Methoden.
Listing 3.6: StringBuilder zur Verkettung von Strings
int x = 4;
StringBuilder sb = new StringBuilder();
sb.append(x.toString());
sb.AppendFormat("GHI{0}{1}", 'J', 'k');
13
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.3 Typen
sb.append(" und ");
//Einen Text am Anfang einfügen
sb.Insert(0, "Alphabet: ");
sb.Replace('k', 'K');
Console.WriteLine("{0} chars: {1}", sb.Length, sb.ToString());
Noch eine Performanceverbesserung gibt es beim Prüfen auf Leerstrings: Der Vergleich von Zeichenfolgen mit
der System.String.Length-Eigenschaft oder der System.String.IsNullOrEmpty(System.String)-Methode ist bedeutend schneller als ein Vergleich mit Equals.
Listing 3.7: String auf leer prüfen
string s1 = "test";
//nicht performant if (s1 == "")
if ( !String.IsNullOrEmpty(s1) )
{
Console.WriteLine("s1 != null and s1.Length != 0.");
}
Neben StringBuilder und dem Prüfen auf Leerstrings gibt es weitere Möglichkeiten, Code performanter zu
schreiben. Wenn Sie ein Feld als static und readonly deklarieren und mit einem Wert initialisieren, dann sollten
Sie stattdessen const verwenden. Der Wert eines const-Felds wird zur Kompilierungszeit berechnet und in
den Metadaten gespeichert. Dadurch wird im Vergleich zu einem static readonly-Feld die Laufzeitleistung
gesteigert. Wenn mit dem is-Operator von C# getestet wird, ob die Umwandlung erfolgreich durchgeführt
werden kann, bevor die eigentliche Umwandlung erfolgt, sollten Sie erwägen, stattdessen das Ergebnis des
as-Operators zu testen. Dieses Vorgehen bietet die gleiche Funktionalität ohne die implizite vom is-Operator
ausgeführte Umwandlungsoperation.
Listing 3.8: is/as performant einsetzen
// The 'is ' statement performs a cast operation.
if(obj is Control)
{
// The 'as' statement performs a duplicate cast operation.
Control aControl = obj as Control;
}
// Use aControl.
//besser:
Control aControl = obj as Control;
if(aControl != null)
{
}
// Use aControl.
Zur Analyse Ihres Codes, können Sie eine hilfreiche Eigentschaft Ihres Projekts setzen: Option Codeanalyse aktivieren. Weitere Empfehlungen zur Erstellung von hochwertigem Code nden Sie unter Verfassen von qualitativ
hochwertigem Quellcode und darin insbesondere Leistungswarnungen.
3.3 Typen
3.3.1 Strukturen
Klassen und Strukturen sind zwei der grundlegenden Konstrukte des allgemeinen Typsystems in .NET Framework. Bei beiden handelt es sich um eine Datenstruktur, die einen als logische Einheit zusammengehörenden
Satz von Daten und Verhalten kapselt. Die Daten und Verhalten sind die Member der Klasse oder Struktur.
Diese enthalten deren Methoden, Eigenschaften, Ereignisse usw. Eine Struktur ist ein Werttyp (siehe Abschnitt
10.5 auf Seite 1). Wenn eine Struktur erstellt wird, enthält die Variable, der die Struktur zugewiesen wird,
die eigentlichen Daten der Struktur. Wenn die Struktur einer neuen Variable zugewiesen wird, werden diese
kopiert. Die neue Variable und die ursprüngliche Variable enthalten daher zwei separate Kopien der gleichen
Daten. Änderungen an einer Kopie wirken sich nicht auf die andere Kopie aus.
Im Allgemeinen werden Klassen zum Modellieren von komplexerem Verhalten oder von Daten verwendet, die
dafür vorgesehen sind, nach der Erstellung eines Klassenobjekts geändert zu werden. Strukturen sind am besten
für kleine Datenstrukturen geeignet, die überwiegend Daten enthalten, deren Änderung nach der Erstellung der
Struktur nicht vorgesehen ist.
14
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.3 Typen
Besonderheiten von Strukturen im Vergleich zu Klassen:
•
Strukturen haben fast die gleiche Syntax wie Klassen, unterliegen jedoch mehr Beschränkungen als Klassen:
•
Innerhalb einer Strukturdeklaration können Felder nur dann initialisiert werden, wenn sie als konstant
oder statisch deklariert werden.
•
Eine Struktur kann keinen Standardkonstruktor (ein Konstruktor ohne Parameter) und keinen Destruktor
deklarieren.
•
Strukturen können nicht von Klassen oder anderen Strukturen erben.
•
Strukturen werden durch Zuweisung kopiert. Wenn eine Struktur einer neuen Variablen zugewiesen wird,
werden alle Daten kopiert. Änderungen an der neuen Kopie wirken sich nicht auf die Daten in der ursprünglichen Kopie aus.
•
Strukturen sind Werttypen, und Klassen sind Referenztypen.
•
Strukturen können im Gegensatz zu Klassen ohne einen Operator new instanziiert werden.
•
Strukturen können Konstruktoren deklarieren, die über Parameter verfügen.
•
Eine Struktur ist nicht in der Lage, von einer anderen Struktur oder Klasse zu erben, und sie kann
auch nicht die Basis einer Klasse sein. Alle Strukturen erben direkt von System.ValueType, welcher von
System.Object erbt.
•
Eine Struktur kann Schnittstellen implementieren.
Lapidar gesagt, Strukturen sind gut für die Darstellung zusammengehörender Daten, für die Modellierung von
Verhalten und wenn Vererbung benötigt wird, sollten Klassen verwendet werden.
Listing 3.9: Beispiel für Strukturen
public struct CoOrds
{
public int x, y;
}
public CoOrds(int p1, int p2)
{
x = p1;
y = p2;
}
struct/Objekt Parameterübergabe:
Da Strukturen Werttypen sind und Objekte Referenztypen sind, gilt: Wenn struct an eine Methode übergeben
wird, wird eine Kopie der Struktur übergeben. Bei der Übergabe einer class-Instanz wird jedoch ein Verweis
übergeben.
3.3.2 Generika
Manchmal möchte man eine Klasse oder Parameter einer Methode so allgemein halten, dass sie mit Objekten
verschiedener Typen arbeiten können. Dies könnte man durch den allgemeinsten Datentyp Object erreichen,
damit können Typen jedoch gemischt werden. Möchte man auf Typsicherheit nicht verzichten, so kann man
Generika einsetzen. Häug verwendet man generische Listen, um z.B. Stringlisten, Int-Listen oder Listen von
Objekten einer Klasse zu verarbeiten.
Listing 3.10: Generika
public class GenericList<T>
{
void Add(T input) { }
}
class TestGenericList
{
private class ExampleClass { }
static void Main()
15
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.3 Typen
{
}
}
GenericList<int> list1 = new GenericList<int>();
GenericList<string> list2 = new GenericList<string>();
GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
3.3.3 Listen
Abbildung 3.1: Listen
.NET Framework stellt spezialisierte Klassen für das Speichern und Abrufen von Daten bereit. Diese Klassen
bieten Unterstützung für Stapel, Warteschlangen, Listen und Hashtabellen. Die meisten Auistungsklassen
implementieren dieselben Schnittstellen. Diese Schnittstellen können geerbt werden, um neue, auf speziellere
Datenspeicherungsanforderungen zugeschnittene Auistungsklassen zu erstellen.
Hashtable stellt eine Auistung von Schlüssel-Wert-Paaren dar, die nach dem Hashcode des Schlüssels organisiert
sind.
Die generische Dictionary<(Of <(TKey, TValue>)>)-Klasse stellt eine Zuordnung von einem Satz von Schlüsseln zu einem Satz von Werten bereit. Jede Hinzufügung zum Wörterbuch besteht aus einem Wert und dem zugeordneten Schlüssel. Ein Wert kann anhand des zugehörigen Schlüssels sehr schnell abgerufen werden (beinahe
ein O(1)-Vorgang), da die Dictionary<(Of <(TKey, TValue>)>)-Klasse in Form einer Hashtabelle implementiert ist. Wenn ein Objekt im Dictionary<(Of <(TKey, TValue>)>) als Schlüssel verwendet wird, darf es nicht
auf eine Weise geändert werden, die sich auf seinen Hashwert auswirkt. Jeder Schlüssel in einem Dictionary<(Of
<(TKey, TValue>)>) muss für den Gleichheitsvergleich des Wörterbuches eindeutig sein.
ArrayList implementiert die IList-Schnittstelle unter Verwendung eines Arrays, das nach Bedarf dynamisch
vergröÿert wird. Es wird nicht sichergestellt, dass die ArrayList sortiert ist. Sie müssen die ArrayList sortieren,
bevor Sie Vorgänge wie BinarySearch durchführen, die eine sortierte ArrayList voraussetzen.
Die List<(Of <(T>)>)-Klasse stellt die generische Entsprechung der ArrayList-Klasse dar. Sie implementiert
die generische IList<(Of <(T>)>)-Schnittstelle unter Verwendung eines Arrays, das nach Bedarf dynamisch
vergröÿert wird.
Queue stellt eine FIFO-Auistung (First-In-First-Out) von Objekten dar.
Stack stellt eine LIFO (Last-In-First-Out)-Auistung variabler Gröÿe von Instanzen desselben beliebigen Typs
dar.
Listing 3.11: Listen
Hashtable openWith = new Hashtable();
openWith.Add("txt", "notepad.exe");
Dictionary<string, string> openWith2 =
new Dictionary<string, string>();
16
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.3 Typen
openWith2.Add("txt", "notepad.exe");
ArrayList myAL = new ArrayList();
myAL.Add("Hello");
List<string> dinosaurs = new List<string>();
dinosaurs.Add("Tyrannosaurus");
Queue<string> numbers = new Queue<string>();
numbers.Enqueue("one");
Stack<string> numbers = new Stack<string>();
numbers.Push("one");
Wählen Sie Ihre System.Collections-Klasse sorgfältig aus. Wenn der falsche Typ ausgewählt wird, kann dies die
Verwendung der Auistung einschränken.
Beachten Sie Folgendes:
•
Benötigen Sie eine sequenzielle Liste, bei der ein Element normalerweise verworfen werden kann, wenn
sein Wert abgerufen wurde?
•
Benötigen Sie Zugri auf Elemente in einer bestimmten Reihenfolge, z. B. FIFO-, LIFO- oder beliebiger
Reihenfolge?
•
Möchten Sie auf die einzelnen Elemente mithilfe eines Indizes zugreifen? ArrayList, List, Hashtable, SortedList, ListDictionary und StringDictionary
•
Wird jedes Element einen Wert enthalten, eine Kombination aus einem Schlüssel und einem Wert oder
eine Kombination aus einem Schlüssel und mehreren Werten?
•
Müssen die Elemente unabhängig von der Reihenfolge ihrer Eingabe sortiert werden?
•
Ist schnelles Suchen und Abrufen von Informationen erforderlich?
•
Werden Auistungen gebraucht, die nur Zeichenfolgen annehmen?
3.3.4 Enumerationen
Ein Enumerationstyp (auch als Enumeration bezeichnet) bietet eine eziente Möglichkeit, einen Satz benannter
integraler Konstanten zu denieren, die einer Variablen zugewiesen werden können. Angenommen, Sie müssen
eine Variable denieren, deren Wert einen Wochentag darstellt. Es gibt nur sieben sinnvolle Werte, die diese
Variable speichern kann. Um diese Werte zu denieren, können Sie einen Enumerationstyp verwenden, der durch
die Verwendung des enum-Schlüsselworts deklariert wird. Der zugrunde liegende Standardtyp aller Elemente in
der Enumeration lautet int.
Listing 3.12: Enumerationen
enum Days { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };
enum Months : byte { Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec };
Days meetingDay = Days.Monday;
enum MachineState
{
PowerOff = 0,
Running = 5,
Sleeping = 10,
Hibernating = Sleeping + 5
}
Sie können einen Enumerationstyp verwenden, um Bitags zu denieren. Dadurch kann eine Instanz des Enumerationstyps eine Kombination aus den Werten speichern, die in der Enumeratorliste deniert sind.
Im folgenden Beispiel wird eine andere Version des Days-Enumerators, der als Days2 bezeichnet ist, deniert.
Days2 weist das Flags-Attribut auf, und jedem Wert wird die nächste höhere Potenz von 2 zugewiesen. So
können Sie eine Days2-Variable erstellen, deren Wert Days2.Tuesday und Days2.Thursday lautet. Um ein Flag
eines Enumerators festzulegen, verwenden Sie den logischen OR-Operator. Um zu ermitteln, ob ein bestimmtes
Flag festgelegt ist, verwenden Sie eine logische AND-Operation.
17
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.4 Sprachbesonderheiten
Listing 3.13: Enumeration als Flags
[Flags]
enum Days2
{
None = 0x0,
Sunday = 0x1,
Monday = 0x2,
Tuesday = 0x4,
Wednesday = 0x8,
Thursday = 0x10,
Friday = 0x20,
Saturday = 0x40
}
class MyClass
{
Days2 meetingDays = Days2.Tuesday | Days2.Thursday;
private void pleassure() {
// Initialize with two ags using bitwise OR.
meetingDays = Days2.Tuesday | Days2.Thursday;
// Set an additional ag using bitwise OR.
meetingDays = meetingDays | Days2.Friday;
Console.WriteLine("Meeting days are {0}", meetingDays);
// Output: Meeting days are Tuesday, Thursday, Friday
// Remove a ag using bitwise XOR.
meetingDays = meetingDays ^ Days2.Tuesday;
Console.WriteLine("Meeting days are {0}", meetingDays);
// Output: Meeting days are Thursday, Friday
// Test value of ags using bitwise AND.
bool test = (meetingDays & Days2.Thursday) == Days2.Thursday;
Console.WriteLine("Thursday {0} a meeting day.", test == true ? "is" : "is not");
// Output: Thursday is a meeting day.
3.4 Sprachbesonderheiten
3.4.1 Log/Trace
Mit der Trace-Klasse lassen sich Anwendungen instrumentieren. Von einer laufenden Anwendung können Sie
damit Informationsmeldungen erhalten, die beim Diagnostizieren von Problemen oder Analysieren der Leistung
hilfreich sind.
Listing 3.14: Trace
Trace.WriteLine("Entering Main");
Mächtiger und besser zu kongurieren ist log4net.
3.4.2 Ausnahmen
2 Die
Features zur Ausnahmebehandlung in C# helfen Ihnen, wenn bei der Ausführung eines Programms uner-
wartete oder auÿergewöhnliche Situationen auftreten. Bei der Ausnahmebehandlung wird mithilfe der Schlüsselwörter try, catch und nally versucht, Aktionen auszuführen, die möglicherweise fehlschlagen, um Fehler
zu behandeln und anschlieÿend die Ressourcen zu bereinigen. Ausnahmen können von der Common Language
Runtime (CLR ähnlich zur Virtual Maschine von Java), durch .NET Framework oder Bibliotheken von Drittanbietern oder durch den Anwendungscode generiert werden. Ausnahmen werden mit dem throw-Schlüsselwort
erstellt. Es gibt jedoch im Gegensatz zu Java keine Syntax, die kennzeichnet, welche Klassen welche Ausnahmen
werfen können throws wird in C# also nur zum tatsächlichen Erzeugen/werfen von Ausnahmen verwendet.
In vielen Fällen wird eine Ausnahme nicht von einer Methode ausgelöst, die direkt durch den Code aufgerufen
wurde, sondern von einer anderen Methode, die sich weiter unten in der Aufruiste bendet. In diesem Fall
2 Ausnahmen
18
und Ausnahmebehandlung
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.4 Sprachbesonderheiten
Abbildung 3.2: Aunden einer passenden catch-Denition
entlädt die CLR die Aufruiste, sucht eine Methode mit einem catch-Block für den spezischen Ausnahmetyp
und führt den ersten catch-Block aus, der gefunden wird. Falls kein entsprechender catch-Block in der Aufruiste
gefunden wird, wird der Prozess beendet und eine Meldung für den Benutzer angezeigt.
Ausnahmen sind Typen, die letztlich alle von System.Exception abgeleitet werden. Fangen Sie keine Ausnahme
ab, es sei denn, Sie können sie bearbeiten, und belassen Sie die Anwendung in einem bekannten Zustand. Wenn
Sie System.Exception abfangen, lösen Sie es erneut mit dem throw-Schlüsselwort am Ende des catch-Blocks aus.
Ausnahmeobjekte enthalten ausführliche Informationen zum Fehler, z. B. den Zustand der Aufruiste und eine
Textbeschreibung des Fehlers. Nicht abgefangene Ausnahmen werden von einem generischen Ausnahmehandler
des Systems behandelt, der ein Dialogfeld anzeigt. Im Gegensatz zu Java, gibt es keine Checked Exceptions. Eine
Abbildung 3.3: CheckedException Java vs C#
Checked Exception ist eine Ausnahme, bei der der Compiler prüft, ob alle Stellen, wo sie auftreten kann, durch
Code zum Abfangen der Ausnahme abgedeckt sind. Der Code zum Abfangen kann dabei innerhalb derselben
Methode stehen, in der die Ausnahme auftreten kann, oder auch in aufrufenden Methoden. Der Compiler wird
bei Java durch das Schlüsselwort throws unterstüzt. Bei C# ist dies nicht so umgesetzt. Fängt niemand eine
Exception, so bricht das Programm mit dem generischen Fehlerdialog ab. Per Design ist dies vermutlich ein
Nachteil. Da jedoch viele Java-Programmierer einfach mit
Listing 3.15: Schlechter Code Fehler schlucken
catch (E e) {}
Fehler schlucken, führt dies weningstens nicht zu nicht-erklärbaren Verhalten, sondern der Fehler bleibt erhalten.
19
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.4 Sprachbesonderheiten
3.4.3 Indexer
3
einer Klasse oder Struktur auf dieselbe Weise wie Arrays. Abgesehen davon, dass ihre Accessoren Parameter
akzeptieren, sind Indexer mit Eigenschaften vergleichbar.
Im folgenden Beispiel wird eine generische Klasse deniert, die mit einfachen get-Accessormethoden und setAccessormethoden ausgestattet ist. Mit diesen Methoden werden Werte zugewiesen und abgerufen. Die ProgramKlasse erstellt eine Instanz dieser Klasse für das Speichern von Zeichenfolgen.
Listing 3.16: Indexer
class SampleCollection<T>
{
private T[] arr = new T[100];
public T this[int i]
{
get
{
return arr[i];
}
set
{
arr[i] = value;
}
}
}
// This class shows how client code uses the indexer
class Program
{
static void Main(string[] args)
{
SampleCollection<string> stringCollection = new SampleCollection<string>();
stringCollection[0] = "Hello, World";
System.Console.WriteLine(stringCollection[0]);
}
}
Übersicht über Indexer
•
Durch Indexer können Objekte auf ähnliche Weise wie Arrays indiziert werden.
•
Ein get-Accessor gibt einen Wert zurück. Ein set-Accessor weist einen Wert zu.
•
Das this-Schlüsselwort wird zum Denieren der Indexer verwendet.
•
Mithilfe des value-Schlüsselworts wird der Wert deniert, der vom set-Indexer zugewiesen wird.
•
Indexer müssen nicht mit ganzzahligen Werten indiziert werden. Sie können frei wählen, wie Sie den
bestimmten Suchmechanismus denieren.
•
Indexer können überladen werden.
•
Indexer können mehr als einen formalen Parameter haben, z. B. wenn auf ein zweidimensionales Array
zugegrien wird.
3.4.4 Iteratoren
Möchte man eine Klasse mit foreach durchlaufen, so benötigt man die Methode GetEnumerator.
Listing 3.17: Iteratoren
public class DaysOfTheWeek : System.Collections.IEnumerable
{
string[] days = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" };
public System.Collections.IEnumerator GetEnumerator()
{
for (int i = 0; i < days.Length; i++)
{
3 Indexer
20
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.5 Delegaten
}
}
}
yield return days[i];
class TestDaysOfTheWeek
{
static void Main()
{
// Create an instance of the collection class
DaysOfTheWeek week = new DaysOfTheWeek();
// Iterate with foreach
}
}
foreach (string day in week)
{
System.Console.Write(day + " ");
}
3.5 Delegaten
Ein Delegate ist ein Objekt, welches auf eine Methode zeigt, die zu einem späteren Zeitpunkt aufgerufen werden
kann.
Delegaten verfügen über folgende Eigenschaften:
•
Delegaten ähneln C++-Funktionszeigern, sind aber typsicher.
•
Delegaten ermöglichen es, Methoden als Parameter zu übergeben.
•
Delegaten können zum Denieren von Rückrufmethoden verwendet werden.
•
Delegaten können miteinander verkettet werden. So können beispielsweise mehrere Methoden für ein
einziges Ereignis aufgerufen werden.
Delegate:
Delegate folgen dem Design Pattern Publish/Subscribe.
Das folgende Listing zeigt, wie ein Delegate deniert wird.
Listing 3.18: Delegaten
public delegate void Del<T>(T item);
public void Notify(int i) { }
Del<int> d1 = new Del<int>(Notify);
//gleichwertig zu
Del<int> d2 = Notify;
Dieses Beispiel zeigt ein Delegate mit Verweis auf eine statische Funktion und mit einer nicht-statischen Funktion
Listing 3.19: Delegaten, Beispiel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace Bookstore
{
using System.Collections;
public struct Book
{
public string Title;
public string Author;
public decimal Price;
public bool Paperback;
//
//
//
//
Title of the book.
Author of the book.
Price of the book.
Is it paperback?
public Book(string title, string author, decimal price, bool paperBack)
{
Title = title;
Author = author;
Price = price;
Paperback = paperBack;
21
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.5 Delegaten
Abbildung 3.4: Delegaten
17
18
19
20
21
22
23
24
25
26
27
28
29
30
}
}
public delegate void ProcessBookDelegate(Book book);
public class BookDB
{
ArrayList list = new ArrayList();
public void AddBook(string title, string author, decimal price, bool paperBack)
{
list.Add(new Book(title, author, price, paperBack));
}
public void ProcessPaperbackBooks(ProcessBookDelegate processBook)
{
foreach (Book b in list)
{
22
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.5 Delegaten
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
if (b.Paperback)
// Calling the delegate:
}
}
}
processBook(b);
}
namespace BookTestClient
{
using Bookstore;
class PriceTotaller
{
int countBooks = 0;
decimal priceBooks = 0.0m;
internal void AddBookToTotal(Book book)
{
countBooks += 1;
priceBooks += book.Price;
}
internal decimal AveragePrice()
{
return priceBooks / countBooks;
}
}
}
class TestBookDB
{
static void PrintTitle(Book b)
{
System.Console.WriteLine(" {0}", b.Title);
}
static void Main()
{
BookDB bookDB = new BookDB();
AddBooks(bookDB);
System.Console.WriteLine("Paperback Book Titles:");
bookDB.ProcessPaperbackBooks(PrintTitle);
PriceTotaller totaller = new PriceTotaller();
bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);
System.Console.WriteLine("Average Paperback Book Price: ${0:#.##}",
totaller.AveragePrice());
}
static void AddBooks(BookDB bookDB)
{
bookDB.AddBook("The C Programming Language", "Brian W. Kernighan and Dennis M. Ritchie", 19.95m, true);
bookDB.AddBook("The Unicode Standard 2.0", "The Unicode Consortium", 39.95m, true);
bookDB.AddBook("The MS-DOS Encyclopedia", "Ray Duncan", 129.95m, false);
bookDB.AddBook("Dogbert's Clues for the Clueless", "Scott Adams", 12.00m, true);
}
}
Aufgabe 3.2
Kommentieren Sie das Programm ausführlich. Beschreiben Sie den Ablauf in Worten oder malen
Sie ein geeignetes Bild. Was gibt das Programm aus?
Ein Delegat kann entweder (wie in diesem Beispiel) synchron oder mithilfe der BeginInvoke-Methode und der
EndInvoke-Methode asynchron aufgerufen werden.
3.5.1 Mulitcastdelegaten
Eine nützliche Eigenschaft von delegate-Objekten besteht darin, dass sie mithilfe des Operators + einer Delegatinstanz als Multicast zugewiesen werden können. Ein zusammengesetzter Delegat ruft die beiden Delegaten
auf, aus denen er besteht. Es können ausschlieÿlich Delegaten mit demselben Typ kombiniert werden.
Mithilfe des Operators - kann ein Komponentendelegat aus einem zusammengesetzten Delegaten entfernt werden. Das folgende Beispiel zeigt die Verwendung dieser Operatoren.
Listing 3.20: Multicastdelegaten
23
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.5 Delegaten
delegate void Del(string s);
class TestClass
{
static void Hello(string s)
{
System.Console.WriteLine(" Hello, {0}!", s);
}
static void Goodbye(string s)
{
System.Console.WriteLine(" Goodbye, {0}!", s);
}
static void Main()
{
Del a, b, c, d;
a = Hello;
b = Goodbye;
c = a + b;
d = c - a;
System.Console.WriteLine("Invoking
a("A");
System.Console.WriteLine("Invoking
b("B");
System.Console.WriteLine("Invoking
c("C");
System.Console.WriteLine("Invoking
d("D");
}
}
Aufgabe 3.3
delegate a:");
delegate b:");
delegate c:");
delegate d:");
Was gibt das Programm Multicastdelegaten aus?
3.5.2 Schnittstelle vs. Delegate
4 Sowohl
Delegaten als auch Schnittstellen ermöglichen es einem Klassendesigner, Typdeklarationen und Imple-
mentierung voneinander zu trennen. Eine bestimmte Schnittstelle kann von jeder Klasse oder Struktur geerbt
und implementiert werden. Ein Delegat kann für eine Methode jeder beliebigen Klasse erstellt werden, solange die Methode mit der Methodensignatur des Delegaten übereinstimmt. Ein Schnittstellenverweis oder ein
Delegat kann von jedem Objekt verwendet werden, ohne über Informationen zu der Klasse zu verfügen, die
die Schnittstelle oder die Delegatmethode implementiert. Wann sollte ein Klassendesigner in Anbetracht dieser
Gemeinsamkeiten einen Delegaten verwenden, und wann sollte eine Schnittstelle verwendet werden?
Verwenden Sie Delegaten, wenn
•
ein Ereignisentwurfsmuster verwendet wird.
•
eine statische Methode gekapselt werden soll.
•
der Aufrufer keinen Zugri auf weitere Eigenschaften, Methoden oder Schnittstellen des Objekts benötigt,
das die Methode implementiert.
•
die einfache Verknüpfung von Delegaten gewünscht ist.
•
eine Klasse möglicherweise mehr als eine Implementierung der Methode benötigt.
Verwenden Sie Schnittstellen, wenn
•
es eine Gruppe verwandter Methoden gibt, die möglicherweise aufgerufen werden.
•
eine Klasse nur eine Implementierung der Methode benötigt.
•
die Klasse, die die Schnittstelle verwendet, eine Umwandlung dieser Schnittstelle in andere Schnittstellen
oder Klassentypen durchführt.
•
die Methode, die implementiert wird, mit dem Typ oder der Identität der Klasse verknüpft ist, wie zum
Beispiel bei Vergleichsmethoden.
4 Wann
24
sind Delegaten Schnittstellen vorzuziehen?
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.5 Delegaten
Ein gutes Beispiel für die Verwendung einer Schnittstelle mit nur einer Methode anstatt eines Delegaten ist
IComparable oder die generische Version IComparable<(Of <(T>)>). IComparable deklariert die CompareToMethode, die eine ganze Zahl zurückgibt, die ein Kleiner-als-, Gleich- oder Gröÿer-als-Verhältnis zwischen zwei
Objekten desselben Typs angibt. IComparable kann als Grundlage für einen Sortieralgorithmus verwendet werden. Die Verwendung einer Delegatenvergleichsmethode als Grundlage für den Sortieralgorithmus ist zwar möglich, aber nicht optimal. Da die Vergleichsfunktionalität zu einer Klasse gehört und der Vergleichsalgorithmus
zur Laufzeit nicht geändert wird, stellt eine Schnittstelle mit nur einer Methode die ideale Lösung dar.
3.5.3 Ereignisse
Ereignisse aktivieren eine Klasse oder ein Objekt, um Informationen über Aktionen von Interesse an andere
Klassen oder Objekte zu übermitteln. Die Klasse, die das Ereignis sendet (oder auslöst), wird als Herausgeber
bezeichnet und die Klassen, die das Ereignis empfangen (oder behandeln), werden als Abonnenten bezeichnet.
In einer typischen C# Windows Forms- oder -Webanwendung abonnieren Sie Ereignisse, die von Steuerelementen
wie Schaltächen und Listenfeldern ausgelöst wurden. In der integrierten Entwicklungsumgebung (IDE) von
Visual C# können Sie die Ereignisse durchsuchen, die ein Steuerelement veröentlicht, und diejenigen auswählen,
die Sie behandeln möchten. Die IDE fügt automatisch eine leere Ereignishandlermethode und den Code zum
Abonnieren des Ereignisses hinzu.
Ereignisse verfügen über folgende Eigenschaften:
•
Der Herausgeber bestimmt, wann ein Ereignis ausgelöst wird; die Abonnenten bestimmen, welche Maÿnahme als Reaktion auf das Ereignis ergrien wird.
•
Ein Ereignis kann mehrere Abonnenten haben. Ein Abonnent kann mehrere Ereignisse von mehreren
Herausgebern behandeln.
•
Ereignisse, die keine Abonnenten haben, werden nie ausgeführt.
•
Ereignisse dienen normalerweise zur Signalisierung von Benutzeraktionen wie das Klicken auf Schaltächen
oder Auswählen von Menüs in der graschen Benutzeroberäche.
•
Ereignisse können verwendet werden, um Threads zu synchronisieren.
•
In der .NET Framework-Klassenbibliothek basieren Ereignisse auf dem EventHandler-Delegaten und der
EventArgs-Basisklasse
Listing 3.21: Ereignisse
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/∗ Denition in System−Namensraum
public delegate void EventHandler<TEventArgs>(
Object sender,
TEventArgs e
)
∗/
public class MyEventArgs : EventArgs
{
privatestring msg;
public MyEventArgs( string messageData ) {
msg = messageData;
}
publicstring Message {
get { return msg; }
set { msg = value; }
}
}
public class HasEvent
{
public void DemoEvent(string val)
{
// Copy to a temporary variable to be thread−safe.
EventHandler<MyEventArgs> temp = SampleEvent;
if (temp != null)
temp(this, new MyEventArgs(val));
}
}
public class Sample
{
publics tatic void Main()
25
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.5 Delegaten
32
33
34
35
36
37
38
39
40
41
42
43
44
{
}
HasEvent he = new HasEvent();
he.SampleEvent +=
new EventHandler<MyEventArgs>(SampleEventHandler);
he.DemoEvent("Hey there, Bruce!");
he.DemoEvent("How are you today?");
}
private static void SampleEventHandler(object src, MyEventArgs mea)
{
Console.WriteLine(mea.Message);
}
Aufgabe 3.4
Kommentieren Sie das Programm ausführlich. Beschreiben Sie den Ablauf in Worten oder malen
Sie ein geeignetes Bild. Was gibt das Programm aus?
Aufgabe 3.5
Arbeiten Sie das Zusammenspiel von Delegaten und Events anhand des Artikels im Anhang
??
heraus.
Aufgabe 3.6
Kommentieren Sie das Programm ausführlich. Beschreiben Sie den Ablauf in Worten oder malen
Sie ein geeignetes Bild.
Listing 3.22: Asynchrone Delegate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using System;
using System.Threading;
using System.Runtime.Remoting.Messaging;
//Quelle: http://msdn.microsoft.com/de−de/library/h80ttd5f%28en−us,VS.100%29.aspx
namespace Examples.AdvancedProgramming.AsynchronousOperations
{
// Create a class that factors a number.
public class PrimeFactorFinder
{
public static bool Factorize(
int number,
ref int primefactor1,
ref int primefactor2)
{
primefactor1 = 1;
primefactor2 = number;
// Factorize using a low−tech approach.
}
}
for (int i=2;i<number;i++)
{
if (0 == (number % i))
{
primefactor1 = i;
primefactor2 = number / i;
break;
}
}
if (1 == primefactor1 )
return false;
else
return true ;
// Create an asynchronous delegate that matches the Factorize method.
public delegate bool AsyncFactorCaller (
int number,
ref int primefactor1,
ref int primefactor2);
public class DemonstrateAsyncPattern
{
// The waiter object used to keep the main application thread
// from terminating before the callback method completes.
ManualResetEvent waiter;
// Dene the method that receives a callback when the results are available .
26
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.5 Delegaten
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
public void FactorizedResults(IAsyncResult result)
{
int factor1=0;
int factor2=0;
// Extract the delegate from the
// System.Runtime.Remoting.Messaging.AsyncResult.
AsyncFactorCaller factorDelegate = (AsyncFactorCaller)((AsyncResult)result).AsyncDelegate;
int number = (int) result.AsyncState;
// Obtain the result .
bool answer = factorDelegate.EndInvoke(ref factor1, ref factor2, result);
// Output the results.
}
Console.WriteLine("On CallBack: Factors of {0} : {1} {2} - {3}",
number, factor1, factor2, answer);
waiter.Set();
// The following method demonstrates the asynchronous pattern using a callback method.
public void FactorizeNumberUsingCallback()
{
AsyncFactorCaller factorDelegate = new AsyncFactorCaller (PrimeFactorFinder.Factorize);
int number = 1000589023;
int temp=0;
//
//
//
//
Waiter will keep the main application thread from
ending before the callback completes because
the main thread blocks until the waiter is signaled
in the callback .
waiter = new ManualResetEvent(false);
// Dene the AsyncCallback delegate.
AsyncCallback callBack = new AsyncCallback(this.FactorizedResults);
// Asynchronously invoke the Factorize method.
IAsyncResult result = factorDelegate.BeginInvoke(
number,
ref temp,
ref temp,
callBack,
number);
// Do some other useful work while
// waiting for the asynchronous operation to complete.
// When no more work can be done, wait.
}
waiter.WaitOne();
// The following method demonstrates the asynchronous pattern
// using a BeginInvoke, followed by waiting with a time−out.
public void FactorizeNumberAndWait()
{
AsyncFactorCaller factorDelegate = new AsyncFactorCaller (PrimeFactorFinder.Factorize);
int number = 1000589023;
int temp=0;
// Asynchronously invoke the Factorize method.
IAsyncResult result = factorDelegate.BeginInvoke(
number,
ref temp,
ref temp,
null,
null);
while (!result.IsCompleted)
{
// Do any work you can do before waiting.
}
result.AsyncWaitHandle.WaitOne(10000, false);
// The asynchronous operation has completed.
int factor1=0;
int factor2=0;
// Obtain the result .
bool answer = factorDelegate.EndInvoke(ref factor1, ref factor2, result);
27
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.6 Attribute und Reektion
124
125
126
127
128
129
130
131
132
133
134
135
136
// Output the results.
}
}
}
Console.WriteLine("Sequential : Factors of {0} : {1} {2} - {3}",
number, factor1, factor2, answer);
public static void Main()
{
DemonstrateAsyncPattern demonstrator = new DemonstrateAsyncPattern();
demonstrator.FactorizeNumberUsingCallback();
demonstrator.FactorizeNumberAndWait();
}
Delegate:
Ein Delegate deniert einen neuen Typ, der Referencen auf Methoden enthält.
Ereignisse nutzen Delegate, um Objekte über das Eintreten des Ereignisses zu informieren. Alle Abonnenten
(Subscriber) des Ereignisses werden informiert.
5
Ein Callback
deniert eine Beziehung zwischen zwei Klassen. Ein Callback ist ein Pattern. Ein Callback wird
nicht in die Welt rauspossaunt, sondern bleibt für sich. Die Beziehung sorgt dafür, dass ein Objekt automatisch
auf ein anderes Objekt reagiert.
3.6 Attribute und Reektion
Abbildung 3.5: Reektion und Attribute
Attribute stellen eine eziente Methode dar, um Deklarationsinformationen mit C#-Code (Typen, Methoden,
Eigenschaften usw.) zu verknüpfen. Sobald das Attribut einer Programmentität zugeordnet ist, kann es zur
Laufzeit mithilfe eines Verfahrens abgefragt werden, das als Reektion bezeichnet wird.
3.6.1 Attribute
Attribute fügen dem Programm Metadaten hinzu. Metadaten sind Informationen zu den in einem Programm
denierten Typen. Alle .NET-Assemblys enthalten einen angegebenen Satz von Metadaten, die die in der Assembly denierten Typen und Typmember beschreiben. Sie können benutzerdenierte Attribute hinzufügen,
um bei Bedarf zusätzliche Informationen anzugeben.
5 Der Begri Callback (Rückruf) wird in verschiedene Programmiermodellen verwendet als Begri für die Möglichkeit, dass aufgeru-
fener Programmcode seinerseits einen Aufruf zu dem Aufrufer startet. Ein Callback ist eine asynchrone Operation im Gegensatz
zu einem Rückgabewert auf einen Aufruf.
28
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.6 Attribute und Reektion
Sie können eigene benutzerdenierte Attribute erstellen, indem Sie eine Attributklasse denieren. Dies ist eine
Klasse, die direkt oder indirekt Elemente von Attribute ableitet, wodurch die schnelle und einfache Identikation
von Attributdenitionen in Metadaten ermöglicht wird. Angenommen, Sie möchten Klassen und Strukturen mit
dem Namen des Programmierers kennzeichnen, der sie geschrieben hat. Sie könnten beispielsweise eine benutzerdenierte Author-Attributklasse denieren: Der Klassenname entspricht dem Attributnamen, also Author. Da
er von System.Attribute abgeleitet ist, handelt es sich um eine benutzerdenierte Attributklasse. Die Parameter
des Konstruktors sind die positionellen Parameter des benutzerdenierten Attributs (in diesem Fall name), und
alle öentlichen Felder oder Eigenschaften, die Lese- und Schreibzugri unterstützen, sind benannte Parameter
(in diesem Fall ist version der einzige benannte Parameter). Beachten Sie, dass das AttributeUsage-Attribut
verwendet wird, um die Gültigkeit des Author-Attributs auf class- und struct-Deklarationen zu beschränken.
3.6.2 Reektion
Metadaten eines Moduls können zur Laufzeit ausgelesen und geändert werden Diesen Vorgang nennt man
Reektion .NET Framework stellt entsprechende Klassen über den Namespace System.Reection bereit
6 Bei
der Reektion werden Objekte (vom Typ Type) bereitgestellt, die Assemblys, Module und Typen beschrei-
ben. Mithilfe von Reektion können Instanzen von Typen dynamisch erzeugt, Typen an ein vorhandenes Objekt
gebunden und Typinformationen von vorhandenen Objekten abgefragt werden. Ebenso können die Methoden
vorhandener Objekte aufgerufen werden, und es kann auf ihre Felder und Eigenschaften zugegrien werden.
Wenn Sie Attribute im Code verwenden, können Sie mithilfe von Reektion auf diese Attribute zugreifen.
Reektion ist in folgenden Situationen nützlich:
•
Wenn Sie auf Attribute in den Metadaten des Programms zugreifen müssen.
•
Für das Überprüfen und das Instanziieren von Typen in einer Assembly.
•
Für das Erstellen neuer Typen zur Laufzeit. Verwenden Sie die Klassen in System.Reection.Emit.
•
Für das Ausführen von spätem Binden und für den Zugri auf Methoden von zur Laufzeit erstellten
Typen.
3.6.3 Zugri auf Attribute mit Reektion
Listing 3.23: Benutzerdeniertes Attribute und Reektion
//Denition
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct,
AllowMultiple = true) // multiuse attribute
]
public class Author : System.Attribute
{
string name;
public double version;
public Author(string name)
{
this.name = name;
version = 1.0; // Default value
}
public string GetName()
{
return name;
}
}
[Author("H. Ackerman")]
private class FirstClass
{
// ...
}
// No Author attribute
private class SecondClass
6 Reektion
29
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.7 Lambda-Ausdrücke
{
}
// ...
[Author("H. Ackerman"), Author("M. Knott", version = 2.0)]
private class ThirdClass
{
}
// ...
class TestAuthorAttribute
{
static void Main()
{
PrintAuthorInfo(typeof(FirstClass));
PrintAuthorInfo(typeof(SecondClass));
PrintAuthorInfo(typeof(ThirdClass));
}
private static void PrintAuthorInfo(System.Type t)
{
System.Console.WriteLine("Author information for {0}", t);
System.Attribute[] attrs = System.Attribute.GetCustomAttributes(t); // reection
}
}
foreach (System.Attribute attr in attrs)
{
if (attr is Author)
{
Author a = (Author)attr;
System.Console.WriteLine(" {0}, version {1:f}", a.GetName(), a.version);
}
}
Aufgabe 3.7
Welche Ausgabe erzeugt das Programm?
3.7 Lambda-Ausdrücke
Bei einem Lambda-Ausdruck handelt es sich um eine anonyme Funktion, die Ausdrücke und Anweisungen
enthalten und für die Erstellung von Delegaten oder Ausdrucksbaumstrukturen verwendet werden kann.
Alle Lambda-Ausdrücke verwenden den Operator Lambda =>, der so viel bedeutet wie wechselt zu. Auf der
linken Seite des Operators Lambda werden die Eingabeparameter angegeben (falls vorhanden), und auf der
rechten Seite bendet sich der Ausdruck oder Anweisungsblock. Der Lambda-Ausdruck x => x * x bedeutet x
wechselt x Mal zu x. Dieser Ausdruck kann wie folgt einem Delegattyp zugewiesen werden:
Listing 3.24: Ausdruckslambdas
(input parameters) => expression
//Beispiele
(x, y) => x == y
n => n<5
Listing 3.25: Anweisungslambdas
(input parameters) => {statement;}
//Beispiel :
n => { string s = n + " " + "World"; Console.WriteLine(s); }
3.8 IDisposable
IDisposable deniert eine Methode zur Freigabe von reservierten Ressourcen. Über diese Schnittstelle werden
hauptsächlich nicht verwaltete Ressourcen freigegeben. Der Garbage Collector gibt automatisch den für ein
30
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.8 IDisposable
verwaltetes Objekt reservierten Speicher frei, wenn dieses Objekt nicht mehr verwendet wird. Es kann allerdings
nicht vorausgesagt werden, wann die Garbage Collection stattndet. Darüber hinaus hat der Garbage Collector
keine Kenntnis von nicht verwalteten Ressourcen wie Fensterhandles oder oenen Dateien und Streams.
Mit der Dispose-Methode dieser Schnittstelle können nicht verwaltete Ressourcen in Verbindung mit dem Garbage Collector explizit freigegeben werden. Der Consumer eines Objekts kann diese Methode aufrufen, wenn das
Objekt nicht mehr benötigt wird. Verwenden Sie für den Aufruf einer Klasse, die die IDisposable-Schnittstelle
implementiert, einen try-nally-Block, um sicherzustellen, dass nicht verwaltete Ressourcen auch dann freigegeben werden, wenn die Anwendung aufgrund einer Ausnahme beendet wird.
Das Muster zum Verwerfen eines Objekts, Dispose-Muster genannt, legt eine Ordnung für die Lebensdauer von
Objekten fest.
Die Dispose-Methode eines Typs sollte alle Ressourcen freigeben, die dieser besitzt. Sie sollte auÿerdem alle Ressourcen freigeben, deren Eigentümer die Basistypen der Methode sind, indem die Dispose-Methode des übergeordneten Typs aufgerufen wird. Die Dispose-Methode des übergeordneten Typs sollte alle Ressourcen freigeben,
die dieser besitzt, und dann die Dispose-Methode seines übergeordneten Typs aufrufen. Dieses Muster wird
durch die Hierarchie der Basistypen weitergegeben. Um sicherzustellen, dass Ressourcen immer entsprechend
bereinigt werden, sollte eine Dispose-Methode auch mehrmals aufgerufen werden können, ohne eine Ausnahme
auszulösen.
Durch das Implementieren der Dispose-Methode für Typen, die nur verwaltete Ressourcen (z. B. Arrays) verwenden, werden keine Leistungsvorteile erzielt, da diese automatisch vom Garbage Collector freigegeben werden.
Verwenden Sie die Dispose-Methode primär für verwaltete Objekte, die systemeigene Ressourcen verwenden, und
für COM-Objekte, die für .NET Framework verfügbar sind. Verwaltete Objekte, die systemeigene Ressourcen
(z. B. die FileStream-Klasse) verwenden, implementieren die IDisposable-Schnittstelle.
Eine Dispose-Methode sollte die SuppressFinalize-Methode für das Objekt aufrufen, das es freigibt. Wenn sich
das Objekt gegenwärtig in der Finalisierungswarteschlange bendet, verhindert SuppressFinalize, dass dessen
Finalize-Methode aufgerufen wird. Beachten Sie, dass das Ausführen einer Finalize-Methode hohen Leistungsaufwand erfordert. Wenn Sie das Objekt bereits mit der Dispose-Methode bereinigt haben, muss die FinalizeMethode des Objekts nicht mehr vom Garbage Collector aufgerufen werden.
Im folgenden Codebeispiel wird das empfohlene Entwurfsmuster für das Implementieren einer Dispose-Methode
für Klassen veranschaulicht, die nicht verwaltete Ressourcen kapseln.
Ressourcenklassen werden i. d.R. von komplexen systemeigenen Klassen oder APIs abgeleitet und müssen entsprechend angepasst werden. Verwenden Sie dieses Codemuster als Anfangspunkt zum Erstellen einer Ressourcenklasse, und stellen Sie die erforderliche Anpassung basierend auf den Ressourcen, die Sie kapseln, bereit.
Listing 3.26: Dispose-Pattern
//Klasse, die IDisposable implementiert.
public class DisposableResource : IDisposable
{
private Stream resource;
private bool disposed;
// The stream passed to the constructor
// must be readable and not null.
public DisposableResource(Stream stream)
{
if (stream == null)
throw new ArgumentNullException("Stream in null.");
if (!stream.CanRead)
throw new ArgumentException("Stream must be readable.");
resource = stream;
}
disposed = false;
// Demonstrates using the resource.
// It must not be already disposed.
public void DoSomethingWithResource() {
if (disposed)
throw new ObjectDisposedException("Resource was disposed.");
// Show the number of bytes.
31
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.9 using
}
int numBytes = (int) resource.Length;
Console.WriteLine("Number of bytes: {0}", numBytes.ToString());
public void Dispose()
{
Dispose(true);
// Use SupressFinalize in case a subclass
// of this type implements a nalizer .
}
GC.SuppressFinalize(this);
protected virtual void Dispose(bool disposing)
{
// If you need thread safety, use a lock around these
// operations, as well as in your methods that use the resource.
if (!disposed)
{
if (disposing) {
if (resource != null)
resource.Dispose();
Console.WriteLine("Object disposed.");
}
// Indicate that the instance has been disposed.
}
}
}
resource = null;
disposed = true;
//Aufrufende Klasse
class Program
{
static void Main()
{
try
{
// Initialize a Stream resource to pass
// to the DisposableResource class.
Console.Write("Enter filename and its path: ");
string fileSpec = Console.ReadLine();
FileStream fs = File.OpenRead(fileSpec);
DisposableResource TestObj = new DisposableResource(fs);
// Use the resource.
TestObj.DoSomethingWithResource();
// Dispose the resource.
TestObj.Dispose();
}
}
}
catch (FileNotFoundException e)
{
Console.WriteLine(e.Message);
}
3.9 using
using
7 stellt
eine intuitive Syntax bereit, die die richtige Verwendung von IDisposable-Objekten sicherstellt.
Wenn Sie ein IDisposable-Objekt verwenden, sollten Sie es grundsätzlich deklarieren und in einer usingAnweisung instanziieren. Die using-Anweisung ruft die Dispose-Methode für das Objekt auf die richtige Weise
auf und (bei Verwendung wie oben gezeigt) verursacht, dass das Objekt seinen Gültigkeitsbereich verlässt,
7 using-Anweisung
32
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.10 Serialisierung
sobald Dispose aufgerufen wird. Innerhalb des using-Blocks ist das Objekt schreibgeschützt und kann nicht
geändert oder neu zugewiesen werden.
Mit der using-Anweisung wird sichergestellt, dass Dispose auch aufgerufen wird, wenn ein Ausnahmefehler auftritt, während Sie Methoden für das Objekt aufrufen. Sie erzielen dasselbe Ergebnis, indem Sie das Objekt in
einen try-Block einfügen und Dispose in einem nally-Block aufrufen. Auf diese Weise wird die using-Anweisung
vom Compiler übersetzt. Der Code im oben gezeigten Beispiel wird bei der Kompilierung auf folgenden Code erweitert (beachten Sie die zusätzlichen geschweiften Klammern zur Erstellung des begrenzten Gültigkeitsbereichs
für das Objekt):
Listing 3.27: using
using (System.IO.StreamReader sr = new System.IO.StreamReader(@"C:\Users\Public\Documents\test.txt"))
{
string s = null;
while((s = sr.ReadLine()) != null)
{
Console.WriteLine(s);
}
}
Aufgabe 3.8
Fügen Sie in Listing 3.27 das Werfen einer Ausnahme innerhalb des using-Blocks ein. Was
passiert? Schauen Sie sich den Ablauf im Debugger an.
3.10 Serialisierung
8 Serialisierung
beschreibt den Vorgang des Konvertierens eines Objekts in eine Form, die problemlos transpor-
tiert werden kann. So können Sie beispielsweise ein Objekt serialisieren und es über das Internet per HTTP
zwischen einem Client und einem Server transportieren. Am anderen Ende wird das Objekt durch Deserialisierung aus dem Stream wiederhergestellt.
Bei der XML-Serialisierung werden nur die öentlichen Felder und Eigenschaftenwerte eines Objekts in einen
XML-Stream serialisiert. Typinformationen werden bei der XML-Serialisierung nicht berücksichtigt. Wenn Sie
z. B. über ein Book-Objekt im Library-Namespace verfügen, wird es nicht in jedem Fall in ein Objekt desselben
Typs deserialisiert.
Wenn Sie ein Objekt serialisieren möchten, erstellen Sie zuerst das zu serialisierende Objekt, und legen Sie dann
dessen öentliche Eigenschaften und Felder fest. Dazu müssen Sie das Transportformat angeben, in dem der
XML-Stream gespeichert werden soll: als ein Stream oder als eine Datei. Wenn der XML-Stream beispielsweise
in einer permanenten Form gespeichert werden muss, erstellen Sie ein FileStream-Objekt.
Listing 3.28: Attribut Serializable
//Zustand ablegen
MySerializableClass myObject = new MySerializableClass();
// Insert code to set properties and elds of the object.
XmlSerializer mySerializer = new
XmlSerializer(typeof(MySerializableClass));
// To write to a le , create a StreamWriter object.
StreamWriter myWriter = new StreamWriter("myFileName.xml");
mySerializer.Serialize(myWriter, myObject);
myWriter.Close();
//Zustand wieder herstellen
MySerializableClass myObject;
// Construct an instance of the XmlSerializer with the type
// of object that is being deserialized .
XmlSerializer mySerializer =
new XmlSerializer(typeof(MySerializableClass));
// To read the le , create a FileStream.
FileStream myFileStream =
new FileStream("myFileName.xml", FileMode.Open);
// Call the Deserialize method and cast to the object type.
myObject = (MySerializableClass)
mySerializer.Deserialize(myFileStream)
Weitere Beispiele nden Sie unter Beispiele für die XML-Serialisierung.
8 Einführung
33
in die XML-Serialisierung
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.11 XML-Dokumentationskommentare
3.11 XML-Dokumentationskommentare
In Visual C# lässt sich eine Dokumentation des Codes erstellen. Dazu werden XML-Tags in spezielle Kommentarfelder des Quellcodes unmittelbar vor dem Codeblock eingefügt, auf den sie sich beziehen. Wenn Sie mit
/doc kompilieren, sucht der Compiler alle XML-Tags im Quellcode und erstellt eine XML-Dokumentationsdatei.
XML-Dokumentkommentare sind keine Metadaten und sind nicht in der kompilierten Assembly enthalten. Folglich ist der Zugri auf sie über Reektion nicht möglich. Wenn Sie in der IDE /// vor einem Codeblock eingeben,
erzeugt Ihnen VS automatisch einen passenden Kommentar. Möchten Sie zusammenhängende Methoden, z.B.
ctor (Konstruktoren), private Methoden o.ä. einfach ein- und ausklappen können, so verwenden Sie #region
ctor . . . #endregion.
3.12 Namenskonventionen
Nach dem Naming Guideline von Microsoft sollen Parameter, Klassen etc. wie folgt benannt werden, Basis ist
PascalCase und camelCase:
•
Nur Parameter und private Members sind camelCase
•
Namespace:CompanyName.TechnologyName[.Feature][.Design], Beispiel: Microsoft.Media.Design
•
Klassen: Nomen, Beispiel: class FileStream
•
Methoden: Verben, Beispiel: RemoveAll()
•
Interfaces: Nomen oder Nomenausdrücke, Beispiel: IServiceProvider, IFormatable
Diese Namensrichtlinien können Sie mit StyleCop validieren. Auf Assembly-Ebene analysiert das Werkzeug
FxCop vorwiegend Designkonventionen für Entwicklerinnen von Klassenbibliotheken (Design Guidelines for
Class Library Developers), aber auch einige Namenskonventionen.
Sie können natürlich auch einer anderen Richtlinie wie Zend für PHP folgen. Machen Sie dies in der Dokumentation Ihres Codes deutlich.
3.13 Aufgaben
Die Aufgaben stammen von .Net AT JKU.
Aufgabe 3.9
Was sind die Vorteile eines Enumerationstyps (z.B. enum Color red, blue, green) gegenüber int-
Konstanten (z.B. const int red = 0, blue = 1, green = 2;)?
Aufgabe 3.10
Studieren Sie die Klasse Enum aus der Online-Dokumentation des .NET-SDK und schreiben
Sie zwei Methoden zur Ein- und Ausgabe von Werten eines von Ihnen gewählten Enumerationstyps in textueller
Form. Können Sie die Methoden so allgemein halten, dass Sie damit Werte eines beliebigen Enumerationstyps
lesen und schreiben können?
Aufgabe 3.11
Schreiben Sie eine Methode double[,] MatrixMult(double[,] a, double[,] b) die zwei Matrizen a
und b multipliziert und das Ergebnis als Funktionswert liefert. Wenn sich a und b auf Grund ihrer Dimensionen
nicht multiplizieren lassen, soll die Methode eine Ausnahme liefern. allgemein halten, dass Sie damit Werte
eines beliebigen Enumerationstyps lesen und schreiben können?
Aufgabe 3.12
Schreiben Sie eine Methode, die aus einem Pfadnamen (z.B. c:
dotnet
samples
myFile.cs) den Dateinamen ohne Verzeichnisse und Dateiendung herausschält (z.B. myFile).
Aufgabe 3.13
Schreiben Sie eine switch-Anweisung, die zu einem gegebenen Monat die Anzahl seiner Tage
berechnet (ohne Schaltjahre zu berücksichtigen). Implementieren Sie eine Version, bei der das Monat durch eine
Zahl gegeben ist und eine andere, bei der es durch einen String gegeben ist. Vergleichen Sie den erzeugten Code
mittels ildasm.
34
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.13 Aufgaben
Aufgabe 3.14
Erzeugen Sie eine Tabelle der Quadrate und Wurzeln der ersten n natürlichen Zahlen. Verwen-
den Sie dazu formatierte Ausgabe. Zur Berechnung der Wurzel können Sie die Funktion Math.Sqrt verwenden.
Übergeben Sie n als Kommandozeilenparameter.
Aufgabe 3.15
Für welche Aufgaben würden Sie Klassen verwenden, für welche Structs? Nennen Sie Beispiele.
Aufgabe 3.16
Implementieren Sie einen Struct Complex zum Rechnen mit komplexen Zahlen. Sehen Sie ge-
eignete Konstruktoren vor sowie Properties, um auf dem reellen und imaginären Teil einer komplexen Zahl
zugreifen zu können. Implementieren Sie mittels Operator Overloading die Addition, Multiplikation und den
Vergleich komplexer Zahlen. Sehen Sie auch einen impliziten und einen expliziten Konversionsoperator vor, mit
dem man zwischen komplexen Zahlen und double konvertieren kann. Überschreiben Sie schlieÿlich auch die von
Object geerbten Methoden Equals, ToString und GetHashCode.
Aufgabe 3.17
Implementieren Sie eine Klasse GrowableArray, die ein dynamisch wachsendes Array beliebiger
Objekte darstellt. Ein Indexer soll den Zugri auf die einzelnen Elemente erlauben. Wird über einen Index
zugegrien, der gröÿer als die bisherige Arraylänge ist, so soll das Array dynamisch erweitert, d.h. durch ein
längeres ersetzt werden, wobei die Werte des alten Arrays den Anfang des neuen Arrays bilden sollen.
Aufgabe 3.18
Erweitern Sie Ihre Klasse GrowableArray so, dass die Elemente im Array mittels einer foreach-
Schleife durchlaufen werden können. Dazu muss GrowableArray das Interface IEnumerable implementieren.
Studieren Sie dieses Interface in der Online-Dokumentation des .NET-SDK.
Aufgabe 3.19
Schreiben Sie eine Klasse In mit Methoden zum Lesen von Zahlen, Wörtern, Strings, Zeichen
und Booleschen Werten von der Tastatur. Bauen Sie dabei auf die Klasse Console auf.
Aufgabe 3.20
Erweitern Sie Ihre Klasse In so, dass man mit ihr wahlweise von der Tastatur, von einer Datei
oder von einer Zeichenkette lesen kann.
Aufgabe 3.21
Nehmen Sie an, man möchte wissen, wieviele Objekte einer bestimmten Klasse C im Laufe eines
Programms erzeugt wurden. Implementieren Sie eine solche Klasse und versehen Sie sie mit einer statischen
Zählervariablen sowie mit einer statischen Methode, mit der die Zählervariable zurückgesetzt werden kann. Wo
muss die Zählervariable erhöht werden?
Aufgabe 3.22
Implementieren Sie ein Struct Time zur Verwaltung von Uhrzeiten. Intern sollen Uhrzeiten in
Sekunden gespeichert werden. Es soll aber drei Properties geben, die den Stunden-, Minuten-, und Sekundenanteil
einer Uhrzeit liefern. Sehen Sie auch überladene Operatoren und Konstruktoren vor, um mit Uhrzeiten bequem
rechnen zu können.
Aufgabe 3.23
Diskutieren Sie Vor- und Nachteile von Ref-Parametern, Out-Parametern und Parametern, die
als Funktionsergebnisse zurückgegeben werden.
Aufgabe 3.24
Implementieren Sie eine Methode Clear, mit der eine beliebige Anzahl von Elementen eines
beliebigen int-Arrays auf 0 gesetzt werden kann (z.B. Clear(arr, 3, 4, 9, 12);).
Aufgabe 3.25
Was ist der Unterschied zwischen einer abstrakten Klasse und einem Interface?
Aufgabe 3.26
Gegeben sei ein Array von Objekten der gleichen Art (z.B. Strings, Bruchzahlen oder sonstige
Objekte). Schreiben Sie eine Methode Sort(arr, compare), die das Array arr sortiert und dazu eine Vergleichsmethode compare(x, y) verwendet, die als Delegate übergeben wird und zwei Objekte x und y des Arrays miteinander
vergleicht, d.h. feststellt, ob x gröÿer, kleiner oder gleich y ist.
Aufgabe 3.27
Implementieren Sie eine Zählerklasse Counter mit einer Methode Add(x) zum Erhöhen des
Zählerwerts und einer Methode Clear() zum Löschen des Zählerwerts. Wenn der Zählerwert eine bestimmte Bedingung erfüllt (z.B. Zählerwert > n) sollen eine oder mehrere Aktionen ausgelöst werden. Sowohl die Bedingung
als auch die Aktionen sollen als Delegates an die Zählerklasse übergeben werden können.
Aufgabe 3.28
Schreiben Sie eine Methode static void Plot (Function f )
...
der man eine Funktion eine
Delegate-Typs delegate double Function(double x); übergeben kann und die ein zweidimensionales Diagramm
dieser Funktion zeichnet. Verwenden Sie der Einfachheit halber als Grak eine zweidimensionale char-Matrix.
35
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
3.13 Aufgaben
Aufgabe 3.29
Die Methode Convert.ToInt32(s) konvertiert eine im String s gespeicherte Zahlenfolge in eine
int-Zahl. Dabei können verschiedene Ausnahmen auftreten, wenn s nicht das richtige Format hat oder keinen
gültigen Wert enthält. Studieren Sie die möglichen Ausnahmen in der .NET-SDK-Dokumentation und schreiben
Sie ein Programm, in dem diese Ausnahmen durch eine try-catch-Anweisung abgefangen werden.
Aufgabe 3.30
Implementieren Sie eine sortierte verkettete Liste mit den Operationen Add(x) und Remove(x)
sowie mit einem Property Count, das die Anzahl der Elemente der Liste liefert. Auÿerdem soll es einen Indexer
geben, mit dem man auf jedes Listenelement zugreifen kann. Der Typ der Listenelemente soll beliebig sein.
Aufgabe 3.31
Schreiben Sie einen generischen binären Suchbaum BinaryTree<K, V>, der Werte vom Typ V
unter ihrem Schlüssel vom Typ K speichert. Sehen Sie eine Methode Insert vor, mit der man ein Schlüssel/WertPaar in den Baum einfügen kann, sowie eine Methode Contains, die prüft, ob ein bestimmter Schlüssel im Baum
enthalten ist. Schreiben Sie auch einen Indexer, der den Wert zu einem gegebenen Schlüssel liefert oder eine
Ausnahme auslöst, wenn der Schlüssel nicht gefunden wurde.
Aufgabe 3.32
Verwenden Sie den in der Online-Dokumentation von .NET beschriebenen Typ List<T>, um
eine Liste von Wortlisten anzulegen. Lesen Sie einen Text aus mehreren Zeilen, wobei jede Zeile aus mehreren Wörtern (Buchstabenfolgen) besteht. Alle Wörter einer Zeile sollen in einer Liste vom Typ List<string>
gespeichert werden. Bilden Sie dann aus den Wortlisten der einzelnen Zeilen wiederum eine Liste vom Typ
List<List<string.
Aufgabe 3.33
Schreiben Sie eine generische Methode list = Copy(array), die ein Array beliebigen Elementtyps
in eine generische Liste gleichen Elementtyps kopiert. Verwenden Sie als Listentyp List<T> (siehe OnlineDokumentation von .NET).
Aufgabe 3.34
Implementieren Sie eine Klasse zur Verwaltung einer verketteten Liste von Namen (Strings).
Sehen Sie folgende Iteratoren vor:
•
Eine allgemeine Iterator-Methode, die alle in der Liste gespeicherten Namen liefert.
•
Eine spezische Iterator-Methode, die nur jene Namen der Liste liefert, die mit einem bestimmten String
beginnen. Der String soll als Parameter mitgegeben werden.
•
Ein Iterator-Property, das nur jene Namen der Liste liefert, die länger als 5 Buchstaben sind.
Aufgabe 3.35
Versehen Sie eine von Ihnen implementierte Klasse mit XML-Kommentaren und sehen Sie sich
an, was der Compiler daraus erzeugt, wenn er mit der Option /doc:myFile.xml aufgerufen wird.
36
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
4 Komponenten
1 In
der Softwarebranche wird mit dem Begri Komponente ein wiederverwendbares Objekt bezeichnet, das für
Clients auf standardisierte Art eine oder mehrere Schnittstellen verfügbar macht. Zu den am häugsten verwendeten Komponenten im Bereich der .NET Framework-Programmierung zählen die visuellen Steuerelemente,
die Windows Forms hinzugefügt werden. Wird eine Komponente in C# erstellt, kann diese auch von Clients
verwendet werden, die in einer anderen Sprache geschrieben sind, die der CLS (Common Language Specication) entspricht dabei ist hervorzuheben, dass die Komponenten sich durch ihre Attribute selbst beschreibt
und somit keine globale Registrierung zwingend erforderlich ist. Die Anwendung, die eine solche Komponente
nutzen möchte, muss lediglich das zugehörige Assembly einbinden.
2 Während
der Begri Komponente viele Bedeutungen hat, ist im .NET Framework eine Komponente eine
Klasse, die das Interface IComponent implementiert (oder von einer solchen Klasse abgeleitet ist). Ohne diese
engere Denition könnte man jedes Assembly als Komponente bezeichnen. Wobei, wenn man schaut, welche
Klassen IComponent implementieren, so ist man nicht weit davon entfernt . . . .
•
System.ComponentModel.Component: Basisimplemantation von IComponent typische Basisklasse für
nicht-visuelle Komponenten
•
System.Web.UI.Control: Basisklasse für alle ASP.NET-Controls, implementiert IComponent direkt
•
System.Windows.Forms.Control: erbt von System.ComponentModel.Component und ist die Basisklasse
für alle WinForm-Controls
•
System.Data.DataSet: erbt von System.ComponentModel.MarshalByValueComponent implementiert
IComponent.
•
System.Diagnostics.EventLog: erbt von System.ComponentModel.Component.
Abbildung 4.1: IComponent Quelle: Leveraging .NET Components
Dies liegt vermutlich daran, dass IComponent nicht viel vorgibt:
•
IComponent implementiert IDisposable
•
public property:
ISiteSite{get; set; }
mit
ISite erbt von IServiceProvider:
∗ ObjectGetService(T ypeserviceT ype)
IComponentComponent{get; }
Ruft das Dienstobjekt des angegebenen Typs ab.
Ruft bei der Implementierung durch eine Klasse die der ISite zu-
geordnete Komponente ab.
IContainerContainer{get; }
Ruft bei der Implementierung durch eine Klasse den der ISite zuge-
ordneten Container ab. IContainer stellt Add, Dispose, Remove zur Verfügung.
boolDesignM ode{get; } Bestimmt bei der Implementierung durch eine Klasse, ob sich die Komponente im Entwurfsmodus bendet.
1 Erstellen von Komponenten
2 Programmieren mit Komponenten
37
4 Komponenten
stringN ame{get; set; }
Ruft bei der Implementierung durch eine Klasse den Namen der Kompo-
nente ab, die ISite zugeordnet ist, oder legt diesen fest.
•
public event:
•
public method, von IDisposable:
eventEventHandlerDisposed;
voidDispose()
Komponente:
Jede Komponente in .Net implementiert das Interface IComponent.
Unter einem Control versteht Microsoft eine UI-Komponente, diese werden von eine der zwei Klassen System.Windows.Forms.Control oder System.Web.UI.Control abgeleitet. Abbildung 4.2 zeigt die zugehörigen In-
3
terfaces und die Objekthierarchie Container .
Abbildung 4.2: Control
Control vs Komponente:
Jedes Control ist eine Komponente, aber nicht jede Komponente ist ein Control.
Das Thema sinnvoll abzugrenzen und zu strukturieren ist schwierig. Aufgrund der historischen Entwicklung
gibt es viele Begrie und Technologien: COM, ActiveX, Add-In, Excel-Addin, Plugin, ...
COM
Das Component Object Model (COM) ist Microsofts Ansatz für objektorientierte Softwarekomponenten.
Das .NET Framework sollte ursprünglich COM 3.0 heiÿen. COM-Komponenten müssen in die Registry
eingetragen werden. COM basiert auf dem Client/Server-Prinzip. COM läuft nur unter Windows.
DCOM Distributed COM, Komponentenkommunikation über Prozess- und Maschinengrenzen hinweg möglich.
ActiveX ActiveX ist ein Dienst auf Basis von COM und DCOM.
ActiveX-Control Früher wurden diese Steuerelemente auch OLE-Steuerelemente bzw. OCXes (OLE Custom
Controls) genannt.
Es existieren viele COM-Komponenten, die in .Net genutzt werden sollen/müssen. Hier trit man auf COMInterop. Bis Sie mit dem Studium fertig sind, existiert dieses Problem hoentlich nicht mehr ;-) An dieser
Stelle sei jedoch erwähnt, Microsoft bietet eine Brücke zwischen .Net und COM an und die Abbildung von
Parametertypen in COM nach .Net ist ein Problem.
Wann immer man eine Applikation mit fremden Komponenten zur
Laufzeit erweitert, steht man vor folgenden
Problemen:
•
Discovery: Komponente aunden
•
Laden und Entladen (freigeben)
•
Absichern der Host-Anwendung
3 Custom
38
Design-time Control
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
4.1 Eigene GUI Steuerelemente
•
gegen Fehler in der Komponente
deren Daten und Prozesse
Versionierung
.Net bietet hierfür zwei Frameworks, wie man eigene Applikationen für fremde Komponenten zur Laufzeit önen
kann. Microsoft verfolgt diese Ansätze selbst in den Oce-Produkten und in Visual Studio. Beide Frameworks
können auch kombiniert werden dies geschieht z.B. in VS2010. Doch schauen wir uns zunächst die visuellen
Komponenten an, die zur Designzeit in die Applikation eingebunden werden.
4.1 Eigene GUI Steuerelemente
4 Wenn
Sie ein neues Steuerelement entwerfen, müssen Sie zuerst festlegen, welche programmiertechnischen
Mittel dazu eingesetzt werden sollen. Unter .NET gibt es drei unterschiedliche Szenarien:
•
Sie erweitern ein existierendes Steuerelement durch Ableitung um spezische Funktionalitäten. Ähnlich,
wie Sie eine benutzerdenierte Form erstellen, die aus Form abgeleitet ist, können Sie auch ein vorhandenes
Steuerelement ableiten, um daraus eine eigene Klasse zu erstellen. Sie brauchen die abgeleitete Klasse nur
um die Fähigkeiten zu erweitern, die Sie von Ihrem Steuerelement erwarten.
•
Sie kombinieren mehrere existierende Steuerelemente zu einem neuen. Dazu stellt die Klasse UserControl
einen Container bereit, der einem Panel ähnelt. Diesem Container lassen sich mehrere Steuerelemente
aus der Toolbox hinzufügen, die in Kombination das neue Steuerelement bilden. Die Eigenschaften und
Methoden der im UserControl enthaltenen Steuerelemente können oengelegt oder ausgeblendet werden.
UserControl ist ein Steuerelement und von Control abgeleitet. Es veröentlicht daher seinerseits bereits
alle von der Basis geerbten Member.
•
Die beiden vorgenannten Entwicklungsmöglichkeiten basierten auf vorhandenen Steuerelementen. Wollen
Sie ein vollkommen neues Steuerelement entwickeln, müssen Sie die Klasse Control ableiten. Das bedeutet
aber auch, dass Sie die grasche Darstellung des Steuerelements selbst in die Hand nehmen müssen,
da es dafür keine Basiskomponenten gibt. Wenn Sie bedenken, dass allein schon die Aktivierung eines
Steuerelements eine Änderung der Darstellung zur Laufzeit bewirkt, können Sie sich vermutlich vorstellen,
dass der Aufwand beträchtlich ist.
Für welche Alternative Sie sich entscheiden, hängt letztendlich von den Anforderungen an das neue Steuerelement ab. Wann immer es aber möglich ist, sollten Sie auf existierende und damit letztendlich auch auf bewährte
Komponenten zurückgreifen. Sie sparen damit nicht nur Entwicklungszeit, sondern auch die Zeit für ausgiebige
Tests.
4.2 Managed Extensibility Framework (MEF)
5 MEF
ist eine Bibliothek die das Problem der Erweiterbarkeit zur Laufzeit (Runtime Extinsibility) löst. Sie ver-
einfacht die Implementierung von Erweiterbaren Anwendungen und bietet Ermittlung von Typen, Erzeugung
von Instanzen und Composition Fähigkeiten an. Die wichtigste Module im Core der Bibliothek sind Catalog
und CompositionContainer. Das Catalog kontrolliert das Laden von Komponenten, während das CompositionContainer die Instanzen erzeugt und diese an die entsprechenden Variablen bindet. Parts sind die Objekte die
vom Type Export oder Import sein können. Die Exports sind im beschriebenen Beispiel die Komponenten,
die geladen und instanziiert werden sollen. Die Imports sind die Variablen an die die Instanzen von der Komponenten gebunden werden sollen. Zum Beispiel wäre die bereits erwähnte Komponente Component1 in der
MEF-Anwendung ein Export. Die Variable proxy vom Type IComponent die die Instanz dieser Komponente
enthalten soll, wäre ein Import. MEF gibt es erst mit .Net 4.0, also der aktuellen Version. Hiermit soll es leichter
werden, Applikationen zu erweitern. Leichter bringt leider auch Nachteile mit sich, wie z.B.
•
Es wird keine Isolation in AppDomains unterstützt.
•
Es wird kein dediziertes Berechtigungssystem unterstützt.
•
Es wird keine Versionierung unterstüzt.
4 Steuerelemente entwickeln
5 Managed Extensibility Framework
39
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
4.3 Managed Add-in Framework (MAF)
Abbildung 4.3: MEF
Whitepaper und weitere Informationen zu diesem Framework nden Sie unter Welcome to the MEF community
site.
4.3 Managed Add-in Framework (MAF)
MAF gibt es seit .Net 3.5, also schon länger als MEF. MAF unterstützt Versionierung und verschiedene AppDomains, ist jedoch, wie wir gleich sehen werden, deutlich komplexer in der Architektur.
Microsoft liefert eine Vielzahl von Produkten für sehr unterschiedliche Benutzergruppen. Individualisierte Lösungen werden anderen Unternehmen überlassen. So gibt es viele Unternehmen auf dem Markt, die Oce-Produkte
für die Bedürfnisse ihrer Kunden anpassen und Komponente entwickeln, um Oce mächtiger zu machen. Für
die Kunden bedeutet dies, dass sie sich nicht in eine weitere Umgebung einarbeiten müssen, sondern in ihrem geliebten Excel oder Word oder . . . bleiben können. MIS, jetzt Infor, entwickelte beispielsweise einen OLAP-Server
und erlaubte durch ein Add-In Controllern Zugri auf diesen Server. Controller können so, ähnlich wie sie es
von den Pivot-Tabellen in Standard-Excel kennen, auf multidimensionalen Daten navigieren, diese analysieren
und für folgende Jahre eine Planung simulieren und schlieÿlich auf den Server schreiben. Dass dies nicht in VBA
geht, ist oensichtlich. Lesende oder schreibende Zugrie auf Millionen von Daten wird üblicherweise mit C++
implementiert. An der Oberäche bendet sich ein Add-In für Excel. Mit VS2010 können diese Add-Ins noch
leichter implementiert werden als noch vor wenigen Jahren, denn das Objektmodell steht bereits Out-of-the-box
zur Verfügung. Mit einem Add-In kann also zusätzliche Funktionalität für ein Oce-Produkt, aber auch für
Visual Studio oder jedes andere für Add-Ins geönete Produkt, implementiert werden. Dieses Add-In kann auf
Daten fremder Systeme, wie Webservices, SAP-Connectors, MIS-Alea oder andere zugreifen.
Selbst auf dem Server kann über das Objektmodell auf Dokumente zugegrien und diese verändert werden,
ohne dass Oce auf dem Server installiert sein muss. Grundlage hierfür ist das oene XML-Format, in dem
Oce mittlerweile seine Dokumente ablegt.
40
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
4.3 Managed Add-in Framework (MAF)
Abbildung 4.4: Listen
4.3.1 Laden eines Add-Ins
6 Die folgenden Schritte ausgeführt wenn ein Benutzer ein Dokument önet, die Teil einer Microsoft Oce-Lösung
ist:
1. Die Microsoft Oce-Anwendung überprüft die benutzerdenierten Dokumenteigenschaften, ob verwaltete
Codeerweiterungen, die dem Dokument zugeordnet sind. Wenn es verwaltete Codeerweiterungen gibt, lädt
die Anwendung VSTOEE.dll, die VSTOLoader.dll lädt. Diese sind nicht verwaltete DLLs, die die LoaderKomponenten für Visual Studio 2010 Tools for Oce Runtime sind. VSTOLoader.dll .NET Framework
lädt und startet den verwalteten Hostbereich Visual Studio Tools for Oce runtime.
2. Wenn das Dokument von einem anderen Speicherort als dem lokalen Computer geönet wird, überprüft der
Visual Studio Tools for Oce runtime , dass der Speicherort des Dokuments in der Liste vertrauenswürdige
SpeicherorteVertrauensstellungscenter für die bestimmten Oce-Anwendung ist. Wenn der Speicherort des
Dokuments nicht in einem vertrauenswürdigen Speicherort ist, die Anpassung ist nicht vertrauenswürdig
und der Ladevorgang beendet hier.
3. Visual Studio Tools for Oce runtime installiert die Lösung, wenn wurde noch nicht installiert, die neuesten Anwendungs- und Bereitstellung Manifeste lädt und eine Reihe von Sicherheitsüberprüfungen führt.
4. Wenn die Anpassung auszuführenden vertrauenswürdig ist, verwendet Visual Studio Tools for Oce runtime die Bereitstellung-Manifest und Anwendungsmanifest nach Assembly Updates suchen. Wenn eine neue
Version der Assembly verfügbar ist, lädt die Common Language Runtime die neue Version der Assembly
zum Cache im ClickOnce auf dem Clientcomputer.
5. Visual Studio Tools for Oce runtime erstellt eine neue Anwendungsdomäne, in die die Anpassungsassembly geladen werden.
6. Visual Studio Tools for Oce runtime lädt die Anpassungsassembly in der Anwendungsdomäne.
7. Visual Studio Tools for Oce runtime Ruft den Ereignishandler Startup in die Anpassungsassembly.
4.3.2 Modell
7 )Das
Add-In-Modell besteht aus einer Reihe von Segmenten, die die Add-In-Pipeline (wird auch als Kommu-
nikationspipeline bezeichnet) bilden, die für die gesamte Kommunikation zwischen dem Add-In und dem Host
verantwortlich ist. Die Pipeline ist ein symmetrisches Kommunikationsmodell aus Segmenten, die Daten zwischen einem Add-In und dessen Host austauschen. Die Entwicklung dieser Segmente zwischen dem Host und
dem Add-In stellt die erforderlichen Abstraktionsebenen bereit, die die Versionsverwaltung und Isolation des
Add-Ins unterstützen.
6 Architecture of Document-Level
7 Quelle: Übersicht über Add-Ins
41
Customizations
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
4.3 Managed Add-in Framework (MAF)
Abbildung 4.5: Listen
Die Assemblys für diese Segmente müssen sich nicht in derselben Anwendungsdomäne benden. Sie können
ein Add-In in seine eigene neue Anwendungsdomäne, in eine vorhandene Anwendungsdomäne oder sogar in
die Anwendungsdomäne des Hosts laden. Sie können mehrere Add-Ins in dieselbe Anwendungsdomäne laden.
Dadurch können die Add-Ins Ressourcen und Sicherheitskontexte gemeinsam nutzen.
Das Add-In-Modell unterstützt und empehlt eine optionale Grenze zwischen dem Host und dem Add-In, die
als Isolationsgrenze bezeichnet wird (auch unter der Bezeichnung Remotegrenze bekannt). Diese Grenze kann
eine Anwendungsdomänen- oder Prozessgrenze sein.
Das Vertragssegment in der Mitte der Pipeline wird in die Anwendungsdomäne des Hosts und in die Anwendungsdomäne des Add-Ins geladen. Der Vertrag deniert die virtuellen Methoden, mit denen der Host und das
Add-In Typen austauschen.
Um die Isolationsgrenze zu überwinden, müssen Typen entweder Verträge oder serialisierbare Typen sein. Typen, die weder Verträge noch serialisierbare Typen sind, müssen von den Adaptersegmenten in der Pipeline
konvertiert werden.
Die Ansichtssegmente der Pipeline sind abstrakte Basisklassen oder Schnittstellen, die dem Host und dem
Add-In eine Ansicht der gemäÿ der Denition im Vertrag gemeinsam genutzten Methoden bereitstellen.
Der Unterschied zwischen einem Add-In und einem Host besteht darin, dass der Host das Element ist, das das
Add-In aktiviert. Der Host kann das gröÿere der beiden Elemente, wie z. B. eine Textverarbeitungsanwendung
mit ihren Rechtschreibprüfungen, oder das kleinere der beiden Elemente sein, wie z. B. ein Instant MessagingClient mit einem eingebetteten Media Player. Das Add-In-Modell unterstützt Add-Ins sowohl in Client- als
auch in Serverszenarios. Beispiele für Server-Add-Ins sind Add-Ins, die Virenschutz, Spamlter und IP-Schutz
für Mailserver bereitstellen. Beispiele für Client-Add-Ins umfassen Referenz-Add-Ins für Textverarbeitungsprogramme, spezielle Funktionen für Grakprogramme und Spiele sowie Virenschutz für lokale E-Mail-Clients.
8 Im
.NET Framework 3.5 ndet man unter dem Namespace System.AddIn ein Objektmodell mit dem man
Anwendungen um ein Plug-In Modell erweitern kann.
System.AddIn arbeitet hier sehr strikt mit einer Interface Denition. Die Denition ist die Grundlage für
entsprechende Adapterklassen, die letztendlich dafür verantwortlich sind den View zu aktualisieren. Der View
auf beiden Seiten der Pipeline ist dann einmal mit dem Host, zum anderen mit dem AddIn verbunden.
Die Adapter und der Contract sind somit nicht mit dem Host und nicht mit dem AddIn direkt referenziert,
sondern werden durch die System.AddIn Infrastruktur auf Basis von Attributen und Ableitungen entsprechend
geladen und instanziert.
Um ein Add-In zu entwickeln muÿ der Add-In Entwickler nur folgende Abhängigkeiten beachten:
•
Man muÿ den entsprechenden View des Contracts referenzieren, eine abstrakte Basisklasse die überschrieben wird.
8 .NET
42
3.5 Feature: System.AddIn
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
4.3 Managed Add-in Framework (MAF)
•
Das Add-In wird noch Attributiert, um dem Framework mitzuteilen, das es sich hier um ein Add-In
handelt, das über die Infrastruktur instanziert werden kann.
•
Kopieren des Add-In Assemblies in die entsprechende Verzeichnisstruktur. Der Host kann dann das Add-In
laden.
Hier ein kleines Beispiel einer Add-In Implementierung.
Listing 4.1: Add-In
using System;
using System.AddIn;
using Demo.View.Addin;
namespace Demo.Addin.Number2
{
[AddIn( "Hello World (TechTalk Version)", Version="1.0.0.0")]
public class TechTalkHelloWorld : HelloWorld
{
public override string SayHelloWorld()
{
return "Hallo TechTalk!";
}
}
}
HelloWorld ist der View von dem der Entwickler ableitet. Auf der Host-Seite gibt es nun den entsprechenden
Aktivierungscode für Add-Ins. Hier ein Beispiel um ein Add-In zu nden und zu instanzieren.
Listing 4.2: Add-In nutzen
// pipeRoot ist das Verzeichnis in welchem die Infrastruktur sucht
//tokens enthält alle verfügbaren Add−Ins die das Interface HelloWorld implementiert haben.
Collection<AddInToken> tokens = AddInStore.FindAddIns( typeof( HelloWorld ), pipeRoot );
// Code zum anzeigen der verfügbaren Add−Ins und Auswahl−Logik
// das ausgewählte Token wird referenziert
AddInToken helloWorldToken = tokens[ number - 1 ];
// Instanzieren des Add−Ins
//Add−In in einer eigenen AppDomain im gleichen Prozess des Hosts instanziieren
HelloWorld helloWorld = helloWorldToken.Activate<HelloWorld>( AddInSecurityLevel.Host );
// Nutzen des Add−Ins
Console.WriteLine( helloWorld.SayHelloWorld() );
Man erkennt schon, dass Add-Ins dementsprechend Remotable sein müssen, da .NET Remoting als Kommunikationsinfrastruktur genommen wird.
Um das Add-In nun in einen eigenen Prozess zu instanziieren, kann man folgende Zeilen schreiben.
AddInToken helloWorldToken = tokens[ number - 1 ];
AddInProcess process = new AddInProcess();
process.Start();
HelloWorld helloWorld = helloWorldToken.Active<HellWorld>( process,AddInSecurityLevel.Host );
Console.WriteLine( helloWorld.SayHelloWorld() );
Beim Benutzen des Add-Ins wird entsprechend ein neuer Prozess hochgezogen. Die Infrastruktur stellt hierzu
den Host AddInProcess32.exe zur Verfügung.
Die Kommunikation zwischen dem gestarteten Prozess und dem Host läuft dann über den IPC Channel von
.NET Remoting. Die Inter-Process-Communication des Betriebssystems erlaubt es direkt zwischen AppDomains
auf der gleichen physikalischen Maschine performant zu kommunizieren und .NET Remoting bietet eben diesen
Channel an, der dann auch konsequent von AddInProcess32.exe genutzt wird.
Das System.AddIn Objektmodell erlaubt es nun mit relativ einfachen Mitteln, eine Plugin Architektur in eigene Anwendungen einzubauen, mit dem Vorteil der Versionierung und der Isolation durch die verfügbaren
Aktivierungsmöglichkeiten.
Das
Hauptproblem
bei
der
Übertragung
von
UI-Elementen
besteht
nun
darin,
dass
die
Klassen
im
System.Windows.Forms-Namespace nicht serialisierbar sind und somit nicht direkt übertragen werden können.
Die leiten zwar von MarshalByRefObject ab, können aber auch nicht per Referenz übertragen werden. Doch
wie sonst soll die Kommunikation erfolgen? Eine Lösung zum diesem Problem ist hier beschrieben Windows
43
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
4.3 Managed Add-in Framework (MAF)
Forms Support für System.AddIn, Teil 1. Wie das in WPF funktioniert, wird hier beschrieben: Gewusst wie:
Erstellen eines Add-Ins, das eine Benutzeroberäche zurückgibt.
44
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
5 Windows Presentation Foundation
Abbildung 5.1: User Experience
Viele Anwendungen werden vorwiegend von Entwicklern entworfen. Das CI designen Pros, die auch Vorgaben
für Schriften und Farben der Produkte machen, das Look and Feel stammt jedoch häug von mehr oder weniger
begabten Entwicklern. Sie gehen nüchtern davon aus, wenn das Produkt die tollen Features hat und diese für
sie gut bedienbar sind, dann reicht das. Gerade der Erfolg von Apple zeigt, dass Begeisterung für ein Produkt
etwas anderes ist. Zu diesem Thema gibt es einen ganzen Forschungszweig User Experience (UX) und Firmen
investieren zunehmend in die Messung dieser Erfahrungen. Google liefert beispielsweise 15.300.000 Treer zu
diesem Begri.
User-experience is not like usability - it is about feelings. The aim here is to create happiness. You
want people to feel happy before, during and after they have used your product.
Baekdal, Thomas. 2006
1
Dies sind sicher hohe Ansprüche für eine nüchterne DB-Modellierungsanwendung. Dennoch ist es bei jedem
gröÿeren Produkt mit dem entsprechenden Budget sinnvoll, UX zu berücksichtigen und Pros hinzuzuziehen.
Mehr zu diesem Thema nden Sie z.B. unter What's new on Design IBM, An Agile approach to User Experience
and Design oder Windows User Experience Interaction Guidelines
Wie sieht die übliche Zusammenarbeit zwischen Designer und Entwicklern aus bei Desktopapplikationen und
momentan noch verbreiteter im Web? Designer entwerfen coole Oberächen mit ihrem Lieblingstool auf ihrem
Lieblingssystem, runde Ecken, Farbverläufe, Übergänge bei Aktionen, ... sehr schön anzusehen. Entwickler
uchen und möchten sich eigentlich auf die Funktionalität konzentrieren, stattdessen kämpfen sie Stunden,
Wochen um die pixelgenaue Abbildung der in PNG o.ä. gelieferten Vorlagen. Kämpfe mit Vorgesetzten, ob man
nicht hier und da Abstriche an der Vorlage machen kann. Aber gerade im Web, ist Präsentation wichtig, es soll
in jedem Browser gleich aussehen.
Gesucht ist also ein einheitliches Format, das leicht unter Entwicklern und Designern auszutauschen ist und den
Anforderungen der Designer genügt. Einfach auszutauschendes Format schreit geradezu nach XML. Basierend
auf XML gibt es Beschreibungssprachen für Oberächen. Ein ausführliche Übersicht nden Sie bei Wikipedia
List of user interface markup languages. Microsoft wollte nicht nur eine mächtige Beschreibungssprache, die allen
denkbaren Designeransprüchen genügt, sondern auch eine 1:1-Integration in ein OO-Modell, genauer gesagt, in
ihr .Net-OO-Framework. Es wurde daher eine neue Beschreibungssprache deniert XAML und von Grund
auf ein neues Framework aufgesetzt, das sich in .Net integriert und alle XAML-Möglichkeiten in entsprechende
Objekte und Eigenschaften abbildet. Eine Oberächenanwendung in WPF enthält also nicht mehr irgendwie
generierten Code, den man nicht anfassen darf, sondern das Form (oder jedes andere visuelle Element) besteht
1 The
Battle Between Usability and User-Experience
45
5.1 Architektur
Abbildung 5.2: User Experience
aus zwei oder mehr partiellen Klassendenitionen: XAML deniert das Aussehen und visuelle Verhalten deklarativ mit 1:1-Abbildung in das zugehörige Objektmodell und klassischer C#-Code bildet die Logik ab.
5.1 Architektur
Abbildung 5.3: WPF Architektur
2 Das
primäre WPF-Programmiermodell wird mithilfe von verwaltetem Code bereitgestellt. Am Anfang der
Entwurfsphase von WPF gab es einige Diskussionen darüber, wo die Linie zwischen den im System verwalteten
Komponenten und den nicht verwalteten gezogen werden sollte. Die CLR stellt eine Reihe von Features bereit,
die zu einer produktiveren und robusteren Entwicklung führen (einschlieÿlich Speicherverwaltung, Fehlerbehandlung, allgemeines Typsystem usw.), die jedoch auch ihren Preis haben.
PresentationFramework, PresentationCore und milcore stellen die Hauptbestandteile des Codes von WPF dar.
Milcore ist in einem nicht verwalteten Code geschrieben, um eine enge Integration mit DirectX zu ermöglichen.
Alle Anzeigen in WPF erfolgen durch das DirectX-Modul, um ein ezientes Hardware- und Softwarerendering zu gewährleisten. WPF erforderte auÿerdem eine Feinsteuerung des Speichers und der Ausführung. Das
Erstellungsmodul in milcore ist äuÿerst leistungsabhängig und erforderte im Sinne der Leistungsverbesserung
die Aufgabe vieler CLR-Vorteile. Sowohl WPF als auch Silverlight können XAML verarbeiten, also parsen und
rendern (darstellen). Es gibt auch viele Designerwerkzeuge, die einen Export oder Import von XAML unterstützen.
2 WPF-Architektur
46
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
5.1 Architektur
Abbildung 5.4: WPF wesentliche Klassen
Die meisten WPF-Objekte werden aus DispatcherObject abgeleitet, in dem die grundlegenden Konstrukte zur
Handhabung von Parallelität und Threading bereitgestellt werden. WPF basiert auf einem vom Verteiler implementierten Nachrichtensystem. Die Funktionsweise ist mit der bekannten Meldungsverteilschleife in Win32
zu vergleichen. Tatsächlich verwendet der WPF-Verteiler User32-Meldungen zum Ausführen von threadübergreifenden Aufrufen.
WPF stellt ein umfangreicheres Eigenschaftensystem bereit, das vom DependencyObject-Typ abgeleitet wird.
Das Eigenschaftensystem ist insofern wirklich ein Abhängigkeitseigenschaftensystem, als es die Abhängigkeiten
zwischen den Eigenschaftsausdrücken erfasst und im Falle von Abhängigkeitsänderungen die Eigenschaftenwerte
automatisch erneut überprüft. Wenn Sie z. B. eine Eigenschaft verwenden, die Werte erbt (z. B. FontSize), wird
das System automatisch aktualisiert, wenn sich die Eigenschaft in einem übergeordneten Element des Elements
ändert, das den Wert erbt.
Anhand von Measure kann eine Komponente seine Gröÿe bestimmen. Hierbei handelt es sich um eine von
Arrange getrennte Phase, da viele Situationen auftreten können, in denen ein übergeordnetes Element ein
untergeordnetes Element auordert, mehrere Measures zur Ermittlung seiner optimalen Position und Gröÿe
auszuführen. Die Tatsache, dass untergeordnete Elemente von übergeordneten Elementen zur Durchführung von
Measures aufgefordert werden, verdeutlicht eine andere Schlüsselphilosophie von WPF Das Anpassen der Gröÿe
an den Inhalt. Alle Steuerelemente in WPF unterstützen die Fähigkeit, sich an die natürliche Gröÿe ihres Inhalts
anzupassen. Dies führt zu einer einfacheren Lokalisierung und ermöglicht aufgrund der Gröÿenanpassung ein
dynamisches Elementlayout. Anhand der Arrange-Phase kann ein übergeordnetes Element jedes untergeordnete
Element positionieren und dessen endgültige Gröÿe bestimmen.
Jedes Eingabeereignis wird in mindestens zwei Ereignisse konvertiert in ein Vorschauereignis und in das tatsächliche Ereignis. Alle WPF-Ereignisse können innerhalb der Elementstruktur weitergeleitet werden. Ereignisse
steigen in Blasen auf, wenn sie von einem Ziel entlang der Struktur nach oben bis zum Stamm traversieren.
Sie arbeiten nach dem Tunnelprinzip, wenn sie vom Stamm ausgehend nach unten zu einem Ziel traversieren.
Eingabenvorschauereignisse nutzen das Tunnelprinzip, wodurch jedes in der Struktur enthaltene Element das
Ereignis ltern oder eine Ereignismaÿnahme ergreifen kann. Die regulären Ereignisse (keine Vorschauereignisse)
steigen dann vom Ziel hoch zum Stamm auf.
Diese Unterteilung zwischen der Tunnel- und Aufstiegsphase ermöglicht eine einheitliche Implementierung von
Features (z. B. von Zugristasten) in einer gemischten Umgebung. In User32 würden Sie Zugristasten anhand
einer einzelnen globalen Tabelle implementieren, die alle zu unterstützenden Zugristasten (Strg+N mit Neu
verknüpft) enthält. In dem Verteiler Ihrer Anwendung würden Sie TranslateAccelerator aufrufen, der die Eingabemeldungen in User32 einliest und ermittelt, ob eine Übereinstimmung mit einer registrierten Zugristaste
besteht. In WPF würde dies nicht funktionieren, da das System vollständig zusammensetzbar ist, d. h., jedes Element kann jede beliebige Zugristaste verarbeiten und verwenden. Dieses eingabenrelevante Zweiphasenmodell
ermöglicht es Komponenten, den eigenen TranslateAccelerator zu implementieren.
Darüber hinaus führt UIElement das Konzept der CommandBindings ein. Mithilfe des WPF-Befehlssystem
können Entwickler Funktionalitäten in Form von Befehlsendpunkten denieren etwas, wodurch ICommand
implementiert wird. Befehlsbindungen ermöglichen, dass Elemente die Zuordnung einer Eingabeaktion (Strg+N)
47
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
5.2 Ereignisse
zu einem Befehl (Neu) denieren können. Sowohl Eingabeaktionen als auch Befehlsdenitionen sind erweiterbar
und können zum Zeitpunkt der Verwendung miteinander verknüpft werden. Dadurch können Endbenutzer z. B.
die in einer Anwendung zu verwendenden Tastenbindungen überaus einfach anpassen.
5.2 Ereignisse
In XAML kann jeder Objekt andere Objekte enthalten. Ein Button, kann ein Image, einen Text und vielleicht
eine TextBox zur Eingabe von Text enthalten. Wenn ich nun in die Textbox klicke, möchte ich den Text ändern
können. Wie wird dieses Ereignis Klick in die Textbox verarbeitet? Wird gleichzeitig der Button geklickt und
der Text geändert? Arbeiten Sie hierzu den Artikel auf Seite 29 im Anhang durch.
Aufgabe 5.1
Erstellen Sie eine WPF-Anwendung mit einem Button, in dem eine TextBox und ein weiterer
Button enthalten sind. Knüpfen Sie an die typischen Ereignisse eine Ausgabe. Debuggen Sie: Was passiert,
welches Ereignis wird wo behandelt? Erstellen Sie zwei Styles für Ihre Applikation. Klick auf den äuÿeren
Button vertauscht den aktuellen Style mit dem anderen Style.
5.3 WinForm und WPF
Abbildung 5.5: WPF mit WinForm und umgekehrt (Quelle:Interoperabilität zwischen Windows Forms und
WPF
Soll ein auf WinForm basierendes bestehendes Produkt erweitert werden oder gibt es ein Control noch nicht
für WPF, so können WPF und WinForm auch gut zusammen verwendet werden. Zur Integration von WPF
in WinForm benötigen Sie die Assemblies WindowsFormsIntegration.dll, PresentationFramework.dll, PresentationCore.dll, WindowsBase.dll. Umgekehrt basiert der Zugri auf WinForm aus WPF heraus auf den Klassen
WindowsFormsHost und ElementHost aus dem Namespace System.Windows.Forms.Integration.
48
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
6 Datenbankanbindung
Die typischen Probleme datenbankbasierter Applikationen, wie gleichzeitiger lesender oder schreibender Zugri
unter Berücksichtigung von Transaktionen oder die Unterstützung unterschiedlicher Datenbankmanagementsysteme bei sich veränderten Strukturen der beteiligten Tabellen, erfordern eine gute Unterstützung durch die
Entwicklungsumgebung und Klassenbibliotheken. Handarbeit und umfangreiche individuelle Implementationen
sind fehleranfällig und inezient. Die Abbildung der OO-Strukturen auf das relationale System erfolgt gerade
bei gröÿeren Projekten über ORM (Objekt-Relational-Mapping). Dadurch können Änderungen an den Tabellen
robust in die Applikation eingepegt werden. Dies übernimmt bei Microsoft das Entity Framework.
Abbildung 6.1: DB: Technologien
Abbildung 6.2: DB Technologien
Die Basis von Datenbankzugrien in .Net ist ADO.Net. ADO.Net bietet verschiedene Provider zum Zugri
auf Datenbanken wie SQL Server, Oracle oder über OLE.Db und ODBC auf unterschiedliche Datenquellen.
XML wird als Datenquelle zunehmend bedeutender, so dass auch hier ein performanter lesender, suchender und
schreibender Zugri nötig ist. Über ADO.Net gibt es passende Interfaces, man muss jedoch für SQL Server und
Oracle unterschiedliche Provider verwenden. Bisher musste man zur Unterstützung verschiedener Datenbankmanagementsysteme (DBMS) eine eigene Factory implementieren - dies wurde durch ein einheitliches Interface
gut unterstützt, aber war eben händisch zu implementieren. Mit dem nun herausgegebenen Entity Framework
soll dies entfallen ob dies auch für die grundunterschiedliche Handhabung von Triggern in Oracle und SQL
Server zutrit, konnte ich noch nicht evaluieren. Mit LINQ steht eine zusätzliche, vom jeweiligen Datenbankmanagementsystem unabhängige Abfragesprache zur Verfügung, so dass auch Queries nicht für jedes System
entsprechend formuliert werden müssen. Diese Unterschiede zwischen SQL Server und Oracle dies gilt natürlich auch für DB2, MySQL und andere DBMS können schon auf Select- oder Insert-Statements zutreen.
Abhängig davon wie DBMS-spezisch man bisher ausgebildet ist, fällt einem mitunter gar nicht auf, dass man
spezielle Features bei seiner Abfrage nutzt, die so in anderen DBMS nicht zur Verfügung stehen oder einer
anderen Syntax bedarfen.
Es gibt zwei Komponenten von ADO.NET, die Sie zum Zugreifen auf Daten und deren Bearbeitung verwenden
können: .NET Framework-Datenanbieter und das DataSet
6.0.1 .NET Framework-Datenanbieter
Die .NET Framework-Datenanbieter sind Komponenten, die explizit für die Datenbearbeitung und den schnellen,
vorwärts gerichteten, schreibgeschützten Zugri auf Daten entworfen wurden. Das Connection-Objekt sorgt für
eine Verbindung mit einer Datenquelle. Mit dem Command-Objekt können Sie auf Datenbankbefehle zugreifen,
um Daten zurückzugeben oder zu ändern, gespeicherte Prozeduren auszuführen und Parameterinformationen
49
6 Datenbankanbindung
Abbildung 6.3: Verbindungsorientierter DB-Zugri
zu senden oder abzurufen. Das DataReader-Objekt stellt einen Hochleistungsstream von Daten aus der Datenquelle zur Verfügung. Schlieÿlich fungiert der DataAdapter als Brücke zwischen dem DataSet-Objekt und der
Datenquelle. Der DataAdapter verwendet Command-Objekte, zum Ausführen von SQL-Befehlen in der Datenquelle, um damit das DataSet mit Daten zu laden und Änderungen an den Daten im DataSet in die Datenquelle
zu übernehmen. Der DataAdapter önet die Verbindung automatisch falls sie noch nicht oen ist und schlieÿt
die Verbindung automatisch, falls diese selbst geönet wurde. Die wichtigsten Methoden des DataAdapters sind
Fill() und Update().
Listing 6.1: Datenzugri mit DataReader
string connString = "Data Source=localhost;Integrated Security=SSPI;Initial Catalog=Northwind;";
//mit using wird Connection und DataReader immer geschlossen!
using (SqlConnection conn = new SqlConnection(connString))
{
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "SELECT CustomerId, CompanyName FROM Customers";
conn.Open();
}
using (SqlDataReader dr = cmd.ExecuteReader())
{
while (dr.Read())
Console.WriteLine("{0}\t{1}", dr.GetString(0), dr.GetString(1));
}
SqlConnection nwindConn = new SqlConnection("Data Source=localhost; Integrated Security=SSPI;" +
"Initial Catalog=northwind");
nwindConn.Open();
SqlCommand catCMD = new SqlCommand("SELECT CategoryID, CategoryName FROM Categories", nwindConn);
SqlDataReader myReader = myCommand.ExecuteReader();
if (myReader.HasRows)
while (myReader.Read())
Console.WriteLine("\t{0}\t{1}", myReader.GetInt32(0), myReader.GetString(1));
else
Console.WriteLine("No rows returned.");
myReader.Close();
//nur einen Wert auslesen:
SqlCommand ordersCMD = new SqlCommand("SELECT Count(*) FROM Orders", nwindConn);
Int32 count = (Int32)ordersCMD.ExecuteScalar();
nwindConn.Close();
//mit Parametern
using (SqlConnection connection =
new SqlConnection(connectionString))
{
SqlDataAdapter dataAdpater = new SqlDataAdapter(
50
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
6 Datenbankanbindung
"SELECT CategoryID, CategoryName FROM Categories",
connection);
dataAdpater.UpdateCommand = new SqlCommand(
"UPDATE Categories SET CategoryName = @CategoryName " +
"WHERE CategoryID = @CategoryID", connection);
dataAdpater.UpdateCommand.Parameters.Add(
"@CategoryName", SqlDbType.NVarChar, 15, "CategoryName");
SqlParameter parameter = dataAdpater.UpdateCommand.Parameters.Add(
"@CategoryID", SqlDbType.Int);
parameter.SourceColumn = "CategoryID";
parameter.SourceVersion = DataRowVersion.Original;
DataTable categoryTable = new DataTable();
dataAdpater.Fill(categoryTable);
DataRow categoryRow = categoryTable.Rows[0];
categoryRow["CategoryName"] = "New Beverages";
dataAdpater.Update(categoryTable);
}
Console.WriteLine("Rows after update.");
foreach (DataRow row in categoryTable.Rows)
{
{
Console.WriteLine("{0}: {1}", row[0], row[1]);
}
}
6.0.2 DataSet
Abbildung 6.4: CTS in Relation zu CLS
Das DataSet, ein aus einer Datenquelle abgerufener Datencache im Arbeitsspeicher, Das DataSet besteht aus
einer Auistung von DataTable-Objekten, die Sie mit DataRelation-Objekten aufeinander beziehen können.
In einer typischen Implementierung mit mehreren Ebenen werden die folgenden Schritte zum Erstellen und
Aktualisieren eines DataSet und zum anschlieÿenden Aktualisieren der ursprünglichen Daten ausgeführt:
1 Das
DataSet besteht aus einer Auistung von DataTable-Objekten, die Sie mit DataRelation-Objekten auf-
einander beziehen können.
1. Erstellen Sie mithilfe eines DataAdapter jede DataTable in einem DataSet, und füllen Sie sie mit Daten
aus einer Datenquelle.
1 DataSet-Klasse
51
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
6.1 Entity Framework
2. Ändern Sie die Daten in einzelnen DataTable-Objekten, indem Sie DataRow-Objekte hinzufügen, aktualisieren oder löschen.
3. Rufen Sie die GetChanges-Methode auf, um ein zweites DataSet zu erstellen, das nur die Änderungen an
den Daten darstellt.
4. Rufen Sie die Update-Methode von DataAdapter auf, und übergeben Sie das zweite DataSet als Argument.
5. Rufen Sie die Merge-Methode auf, um die Änderungen aus dem zweiten DataSet mit dem ersten zusammenzuführen.
6. Rufen Sie AcceptChanges für das DataSet auf. Sie können auch RejectChanges aufrufen, um die Änderungen zu verwerfen.
Listing 6.2: Update mit DataSet
//connect to db
SqlCommand mySelectCommand = New SqlCommand("select * from customers", myConnection);
SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter(mySelectCommand);
DataSet myDataSet = new DataSet();
mySqlDataAdapter.Fill(myDataSet,"Customers");
//anzeigen
foreach (DataRow myDataRow in myDataSet.Tables["Customers"].Rows)
{
Console.WriteLine(myDataRow["CustomerId"].ToString());
}
//löschen
myDataSet.Tables["Customers"].Rows[0].Delete();
//update
myDataSet.Tables["Customers"].Rows[0]["ContactName"]="Peach";
mySqlDataAdapter.Update(myDataSet);
6.0.3 DataSet oder DataReader
Das ADO.NET-DataSet wurde explizit für den Datenzugri unabhängig von Datenquellen entworfen. Aus diesem Grund kann es mit mehreren, unterschiedlichen Datenquellen, mit XML-Daten oder zum Verwalten von
lokalen Anwendungsdaten verwendet werden. Das DataSet enthält eine Auistung von einem oder mehreren
DataTable-Objekten, die aus Datenzeilen und -spalten sowie aus Primärschlüsseln, Fremdschlüsseln, Einschränkungen und Beziehungsinformationen über die Daten in den DataTable-Objekten besteht.
Wenn Sie sich zwischen einem DataReader oder einem DataSet für die Anwendung entscheiden möchten, müssen
Sie den für die Anwendung erforderlichen Funktionalitätstyp berücksichtigen. Verwenden Sie für folgende Zwecke
ein DataSet:
•
Lokales Zwischenspeichern von Daten in Ihrer Anwendung, um sie bearbeiten zu können. Wenn Sie nur
die Ergebnisse einer Abfrage anzeigen müssen, ist der DataReader die bessere Alternative.
•
Verschieben von Daten zwischen Ebenen oder von einem XML-Webdienst.
•
Dynamisches Interagieren mit Daten, wie Datenbindung an ein Windows Forms-Steuerelement, oder Kombinieren von und Erstellen einer Beziehung zwischen Daten aus mehreren Quellen.
•
Ausführen umfangreicher Datenverarbeitungsschritte ohne eine oene Verbindung zur Datenquelle. Dadurch wird die Verbindung zur Nutzung durch andere Clients freigegeben.
Wenn Sie die von DataSet bereitgestellten Funktionen nicht benötigen, können Sie die Leistung Ihrer Anwendung verbessern, indem Sie mithilfe des DataReader die Daten als schreibgeschützte Vorwärtsdaten zurückgeben. Obwohl der DataAdapter den DataReader zum Füllen des DataSet-Inhalts verwendet (siehe Auüllen
eines DataSets mit einem DataAdapter-Objekt), können Sie die Leistung erhöhen, wenn Sie den DataReader
verwenden. Dies ist möglich, weil Sie den Arbeitsspeicher, der für das DataSet erforderlich ist, nicht benötigen
und die Verarbeitung, die zum Erstellen und Füllen des DataSet-Inhalts nötig ist, überüssig ist.
6.1 Entity Framework
Entity Framework ist ein Satz von Technologien in ADO.NET, die die Entwicklung datenorientierter (relational)
Softwareanwendungen (objektorient) unterstützen.
52
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
6.1 Entity Framework
6.1.1 Mapping von Objekten zu Daten
2 Die
objektorientierte Programmierung stellt eine Herausforderung für die Interaktion mit Datenspeichersyste-
men dar. Obwohl die Organisation der Klassen häug den Aufbau relationaler Datenbanktabellen recht genau
widerspiegelt, passen diese Strukturen nicht perfekt zusammen. Oft entsprechen mehrere normalisierte Tabellen einer einzigen Klasse, und Beziehungen zwischen Klassen werden nicht auf dieselbe Weise dargestellt wie
Beziehungen zwischen Tabellen. Um beispielsweise den Kunden für einen Auftrag darzustellen, verwendet die
Order-Klasse eine Eigenschaft, die einen Verweis auf eine Instanz einer Customer-Klasse enthält. Eine Zeile in der Order-Tabelle in einer Datenbank enthält jedoch eine Fremdschlüsselspalte (oder mehrere Spalten
als Fremdschlüssel) mit einem Wert, der einem Primärschlüsselwert in der Customer-Tabelle entspricht. Eine
Customer-Klasse kann über eine Eigenschaft mit dem Namen Orders verfügen, die eine Auistung von Instanzen
der Order-Klasse enthält. Die Customer-Tabelle in einer Datenbank verfügt jedoch nicht über eine vergleichbare
Spalte.
Vorhandene Lösungen haben versucht, diese Lücke, oft als Impedance Mismatch bezeichnet, zu füllen, indem
nur objektorientierte Klassen und Eigenschaften relationalen Tabellen und Spalten zugeordnet wurden. Statt
diesen herkömmlichen Ansatz zu verfolgen, ordnet Entity Framework relationale Tabellen, Spalten und Fremdschlüsseleinschränkungen in logischen Modellen den Entitäten und Beziehungen in konzeptionellen Modellen zu.
Dadurch wird sowohl die Denition von Objekten als auch die Optimierung des logischen Modells viel exibler.
Die Entitätsdatenmodell-Tools generieren erweiterbare Datenklassen auf der Grundlage des konzeptionellen
Modells. Diese Klassen sind partielle Klassen, die mit zusätzlichen, vom Entwickler hinzuzufügenden Membern
erweitert werden können. Die für ein bestimmtes konzeptionelles Modell erzeugten Klassen werden von Basisklassen abgeleitet, die Object Services zur Umsetzung von Entitäten in Objekte und zum Nachverfolgen und
Speichern von Änderungen zur Verfügung stellen. Entwickler können diese Klassen verwenden, um die Entitäten
und Beziehungen als Objekte zu behandeln, die durch Navigationseigenschaften verknüpft sind.
6.1.2 Zugreifen auf und Ändern von Entitätsdaten
Entity Framework ist mehr als nur eine weitere objektrelationale Mappinglösung. Es dient im Wesentlichen dazu,
Anwendungen den Zugri auf und die Änderung von Daten zu ermöglichen, die als Entitäten und Beziehungen im konzeptionellen Modell dargestellt werden. Object Services verwendet das EDM, um Objektabfragen
von Entitätstypen, die im konzeptionellen Modell dargestellt werden, in datenquellenspezische Abfragen zu
übersetzen. Abfrageergebnisse werden in Objekte umgesetzt, die von Object Services verwaltet werden. Entity
Framework bietet die folgenden Möglichkeiten, ein EDM abzufragen und Objekte zurückzugeben:
•
LINQ-to-Entities bietet Language Integrated Query (LINQ)-Unterstützung zum Abfragen von Entitätstypen, die in einem konzeptionellen Modell deniert sind.
•
Entity SQL ein speicherunabhängiger SQL-Dialekt, der direkt mit Entitäten im konzeptionellen Modell arbeitet und EDM-Features, wie beispielsweise Vererbung und Beziehungen, unterstützt. Entity SQL
wird sowohl für Objektabfragen als auch für Abfragen verwendet, die mithilfe des EntityClient-Anbieters
ausgeführt werden.
•
Abfrage-Generator-Methoden ermöglichen das Erstellen von Entity SQL-Abfragen unter Verwendung
von Abfragemethoden im Stil von LINQ.
Entity Framework enthält den EntityClient-Datenanbieter. Dieser Anbieter verwaltet Verbindungen, übersetzt
Entitätsabfragen in datenquellenspezische Abfragen und gibt einen Datenleser zurück, mit dem Object Services
Entitätsdaten in Objekte umsetzt. Wenn die Umsetzung in Objekte nicht erforderlich ist, kann der EntityClientAnbieter auch wie ein ADO.NET-Standarddatenanbieter verwendet werden, indem er Anwendungen das Ausführen von Entity SQL-Abfragen und die Verarbeitung des zurückgegebenen schreibgeschützten Datenlesers
ermöglicht.
Entity Framework generiert eine von ObjectContext abgeleitete Klasse, die den im konzeptionellen Modell
denierten Entitätencontainer darstellt. Dieser Objektkontext stellt die Funktionen zum Nachverfolgen von
Änderungen und zum Verwalten von Identitäten, Parallelität und Beziehungen bereit. Diese Klasse macht auch
eine SaveChanges-Methode verfügbar, die Einfügungen, Aktualisierungen und Löschungen in die Datenquelle
schreibt. Diese Änderungen werden wie bei Abfragen entweder durch automatisch vom System generierte Befehle
oder durch vom Entwickler angegebene gespeicherte Prozeduren vorgenommen.
2 Einführung
53
in Entity Framework
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
6.2 LINQ
Abbildung 6.5: Entity Framework
6.1.3 Datenmodellierung im Entity Framework
3
Das Entitätsdatenmodell (EDM) ist ein Entitätsbeziehungsmodell. Mit dem EDM werden Daten in einem
neutralen Format deniert, das nicht durch die Struktur von Programmiersprachen oder relationalen Datenbanken eingeschränkt ist. EDM-Schemas werden verwendet, um Details zu Entitäten und Beziehungen anzugeben
und diese als Datenstrukturen zu implementieren.
Eine Entität ist etwas in der Anwendungsdomäne, das durch Daten dargestellt werden muss. Beispiele für Entitäten und Beziehungen sind in einer typischen Geschäftsbereichsanwendung zu nden. Entitäten in der Domäne
einer Geschäftsbereichsanwendung können Kunden, Aufträge, Auftragspositionen, Lieferanten, Produkte, Verkaufsmitarbeiter, Spediteure, Rechnungen usw. umfassen. Ein EDM-EntityType ist die Spezikation für einen
Datentyp, der die Entität in der Anwendungsdomäne darstellt.
Eine Beziehung ist die logische Verbindung zwischen Entitäten, wie beispielsweise zwischen einem Warenauftrag
und dem Kunden, der den Auftrag aufgibt. Da ein Kunde über viele zugeordnete Aufträge verfügen kann, ist die
Beziehung zwischen dem Kunden und seinen Aufträgen eine 1:n-Beziehung. Zwischen Produkten und Lieferanten
kann eine m:n-Beziehung bestehen.
Das Denieren von Entitäten und Beziehungen kann ein sehr komplexer Vorgang sein. Etwas so Grundlegendes wie ein Warenauftrag in einer Geschäftsbereichsanwendung erfordert eine nicht zu unterschätzende Menge
an Details. Zum Beispiel kann ein Warenauftrag unterschiedliche Formen haben. So könnte der Auftrag im
Laden, am Telefon, im Internet oder per Katalog aufgegeben werden. Im EDM sind die Details der einzelnen
Auftragstypen konzeptionell in XML-Syntax angegeben. Die Eigenschaften jedes Auftragstyps und notwendige
Einschränkungen werden an Anwendungen weitergegeben, die Daten mit dem konzeptionellen Schema verwenden.
Die Entitäten und ihre Beziehungen werden vom EDM mithilfe zweier grundlegender Typen modelliert.
•
EntityType: Die abstrakte Spezikation für die Details einer Datenstruktur in der Anwendungsdomäne.
•
AssociationType: Die logische Verbindung zwischen Typen.
EDM-Generator (EdmGen.exe) ist ein Befehlszeilendienstprogramm, dass eine Verbindung zu einer Datenquelle
herstellt und ein EDM auf der Grundlage eines 1:1-Mappings zwischen Entitäten und Tabellen erstellt. Auÿerdem verwendet es eine Konzeptmodelldatei (.csdl), um eine Objektebenendatei mit Klassen zu erzeugen, die
Entitätstypen und den ObjectContext darstellen.
6.2 LINQ
Mit LINQ (Language Integrated Query) gibt es eine Abfragesprache, die sowohl für Listen als auch als Datenbankabfragesprache oder zur Abfrage von XML-Dateien verwendet werden kann. Dabei ist die Sprache direkt
in C# integriert, es müssen also nicht irgendwelche Strings zur Abfrage zusammengesetzt werden.
3 Datenmodellierung
54
im Entity Framework
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
6.2 LINQ
Abbildung 6.6: LINQ
4 Im
Gegensatz zu z.B. SQL wird die Abfrage nicht als String übergeben, sondern ist nativ in den Quellcode
integriert. Das bietet z.B. den Vorteil, dass der .NET-Sprachcompiler die Abfrage direkt auf Syntaxfehler prüfen
kann, um spätere Laufzeitfehler zu vermeiden. Die Syntax von LINQ erinnert sehr an die SQL-Abfragesprache
mit Befehlen wie z.B. select, from, where und orderby. LINQ ndet im Wesentlichen die folgenden vier Anwendungsgebiete:
•
LINQ-to-Dataset: bietet Abfragemöglichkeiten für Objekte, die in einem DataSet-Objekt gespeichert sind.
•
LINQ-to-SQL (DLINQ): übersetzt die native LINQ-Abfrage in SQL und sendet diese an eine relationale
Datenbank. Von der Datenbank gelieferte Ergebnisse werden dann entsprechend wieder in ein Objektmodell übersetzt.
•
LINQ-to-XML (XLINQ): bietet Zugri auf XML-Dokumente (DOM-Manipulation)
•
LINQ-to-Objects: bietet Abfragemöglichkeiten für .NET-Objektmengen, die in Listen gespeichert sind,
z.B. in List, Array oder Dictionary (müssen das Interface IEnumerable bzw. IEnumerable<T> implementieren.
Die wesentlichen LINQ-Klassen werden in den Namensräumen System.Linq.*, System.Data.Linq sowie System.Xml.Linq zur Verfügung gestellt.
Darüber hinaus gibt es zahlreiche LINQ-Projekte anderer Hersteller und Anbieter, um nur einige der zahlreichen
Beispiele zu nennen: LINQ-to-MySQL, LINQ-to-LDAP, LINQ-to-Flickr, LINQ-to-Amazon, . . .
Listing 6.3: LINQ-Beispiele
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var lowNums =
from n in numbers
where n < 5
select n;
List<Product> products = GetProductList();
var soldOutProducts =
from p in products
where p.UnitsInStock == 0
select p;
string[] words = { "aPPLE", "BlUeBeRrY", "cHeRry" };
var upperLowerWords =
from w in words
select new { Upper = w.ToUpper(), Lower = w.ToLower() };
4 .Net
55
Glossar
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
6.3 Datenbindung
6.3 Datenbindung
Der Begri Datenbindung beschäftigt sich mit der Problematik Änderungen an den Daten einer Datenquelle in
Steuerelementen eines Formulars anzuzeigen und vorzunehmen.
Die Datenbindung ist grundsätzlich ein automatisches Verfahren, um für beliebige Steuerelemente im Formular
Eigenschaften festzulegen, auf die zur Laufzeit zugegrien werden kann.
5 Die
Änderungsbenachrichtigung ist einer der wichtigsten Bestandteile der Windows Forms-Datenbindung. Um
sicherzustellen, dass die Datenquelle und die gebundenen Steuerelemente immer über aktuelle Daten verfügen,
müssen Sie die Änderungsbenachrichtigung für die Datenbindung hinzufügen. Der Hauptzweck liegt darin, dass
gebundene Steuerelemente über Änderungen an der zugehörigen Datenquelle benachrichtigt werden und die
Datenquelle über Änderungen benachrichtigt wird, die an den gebundenen Eigenschaften eines Steuerelements
vorgenommen wurden.
6.3.1 Datenbindung in WinForm
6 Herkömmlicherweise
wurde die Datenbindung in Anwendungen verwendet, um in Datenbanken gespeicherte
Daten nutzen zu können. Mit der Datenbindung von Windows Forms können Sie auf Daten zugreifen, die in
Datenbanken sowie in anderen Strukturen, z. B. Arrays oder Auistungen, enthalten sind.
Die folgende Liste enthält die Strukturen, an die Sie in Windows Forms eine Bindung vornehmen können.
•
BindingSource ist die häugste Windows Forms-Datenquelle. Sie fungiert als Proxy zwischen einer Datenquelle und Windows Forms-Steuerelementen. Das allgemeine Verwendungsmuster von BindingSource
sieht vor, Steuerelemente an BindingSource und BindingSource an die Datenquelle (z. B. eine ADO.NETDatenquelle oder ein Geschäftsobjekt) zu binden.
•
Windows Forms unterstützt in Objektinstanzen mithilfe des Binding-Typs die Datenbindung von Steuerelementeigenschaften an öentliche Eigenschaften. Auÿerdem unterstützt Windows Forms die Bindung
listenbasierter Steuerelemente, z. B. ListControl, an eine Objektinstanz, wenn BindingSource verwendet
wird.
•
Damit eine Liste als Datenquelle verwendet werden kann, muss sie die IList-Schnittstelle implementieren.
•
ADO.NET:
DataColumn ist der wichtigste Baustein von DataTable, da eine Tabelle sich aus einer Reihe von
Spalten zusammensetzt. Jedes DataColumn-Objekt verfügt über eine DataType-Eigenschaft, die die
Art der Daten bestimmt, die in der Spalte enthalten sind (in einer Tabelle zur Beschreibung von
Autos z. B. die Marke der Autos). Ein Steuerelement (bei einem TextBox-Steuerelement z. B. die
Text-Eigenschaft) kann mithilfe der einfachen Bindung an eine Spalte in einer Datentabelle gebunden
werden.
DataTable ist die Darstellung einer Tabelle mit Zeilen und Spalten, wie sie in ADO.NET verwendet wird. Eine Datentabelle umfasst zwei Auistungen: DataColumn für die Datenspalten in einer
bestimmten Tabelle (dadurch wird letztlich bestimmt, welche Arten von Daten in diese Tabelle
eingegeben werden können) sowie DataRow für die Datenzeilen in einer bestimmten Tabelle. Ein
Steuerelement kann mithilfe der komplexen Bindung an die Daten in einer Datentabelle gebunden
werden (beispielsweise bei Bindung des DataGridView-Steuerelements an eine Datentabelle). Bei der
Bindung an DataTable erstellen Sie tatsächlich eine Bindung an die Standardansicht dieser Tabelle.
DataView ist eine angepasste Ansicht einer einzelnen Datentabelle, die geltert oder sortiert werden
kann. Eine Datenansicht ist eine Momentaufnahme der Daten, die von komplex gebundenen Steuerelementen verwendet werden. Für die Daten in einer Datenansicht können Sie einfache oder komplexe
Bindungen vornehmen. Dabei sollten Sie jedoch beachten, dass Sie nicht Bindungen an eine saubere,
aktualisierende Datenquelle, sondern an ein festes Abbild der Daten vornehmen.
DataSet ist eine Auistung von Tabellen, Beziehungen und Einschränkungen der Daten in einer Datenbank. Sie können Daten innerhalb eines DataSets mithilfe der einfachen oder komplexen Bindung
binden. Stellen Sie jedoch sicher, dass Sie an den standardmäÿigen DataViewManager für DataSet
binden (siehe dazu den nächsten Gliederungspunkt).
5 Änderungsbenachrichtigung in der Windows Forms-Datenbindung
6 Von Windows Forms unterstützte Datenquellen
56
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
6.3 Datenbindung
DataViewManager ist eine angepasste Ansicht des gesamten DataSet. Sie entspricht grundsätzlich
DataView, enthält aber zusätzlich Beziehungen. Mit einer DataViewSettings-Auistung können Sie
standardmäÿige Filter- und Sortieroptionen für beliebige Ansichten festlegen, die DataViewManager
für eine bestimmte Tabelle bereitstellt.
Die Bindung eines Datagrids DataGrid1 an eine Tabelle Customers eines Datasets dsCustomers ist nun nur
noch ein Einzeiler:
Listing 6.4: Datenbindung an ein DataGrid
DataGrid1.SetDataBinding(dsCustomers, "Customers");
6.3.2 Datenbindung in WPF
7 Elemente können an Daten aus einer Vielzahl von Datenquellen in Form von common language runtime (CLR)Objekten und XML gebunden werden. ContentControl wie Button und ItemsControl wie ListBox und ListView
verfügen über integrierte Funktionen, die eine exible Formatierung von einzelnen Datenelementen oder Auflistungen von Datenelementen ermöglichen. Sortier-, Filter- und Gruppenansichten können übergreifend für die
Daten generiert werden.
Unabhängig davon, was für ein Element Sie binden und welcher Art die Datenquelle ist, geschieht die Bindung
immer nach dem in Abbildung 6.7 gezeigten Modell:
Abbildung 6.7: Datenbindung in WPF
Wie in der Abbildung dargestellt, ist Datenbindung die Brücke zwischen dem Bindungsziel und der Bindungsquelle. Eine Bindung besteht in der Regel aus diesen vier Komponenten: einem Bindungszielobjekt, einer Zieleigenschaft, einer Bindungsquelle sowie einem Pfad zum Wert in der Bindungsquelle, die verwendet werden soll.
Angenommen, Sie möchten den Inhalt von TextBox an die Name-Eigenschaft eines Employee-Objekts binden,
dann ist Ihr Zielobjekt TextBox, die Zieleigenschaft die Text-Eigenschaft, der zu verwendende Wert Name und
das Quellobjekt das Employee-Objekt.
Über die Mode-Eigenschaft des Binding-Objekts können Sie festlegen, ob und wie Änderungen festgeschrieben
werden.
Abbildung 6.8: Datenbindungsuss
OneWay-Bindung bewirkt, dass bei Änderungen an der Quelleigenschaft die Zieleigenschaft automatisch aktualisiert wird, ohne dass Änderungen an der Zieleigenschaft an die Quelleigenschaft zurückübertragen werden.
TwoWay-Bindung bewirkt, dass bei Änderungen an der Quelleigenschaft bzw. der Zieleigenschaft die jeweils
andere automatisch aktualisiert wird. OneWayToSource stellt die Umkehrung der OneWay-Bindung dar: die
Quelleigenschaft wird aktualisiert, wenn sich die Zieleigenschaft ändert.
Folgendes Beispiel zeigt, wie ein Windows Presentation Foundation (WPF)ListBox-Steuerelement an ein
ADO.NETDataSet gebunden wird.
7 Übersicht
57
über Datenbindung
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
6.4 Aufgaben
Listing 6.5: Datenbindung in WPF
DataSet myDataSet;
private void OnInit(object sender, EventArgs e)
{
//Verbindung herstellen ...
}
OleDbDataAdapter adapter = new OleDbDataAdapter("SELECT * FROM BookTable;", conn);
myDataSet = new DataSet();
adapter.Fill(myDataSet, "BookTable");
myListBox.DataContext = myDataSet;
...
XAML:
<ListBox Name="myListBox" Height="200"
ItemsSource="{Binding Path=BookTable}"
ItemTemplate ="{StaticResource BookItemTemplate}"/>
<StackPanel.Resources>
<c:IntColorConverter x:Key="MyConverter"/>
<DataTemplate x:Key="BookItemTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Title}" Grid.Column="0"
FontWeight="Bold" />
<TextBlock Text="{Binding Path=ISBN}" Grid.Column="1" />
<TextBlock Grid.Column="2" Text="{Binding Path=NumPages}"
Background="{Binding Path=NumPages,
Converter={StaticResource MyConverter}}"/>
</Grid>
</DataTemplate>
</StackPanel.Resources>
6.3.3 Datenbindung in Asp.Net
Die serverseitige Datenbindung in WebForms erfolgt analog zu WinForm:
Listing 6.6: Serverseitige Datenbindung in Asp.Net
Collection<StockInfo> stocks = GetLatestQuotes(...);
DataGrid1.DataSource = stocks;
DataGrid1.DataBind();
Mit AJAX gibt es auch die Möglichkeit einer clientseitigen Datenbindung.
6.4 Aufgaben
Die Aufgaben stammen von .Net AT JKU.
Aufgabe 6.1
Zugrisart bei gleichzeitigem Zugri. In einer Anwendung möchten viele Benutzer gleichzeitig
die Daten einer Tabelle (z.B. Produkte) lesen. Verwenden Sie eher einen verbindungsorientierten oder einen
verbindungslosen Zugri ?
Aufgabe 6.2
Ändern von Daten. Werden die Änderungen der Daten bei einem verbindungslosen Zugri auch
in der Datenbank automatisch durchgeführt? Falls ja, wodurch, falls nein, wie können sie trotzdem durchgeführt
Aufgabe 6.3
Schreiben Sie ein Kommandozeilenprogramm ExportXML, welches alle Daten und Tabellen einer
Datenbank ausliest und in einer XML-Datei abspeichert. Der erste Kommandozeilenparameter ist der Name der
Verbindung und der zweite Paramter gibt den Namen der zu erzeugenden XML-Datei an. Die exportierte XMLDatei soll auch das Schema der Datenbank enthalten.
58
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
7 Webentwicklung
Abbildung 7.1: Asp.Net Überblick
Aus den ursprünglichen skriptbasierten ASP-Seiten, die ähnlich wie frühere PHP-Seiten aussahen, häug sogar
mit VB-Skript nur browserspezisch arbeiteten, ist mittlerweile eine gute Entwicklungsmöglichkeit fürs Web
entwachsen. Selbst die erste Version von Asp.Net war schon mächtig. Man gestaltet seine Webseiten wie bei
WinForm durch Drag&Drop vorhandener Steuerelemente, kann eigene Komponenten implementieren und über
CodeBehind programmiert man die zugehörigen PostBacks. Prinzipiell wird immer an die gleiche Seite zurückgeschickt. Das Konzept bei Asp.Net ist ähnlich zu WinForm: ereignisorientiert. Über Server.Transfer kann man
zu einer anderen Seite navigieren. Die Steuerelemente ermöglichen einen einfachen Anschluss an Datenstrukturen und Datenbanken und hinter der Oberäche arbeitet man mit C# wie gewohnt objektorientiert. In
der ersten Version von Asp.Net waren die Seiten noch nicht barrierefrei und ein einheitliches Look&Feel war
nur umständlich selbst zu programmieren. Durch die Einführung von sogenannten Masterpages und eine bessere HTML-Codeerzeugung sind diese beiden Probleme gut gelöst. Die aktuelle Version setzt auf JSON und
unterstützt nun auch die Entwicklung von AJAX-basierten Seiten gut. Asp.Net implementiert nicht das MVCPattern, Modell und Controller sind typischerweise miteinander vermischt. Für gröÿere Applikationen ist MVC
jedoch aktuell State of the Art, so dass mit .Net 4.0 nun auch eine Klassenbibliothek hierfür existiert, die auf
Asp.Net aufsetzt.
59
7.1 Asp.Net
7.1 Asp.Net
Das ASP.NET-Framework für Seiten und Steuerelemente ist ein Programmierframework, das auf einem Webserver ausgeführt wird, um ASP.NET-Webseiten dynamisch zu erstellen und zu rendern. ASP.NET-Webseiten
können von jedem Browser oder Clientgerät angefordert werden, und ASP.NET rendert Markup (wie HTML)
für den anfordernden Browser. Grundsätzlich kann dieselbe Seite für mehrere Browser verwenden werden, da
ASP.NET das Markup dem anfordernden Browser entsprechend rendert. ASP.NET unterstützt mobile Steuerelemente für webkompatible Geräte, wie Handys, Taschencomputer und persönliche digitale Assistenten (PDAs,
Personal Digital Assistants). ASP.NET-Webseiten sind vollkommen objektorientiert. Innerhalb von ASP.NETWebseiten können Sie für die Arbeit mit HTML-Elementen Eigenschaften, Methoden und Ereignisse verwenden.
Das ASP.NET-Seitengerüst entfernt die Implementierungsdetails der Trennung von Client und Server, die den
webbasierten Anwendungen eigen ist, indem ein vereinheitlichtes Modell für das Reagieren auf Clientereignisse
im Code verwendet wird, das auf dem Server ausgeführt wird. Das Gerüst verwaltet während des Lebenszyklus der Seitenverarbeitung auch automatisch den Status einer Seite und der Steuerelemente auf der Seite. Das
ASP.NET-Framework für Seiten und Steuerelemente bietet durch verschiedene Designs die Möglichkeit, das
Gesamterscheinungsbild und Verhalten einer Website zu steuern. Sie können Designs und Skins denieren und
dann auf Seitenebene oder Steuerelementebene anwenden.
7.1.1 Masterseiten
Neben Designs können Sie auch Masterseiten denieren, um ein konsistentes Layout der Anwendungsseiten zu
erstellen. In einer Masterseite werden das Layout und das Standardverhalten aller Seiten (oder einer Gruppe
von Seiten) der Anwendung festgelegt. Sie können anschlieÿend einzelne Inhaltsseiten erstellen, die den spezischen Inhalt für die darzustellende Seite enthalten. Beim Anfordern einer Inhaltsseite durch den Benutzer
werden Inhaltsseite und Masterseite zusammengeführt. Das Ergebnis ist eine Kombination aus dem Inhalt der
Inhaltsseite und dem Layout der Masterseite. Mit ASP.NET-Masterseiten können Sie ein Seitenlayout erstellen
(eine Masterseite), das Sie mit ausgewählten oder allen Seiten (Inhaltsseiten) in der Website verwenden können. Masterseiten können ein nützliches Hilfsmittel beim Erstellen eines einheitlichen Erscheinungsbilds einer
Site sein. Die Themen in diesem Abschnitt führen in Masterseiten ein und beschreiben, wie diese erstellt und
verwaltet werden.
7.1.2 Lebenszyklus einer Seite
Im Allgemeinen durchläuft eine Asp.Net-Seite die folgenden Phasen:
•
Seitenanforderung: Der Lebenszyklus einer Seite beginnt, nachdem die Seite angefordert wurde. Wenn ein
Benutzer die Seite anfordert, prüft ASP.NET, ob die Seite analysiert und kompiliert werden muss (Beginn
des Lebenszyklus einer Seite) oder ob eine zwischengespeicherte Version der Seite zurückgesendet werden
kann, ohne dass die Seite verarbeitet werden muss.
•
Starten: In der Startphase werden Seiteneigenschaften wie Request und Response festgelegt. In dieser
Phase wird auch bestimmt, ob es sich bei der Anforderung um ein Postback oder um eine neue Anforderung
handelt, und die IsPostBack-Eigenschaft wird entsprechend festgelegt. Auÿerdem wird in der Startphase
die UICulture-Eigenschaft festgelegt.
•
Seiteninitialisierung: Während der Seiteninitialisierung sind die Steuerelemente auf der Seite verfügbar,
und die UniqueID-Eigenschaft jedes Steuerelements wird festgelegt. Ebenso werden gegebenenfalls Designs
für die Seite übernommen. Wenn es sich bei der aktuellen Anforderung um ein Postback handelt, sind die
Postbackdaten noch nicht geladen, und die Eigenschaftenwerte des Steuerelements wurden noch nicht mit
den Werten aus dem Ansichtszustand wiederhergestellt.
•
Laden:Wenn es sich bei der aktuellen Anforderung um ein Postback handelt, werden die Steuerelementeigenschaften während der Ladephase mithilfe von Informationen aus dem Ansichtszustand und dem
Steuerelementzustand wiederhergestellt.
•
Validierung: Während der Validierung wird die Validate-Methode aller Validierungssteuerelemente aufgerufen, die die IsValid-Eigenschaft einzelner Validierungssteuerelemente sowie der Seite festlegt.
•
Behandlung von Postbackereignissen: Wenn es sich bei der Anforderung um ein Postback handelt, werden
sämtliche Ereignishandler aufgerufen.
60
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
7.1 Asp.Net
•
Rendern: Vor der Darstellung werden der Ansichtszustand für die Seite sowie alle Steuerelemente gespeichert. Während der Wiedergabephase wird von der Seite die Render-Methode für jedes Steuerelement aufgerufen. Dadurch wird ein Textschreiber bereitgestellt, der eine Ausgabe in OutputStream der
Response-Eigenschaft der Seite schreibt.
•
Entladen: Nachdem die Seite vollständig dargestellt und an den Client gesendet wurde und daher verworfen werden kann, wird der Entladevorgang aufgerufen. An dieser Stelle werden Seiteneigenschaften wie
Response und Request entladen, und es werden sämtliche Bereinigungsoperationen ausgeführt.
7.1.3 Ereignismodell für ASP.NET-Webserversteuerelemente
Ein wichtiges Feature von ASP.NET ist die Möglichkeit, Webseiten mit einem ereignisbasierten Modell zu
programmieren, wie es auch für Clientanwendungen verwendet wird. Ein einfaches Beispiel: Sie fügen einer
ASP.NET-Webseite eine Schaltäche hinzu und schreiben anschlieÿend einen Ereignishandler für das Klickereignis der Schaltäche. Dieses Vorgehen kennen wir zwar schon von Webseiten her, die ausschlieÿlich mit Clientskript arbeiten (wobei das onclick-Ereignis mit dynamischem HTML behandelt wird). ASP.NET überträgt dieses
Modell jedoch auf die serverbasierte Verarbeitung. Durch ASP.NET-Serversteuerelemente ausgelöste Ereignisse
unterscheiden sich geringfügig von Ereignissen in herkömmlichen HTML-Seiten oder clientbasierten Webanwendungen. Der Unterschied entsteht hauptsächlich durch die Trennung des Ereignisses selbst von der Stelle, wo das
Ereignis behandelt wird. In clientbasierten Anwendungen werden Ereignisse auf dem Client ausgelöst und behandelt. In ASP.NET-Webseiten haben Ereignisse, die Serversteuerelementen zugeordnet sind, ihren Ursprung
auf dem Client (Browser). Sie werden jedoch von der ASP.NET-Seite auf dem Webserver behandelt. Für auf
dem Client ausgelöste Ereignisse benötigt das Ereignismodell für ASP.NET-Websteuerelemente auf dem Client
erfasste Ereignisinformationen sowie eine Ereignismeldung, die über HTTP Post an den Server übertragen wird.
Die Seite muss die Meldung interpretieren, um festzustellen, welches Ereignis aufgetreten ist, und muss dann
die entsprechende Methode im Code auf dem Server aufrufen, um das Ereignis zu behandeln. ASP.NET behandelt alle Aufgaben, die beim Erfassen, Senden und Interpretieren des Ereignisses durchgeführt werden. Beim
Erstellen von Ereignishandlern in einer ASP.NET-Webseite müssen Sie sich normalerweise nicht darum kümmern, wie die Ereignisinformationen erfasst und dem Code zur Verfügung gestellt werden. Stattdessen können
Sie Ereignishandler wie in einem herkömmlichen Clientformular erstellen. Allerdings gibt es einige Aspekte bei
der Ereignisbehandlung in ASP.NET-Webseiten, die Sie beachten sollten. Ereignissatz für Serversteuerelemente
und Seiten
Serversteuerelementereignisse und PostBack:
Da die meisten ASP.NET-Serversteuerelementereignisse zur Verarbeitung einen Roundtrip zum Server erfordern,
können sie die Leistung der Seite beeinträchtigen.
Daher bieten Serversteuerelemente einen eingeschränkten Satz an Ereignissen, die normalerweise auf Klickereignisse beschränkt sind. Einige Serversteuerelemente unterstützen Änderungsereignisse. Zum Beispiel löst das
CheckBox-Webserversteuerelement im Servercode ein CheckedChanged-Ereignis aus, wenn der Benutzer auf
das Kontrollkästchen klickt. Einige Serversteuerelemente unterstützen abstraktere Ereignisse. Das CalendarWebserversteuerelement z. B. löst ein SelectionChanged-Ereignis aus, das eine abstraktere Version des Klickereignisses ist. Ereignisse, die häug auftreten (und ohne das Wissen des Benutzers ausgelöst werden können), wie
das onmouseover-Ereignis, werden von Serversteuerelementen nicht unterstützt. ASP.NET-Serversteuerelemente
können für solche Ereignisse aber clientseitige Handler aufrufen. Dies wird weiter unten unter Ereignismodell
für ASP.NET-Webserversteuerelemente erklärt. Steuerelemente und die Seite selbst lösen bei jedem Verarbeitungsschritt ebenfalls Lebenszyklusereignisse aus, z. B. Init, Load und PreRender. Sie können diese Lebenszyklusereignisse in der Anwendung nutzen. Sie können zum Beispiel im Load-Ereignis einer Seite Standardwerte für Steuerelemente festlegen. Ereignisargumente Serverbasierte Ereignisse von ASP.NET-Seiten und
-Steuerelementen folgen einem .NET Framework-Standardmuster für Ereignishandlermethoden. Alle Ereignisse
übergeben zwei Argumente: ein Objekt, das das Ereignis auslösende Objekt darstellt, und ein Ereignisobjekt, das
ereignisspezische Informationen enthält. Das zweite Argument ist normalerweise vom Typ EventArgs, aber bei
manchen Steuerelementen ist es von einem Typ, der für dieses Steuerelement spezisch ist. Für ein ImageButtonWebserversteuerelement ist z. B. das zweite Argument vom Typ ImageClickEventArgs. Es enthält Informationen über die Koordinaten des Punkts, auf den der Benutzer geklickt hat. Ereignisse für Seiten (beispielsweise
das Load-Ereignis der Seite) akzeptieren die beiden Standardargumente, in denen dann allerdings keine Werte
übergeben werden. Postbackereignisse und Nicht-Postbackereignisse in Serversteuerelementen In Serversteuerelementen führen bestimmte Ereignisse (normalerweise Klickereignisse) dazu, dass die Seite unmittelbar an
den Server zurückgesendet wird. Änderungsereignisse in HTML-Serversteuerelementen und Webserversteuerelementen, wie das TextBox-Steuerelement, verursachen kein sofortiges Postback. Stattdessen werden sie bei
61
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
7.1 Asp.Net
dem nächsten Postback ausgelöst. Falls dies vom Browser unterstützt wird, können Validierungssteuerelemente Benutzereingaben mit Clientskript prüfen ohne einen Roundtrip zum Server. Ausführliche Informationen
nden Sie unter Überprüfen der Benutzereingabe in ASP.NET-Webseiten. Nach dem Zurücksenden der Seite
werden die Initialisierungsereignisse der Seite (Page_Init und Page_Load) ausgelöst, und anschlieÿend werden
Steuerelementereignisse verarbeitet. Sie sollten keine Anwendungslogik erstellen, die von in einer bestimmten
Reihenfolge ausgelösten Änderungsereignissen abhängt, es sei denn, Sie verfügen über fundierte Kenntnisse
der Seitenereignisverarbeitung. Ausführliche Informationen nden Sie unter Übersicht über den Lebenszyklus
von ASP.NET-Seiten. Falls es für Ihre Anwendung sinnvoll ist, können Sie angeben, dass Änderungsereignisse
einen Postback der Seite auslösen. Webserversteuerelemente, die ein Änderungsereignis unterstützen, enthalten
eine AutoPostBack-Eigenschaft. Wenn diese Eigenschaft auf true festgelegt wurde, löst das Änderungsereignis des Steuerelements sofort ein Postback der Seite aus, ohne dass auf ein Klickereignis gewartet wird. Das
CheckedChanged-Ereignis eines CheckBox-Steuerelements löst z. B. standardmäÿig keine Übermittlung der Seite
aus. Wenn Sie aber die AutoPostBack-Eigenschaft des Steuerelements auf true festlegen, wird die Seite zur Verarbeitung an den Server gesendet, sobald ein Benutzer auf das Kontrollkästchen klickt. Damit die AutoPostBackEigenschaft ordnungsgemäÿ funktioniert, muss der Browser des Benutzers Skripts zulassen. Meistens ist das die
Standardeinstellung. Allerdings deaktivieren manche Benutzer Skripts aus Sicherheitsgründen. Ausführliche Informationen nden Sie unter Clientskript in ASP.NET-Webseiten.
Weitergeleitete Ereignisse: Zusätzlich zu
Seiten- und Steuerelementereignissen bietet ASP.NET Möglichkeiten zum Arbeiten mit Lebenszyklusereignissen, die ausgelöst werden, wenn die Anwendung gestartet oder beendet wird (Anwendungsereignisse) oder wenn
die Sitzung eines einzelnen Benutzers gestartet oder beendet wird (Sitzungsereignisse):
•
Anwendungsereignisse werden für alle Anforderungen einer Anwendung ausgelöst. Zum Beispiel wird
das BeginRequest-Ereignis des HttpApplication-Objekts (Application/_BeginRequest) ausgelöst, wenn
eine ASP.NET-Webseite oder ein XML-Webdienst in der Anwendung angefordert wird. Mit diesem Ereignis können Sie Ressourcen initialisieren, die für die Anforderungen an die Anwendung verwendet
werden. Ein entsprechendes Ereignis, das EndRequest-Ereignis des HttpApplication-Objekts (Application/_EndRequest), gibt Ihnen die Möglichkeit, für die Anforderung verwendete Ressourcen zu schlieÿen
oder freizugeben.
•
Sitzungsereignisse ähneln Anwendungsereignissen (es gibt ein Start-Ereignis und ein End-Ereignis). Sie
werden jedoch für jede einzelne Sitzung innerhalb der Anwendung ausgelöst. Eine Sitzung beginnt, wenn
ein Benutzer eine Seite zum ersten Mal von der Anwendung anfordert, und endet, wenn die Anwendung
die Sitzung explizit schlieÿt oder wenn die Sitzung das Zeitlimit überschreitet.
Das Session_End-Ereignis wird nicht unter allen Umständen ausgelöst.
7.1.4 Zustandsverwaltung
Bei jeder Bereitstellung einer Webseite auf dem Server wird eine neue Instanz der Webseitenklasse erstellt.
Bei der herkömmlichen Webprogrammierung würde dies normalerweise bedeuten, dass alle zur Seite gehörenden Informationen und die Steuerelemente auf der Seite bei jeder Client-Server-Schleife (Roundtrip) verloren
gingen. Wenn z. B. ein Benutzer Informationen in ein Textfeld eingibt, würden diese Informationen während
der Schleife vom Browser oder Clientgerät zum Server verloren gehen. Um diese inhärente Einschränkung der
herkömmlichen Webprogrammierung zu überwinden, enthält ASP.NET eine Reihe von Optionen, mit denen
Sie die Daten sowohl auf Seitenbasis als auch auf Anwendungsbasis bewahren können. Es handelt sich um folgende Features: Ansichtszustand, Steuerelementzustand, Ausgeblendete Felder, Cookies, Abfragezeichenfolgen,
Anwendungszustand, Sitzungszustand, Proleigenschaften. Ansichtszustand, Steuerelementzustand, ausgeblendete Felder, Cookies und Abfragezeichenfolgen speichern die Daten jeweils auf dem Client. Anwendungszustand,
Sitzungszustand und Proleigenschaften hingegen speichern die Daten im Speicher des Servers. Jede Option hat
je nach Einsatzszenario unterschiedliche Vor- und Nachteile.
In den folgenden Abschnitten werden Optionen für die Zustandsverwaltung beschrieben, für die das Speichern
der Informationen entweder in der Seite oder auf dem Client erforderlich ist. Bei diesen Optionen bleiben die
Informationen auf dem Server zwischen den Schleifen nicht erhalten.
Ansichtszustand:Die ViewState-Eigenschaft enthält ein Dictionary-Objekt, um Werte zwischen mehreren Anforderungen derselben Seite beizubehalten. Dies ist die Standardmethode, die die Seite zum Beibehalten von
Seitenwerten und Werten für Steuerelementeigenschaften zwischen den Schleifen verwendet. Bei der Verarbeitung der Seite wird der aktuelle Status der Seite und der Steuerelemente in eine Zeichenfolge gehasht und in
der Seite als ausgeblendetes Feld gespeichert. Wenn die in der ViewState-Eigenschaft gespeicherte Datenmenge
62
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
7.1 Asp.Net
den in der MaxPageStateFieldLength-Eigenschaft angegebenen Wert übersteigt, werden zur Speicherung mehrere ausgeblendete Felder verwendet. Beim Zurücksenden der Seite an den Server wird die Zeichenfolge für den
Ansichtszustand bei der Seiteninitialisierung analysiert, und die Eigenschafteninformationen in der Seite werden wiederhergestellt. Die Möglichkeiten von Cookies und URLs stehen Ihnen in Asp.Net auch zur Verfügung.
ASP.NET bietet Ihnen vielfältige Möglichkeiten zum Verwalten von Zustandsinformationen auf dem Server, sodass die Informationen nicht für längere Zeit auf dem Client gespeichert werden müssen. Mit der serverbasierten
Zustandsverwaltung können Sie die Informationsmenge verringern, die zur Beibehaltung des Zustands an den
Client gesendet wird. Dabei werden jedoch kostbare Ressourcen auf dem Server verbraucht. In den folgenden
Abschnitten werden drei Features für die serverbasierte Zustandsverwaltung beschrieben: Anwendungszustand,
Sitzungszustand und Proleigenschaften.
Anwendungszustand:
In ASP.NET können Sie mithilfe des Anwendungszustands, der eine Instanz der
HttpApplicationState-Klasse ist, Werte für jede aktive Webanwendung speichern. Der Anwendungszustand
ist ein globaler Speichermechanismus, auf den von allen Seiten der Webanwendung aus zugegrien werden
kann. Der Anwendungszustand ist daher nützlich, um Informationen zu speichern, die zwischen Serverschleifen und zwischen Seitenanforderungen beibehalten werden müssen. Der Anwendungszustand wird in einem
Schlüssel/Wert-Wörterbuch gespeichert, das während jeder Anforderung einer bestimmten URL erstellt wird.
Sie können dieser Struktur die anwendungsspezischen Informationen hinzufügen, um sie zwischen Seitenanforderungen zu speichern. Nachdem Sie dem Anwendungszustand die anwendungsspezischen Informationen
hinzugefügt haben, wird er vom Server verwaltet. Verwendungsempfehlungen nden Sie unter Empfehlungen
zur ASP.NET-Zustandsverwaltung.
Sitzungszustand: In ASP.NET können Sie mithilfe des Sitzungszustands,
der eine Instanz der HttpSessionState-Klasse ist, Werte für jede aktive Webanwendungssitzung speichern. Eine
Übersicht nden Sie unter Übersicht über den ASP.NET-Sitzungszustand.
Der Sitzungszustand ist ähnlich dem Anwendungszustand, mit der Ausnahme, dass sich der Gültigkeitsbereich
auf die aktuelle Browsersitzung erstreckt. Wenn die Anwendung von verschiedenen Benutzern verwendet wird,
hat jede Benutzersitzung einen anderen Sitzungszustand. Wenn ein Benutzer die Anwendung verlässt und später
wieder damit arbeitet, hat die zweite Benutzersitzung einen anderen Sitzungszustand wie die erste. ASP.NET
enthält ein Feature mit dem Namen Proleigenschaften, mit dem Sie benutzerspezische Daten speichern können. Dieses Feature ähnelt dem Sitzungszustand, mit dem Unterschied, dass Proldaten nicht verloren gehen,
wenn eine Benutzersitzung abläuft. Das Proleigenschaftenfeature verwendet ein ASP.NET-Prol, das in einem
persistenten Format gespeichert und einem bestimmten Benutzer zugeordnet wird. Über das ASP.NET-Prol
können Sie problemlos Benutzerinformationen verwalten, ohne eine eigene Datenbank erstellen und pegen zu
müssen. Auÿerdem stellt das Prol die Benutzerinformationen mithilfe einer stark typisierten API zur Verfügung, auf die Sie von überall in Ihrer Anwendung zugreifen können. Sie können Objekte eines beliebigen Typs
im Prol speichern. Das ASP.NET-Prolfeature stellt ein generisches Speichersystem dar, mit dem Sie praktisch
alle Arten von Daten denieren und verwalten können. Trotzdem können Sie damit Daten typsicher zur Verfügung stellen. Um Proleigenschaften zu verwenden, müssen Sie einen Prolanbieter kongurieren. ASP.NET
enthält eine SqlProleProvider-Klasse, mit der Sie Proldaten in einer SQL-Datenbank speichern können. Sie
können jedoch auch eine eigene Prolanbieterklasse erstellen, die Proldaten in einem benutzerdenierten Format und mit einem benutzerdenierten Speichermechanismus speichert (z. B. in einer XML-Datei und sogar
in einem Webdienst). Da in Proleigenschaften gespeicherte Daten nicht im Anwendungsspeicher gespeichert
werden, bleiben sie auch bei Neustarts der Internetinformationsdienste (IIS) und des Workerprozesses vollständig erhalten. Auÿerdem können Proleigenschaften auch über mehre Prozesse hinweg erhalten bleiben, z. B. in
einer Webfarm oder in einem Webgarten.
7.1.5 Konguration
Mit den Features des ASP.NET-Kongurationssystems können Sie alle ASP.NET-Anwendungen auf einem kompletten Server, eine einzelne ASP.NET-Anwendung, einzelne Seiten oder Anwendungsunterverzeichnisse kongurieren. Zu den kongurierbaren Features gehören Authentizierungsmodi, Seitenzwischenspeicherung, Compileroptionen, benutzerdenierte Fehler, Debug- und Ablaufverfolgungsoptionen usw. Eine ausführliche Darstellung
nden Sie unter Verwalten von ASP.NET-Websites. Schauen Sie sich die Kongurationsdateien Maschine.cong
und Web.cong an.
Aufgabe 7.1
Arbeiten Sie den Artikel Conguration Overview: ASP.NET im Anhang ab Seite 47 durch. Wel-
che Kongurationsmöglichkeiten bietet Asp.Net?
Aufgabe 7.2
63
Arbeiten Sie die Unterschiede und die Gemeinsamkeiten zwischen ASP.Net und PHP heraus.
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
7.2 Asp.Net MVC
7.1.6 Ajax
Ajax-Entwicklung ist mit VS2010 direkt in die IDE eingebunden. Die ASP.NET Ajax Library enthält vielfältige
Controls und basiert auf der weit verbreiteten Bibliothek jQuery. Sie können natürlich, wie in EWA eingeführt,
eigene clientseitige Scripts erstellen und in Ihrere Applikation verwenden. Um die Ajax-Funktionalität einfach
nutzen zu können, müssen Sie ein ScriptManager Control auf Ihre Seite platzieren. Mit diesem Control werden
automatisch die benötigten Skriptbibliotheken auf den Client in der aktuellen Version geladen.
Mithilfe eines UpdatePanel können Sie die Teile der Seite begrenzen, die sich ändern, wenn Benutzeraktionen
normalerweise ein Postback verursachen würden.
Häug werden mit Ajax Daten eines WCF-Services angezeigt. Die Theorie zu WCF nden Sie in Abschnitt
8 auf Seite 67.
1 In
VS2010 können Sie ein neues Projekt auf Basis des Ajax-enabled WCF erzeugen und dort
folgenden Code eingeben:
Listing 7.1: Ajax-enabled WCF
[ServiceContract(Namespace = "http://www.yourcompany.com/ApplicationName")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class CustomerService
{
[OperationContract]
public List<Customer> GetCustomers(int numberToFetch)
{
using (NorthwindDataContext context = new NorthwindDataContext())
{
return context.Customers.Take(numberToFetch).ToList();
}
}
}
Speichern Sie dies als Service.svc ab.
Mit dem DataView-Control können Sie diese Daten nun einfach anzeigen.
<!DOCTYPE ...>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script type="text/javascript" src="Scripts/MicrosoftAjax.debug.js"></script>
<script type="text/javascript" src="Scripts/MicrosoftAjaxTemplates.debug.js"></script>
<script type="text/javascript" src="Scripts/MicrosoftAjaxAdoNet.debug.js"></script>
</head>
<body xmlns:sys="javascript:Sys"
xmlns:dataview="javascript:Sys.UI.DataView"
sys:activate="*">
<ul class="list sys-template"
sys:attach="dataview"
dataview:autofetch="true"
dataview:dataprovider="{{ new Sys.Data.AdoNetServiceProxy('WebDataService1.svc') }}"
dataview:fetchoperation="GetCustomers"
<li>{{ ContactName }}</li>
</ul>
</body>
</html>
7.2 Asp.Net MVC
2 Ein
zentrales Thema in ASP.NET MVC 2.0 ist die Zusammensetzbarkeit von Websites aus Bausteinen. Nun
kann der Entwickler einen View auf einfache Weise aus mehreren anderen Views zusammensetzen. Auÿerdem
kann eine MVC-Anwendung mehrere Unteranwendungen (sogenannte Areas) mit eigenen Views, Controllern
und Modellen besitzen, sodass sie eine eigenständige MVC-Anwendung bilden. Groÿe Webanwendungen werden
somit überschaubarer und können besser in getrennten Teams entwickelt werden. Während bei den dynamischen
Datenwebsites (Dynamic Data Websites) eine schnelle Lösung die oberste Maxime ist, zielt Microsoft mit dem
im März 2009 veröentlichten ASP.NET Model View Controller Framework (Namensraum System.Web.Mvc)
1 WALKTHROUGH Using a DataView with server data
2 TechEd Europe 2009: Microsoft stellt ASP.NET MVC 2
64
vor
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
7.2 Asp.Net MVC
auf klare Kompetenztrennung in lose gekoppelten, beliebig vielen Schichten, gute Test- und Wartbarkeit, die genaue Kontrolle über die Ausgabe und möglichst einfache, d.h. parameterlosen URLs. ASP.NET MVC folgt dem
allgemeinen MVC-Ansatz. Das Modell besteht aus Geschäftsobjekten, Logik und Datenzugri. Views erzeugen
die Benutzerschnittstelle und Aufgabe der Controller ist es, auf Benutzerinteraktionen zu reagieren und die dazu
passenden Daten aus dem Modell mit den Views zusammenzubringen. Als Modell können wieder LINQ-to-SQL
oder das ADO.NET EF zum Einsatz kommen das ist dann aber auch schon die einzige Gemeinsamkeit mit
den dynamischen Datenwebsites. Controller sind Klassen, die von System.Web.Mvc.Controller abgeleitet sind
und Aktionen in Form von Methoden implementieren. Sie nehmen die HTTP-Anfrage entgegen, wobei die Standardform /Controller/Aktion/Daten ist. So würde also http://Server/Flug/Edit/103 in der Controller-Klasse
Flug die Methode Edit() mit dem Parameter 103 aufrufen. Die Handhabung der vom Benutzer eingegebenen Daten würde eine gleichnamige Methode mit einem Parameter vom Typ FormCollection erledigen (siehe
Listing 7). Den gröÿten Unterschied zu den dynamischen Datenwebsites und auch den ASP.NET-Webforms
merkt man allerdings in den Views, denn für das MVC Framework gibt es nicht die komfortablen ASP.NETWebserversteuerelemente. Stattdessen fällt MVC zurück in die Welt von puren HTML-Tags und eingewobenen
Platzhaltern (< % = ... % >) wie einst die klassischen Active Server Pages (ASP) und heute noch PHP.
Wer eine Tabelle ausgeben will, kann sich also nicht auf die Abstraktion des GridView-Steuerelements stützen,
sondern muss die HTML-Tags <table>, <tr>, <td> etc. verwenden und die zugehörige Schleife über die
Datensätze explizit programmieren. MVC stellt lediglich zur Ausgabeerzeugung eine sehr bescheidene Anzahl
von Hilfsroutinen wie Html.TextBox(), Html.DropDownList() und Html.ActionLink() zur Verfügung, die aber
nur wenig Hilfe sind. Auch die Seitenzustandsverwaltung mit dem ASP.NET View State ist in MVC-Seiten
nicht möglich, d.h. der Entwickler muss sich nun wieder selbst darum kümmern, dass zwischen zwei Aufrufen
der gleichen Seite der Zustand der Seite wiederhergestellt wird. Auf der Haben-Seite notiert man dafür aber
zum einen die volle Kontrolle über das erzeugte HTML und zum anderen die Vermeidung des erzwungenen
Aufblähens der Webseite durch die Zustandsverwaltung.
Die Views sind hierarchisch im Dateisystem organisierte .aspx-Dateien, denen aber die sonst in ASP.NET
üblichen Code-Behind-Dateien fehlen. Listing 8 zeigt einen einfachen View zur Bearbeitung bestehender FlugObjekte. Erwähnt sein muss auch noch, dass es für die Views derzeit keinen Designer in Visual Studio gibt; hier
ist also Handarbeit angesagt. Zwar spricht Microsoft auch bei MVC von Scaolding, meint hier aber anders
als bei den dynamischen Datenwebsites eine einfache Codegenerierung zur Entwicklungszeit, die man in Visual
Studio für Controller und View anstoÿen kann.
Viele andere nicht an Webserversteuerelemente gebundene Dienste von ASP.NET Webforms wie die Vorlagenseiten, Authentizierung, Benutzerverwaltung, Sitzungsverwaltung, Zwischenspeicherung, Laufzeitüberwachung
und Sitemaps stehen aber im MVC Framework zur Verfügung.
Listing 7.2: ASP.Net MVC
//Ausschnitt aus dem Controller "Flug"
Public ActionResult Edit(int id)
{
// Zu aktualisierender Flug aus Datenbank holen
var movieToUpdate = _db.Flug.First(m => m.FlugNr == id);
ViewData.Model = movieToUpdate;
return View("Edit");
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(FormCollection form)
{
// Zu aktualisierender Flug aus Datenbank holen
int id = Int32.Parse(form["FlugNr"]);
Flug flug = _db.Flug.First(m => m.FlugNr == id);
// Deserialisieren
TryUpdateModel(flug, new string[] { "Abflugort", "Zielort" }, form.ToValueProvider());
// Validieren
if (String.IsNullOrEmpty(flug.Abflugort))
ModelState.AddModelError("Abflugort", "Abflugort is required!");
if (String.IsNullOrEmpty(flug.Zielort))
ModelState.AddModelError("Zielort", "Zielort is required!");
// Speichern
if (ModelState.IsValid)
{
65
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
7.2 Asp.Net MVC
_db.SaveChanges();
return RedirectToAction("Index");
}
// Sonst, zeige Eingabeformular erneut an
return View(flug);
}
//View "/Flug/Edit.aspx" zur Eingabe von Flugdaten
<form method="post" action="/Flug/Edit">
<%= Html.Hidden("FlugNr")%>
FlugNr:
<br />
<%= Model.FlugNr %>
<br />
Abflugort:
<br />
<%= Html.TextBox("Abflugort")%>
<br />
Zielort:
<br />
<%= Html.TextBox("Zielort")%>
<br />
<input type="submit" value="Save Flug" />
</form>
Aufgabe 7.3
Arbeiten Sie den Artikel Erstellen von Webanwendungen ohne Webformulare im Anhang ab Seite
56 durch.
66
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
8 Windows Communication Foundation
Abbildung 8.1: Assembly
Was ist die Windows Communication Foundation? Was ist die Windows Communication Foundation? Die
globale Akzeptanz von Webdiensten, wozu Standardprotokolle für Anwendung-zu-Anwendung-Kommunikation
gehören, hat die Softwareentwicklung verändert. Beispielsweise gehören zu den Funktionen, die Webdienste
nun bereitstellen, Sicherheit, Koordination verteilter Transaktionen und zuverlässige Kommunikation. Die Vorteile der Veränderungen im Bereich Webdienste sollten in den von Entwicklern verwendeten Hilfsmitteln und
Technologien widergespiegelt werden. Windows Communication Foundation (WCF) ist darauf ausgelegt, einen
verwaltbaren Ansatz zum verteilten Computing, zur weitreichenden Interoperabilität und für eine direkte Unterstützung der Dienstorientierung zu bieten. WCF erleichtert die Entwicklung von verbundenen Anwendungen
durch ein neues dienstorientiertes Programmierungsmodell. WCF unterstützt durch seine Schichtenarchitektur
viele Arten der Entwicklung verteilter Anwendungen. Auf der Basisschicht bietet die WCF-Kanalarchitektur
asynchrone, nicht typisierte nachrichtenübertragende Stammfunktionen. Auf dieser Basisschicht benden sich
Protokollfunktionen für einen sicheren, zuverlässigen, durchgeführten Datenaustausch und eine weitläuge Auswahl an Transport- und Codierungsoptionen.
8.1 Problembeispiel
Im folgenden Beispiel werden einige der Probleme veranschaulicht, die WCF angeht. Ein Autovermieter entscheidet sich, eine neue Anwendung zum Reservieren von Autos zu erstellen. Die Entwickler dieser Reservierungsanwendung für Mietwagen wissen, dass es möglich sein muss, auf die von ihr implementierte Geschäftslogik
über andere Software, sowohl innerhalb als auch auÿerhalb des Unternehmens, zuzugreifen. Daher entscheiden sie
sich, auf dienstorientierte Weise zu programmieren, wobei die Logik der Anwendung anderer Software über einen
denierten Satz von Diensten verfügbar gemacht wird. Zum Implementieren dieser Dienste und somit zum Kommunizieren mit anderer Software verwendet die neue Anwendung WCF.
Mietwagen-Szenario: Während ihrer
Lebensdauer wird voraussichtlich eine Reihe anderer Anwendungen auf die Mietwagen-Reservierungsanwendung
zugreifen. Bei ihrer Programmierung wissen die Ersteller der Mietwagen-Reservierungsanwendung jedoch, dass
drei andere Arten von Software auf ihre Geschäftslogik zugreifen werden:
•
Eine Call-Center-Clientanwendung, die auf den Windows-Desktops läuft, mit denen die Mitarbeiter im
Call-Center des Unternehmens arbeiten. Da diese Anwendung speziell für das neue Reservierungssystem
erstellt wird, wird sie auch mit Microsoft .NET Framework und WCF programmiert. Diese Anwendung
ist nicht wirklich unabhängig von der neuen Mitwagenreservierungs-Anwendung, da ihr einziger Zweck
darin besteht, als Client für das neue System zu dienen. Aus dienstorientierter Perspektive ist sie nur ein
weiterer Client für die Geschäftslogik des Reservierungssystems.
67
8.1 Problembeispiel
•
Eine vorhandene Reservierungsanwendung, die auf einem J2EE-Server erstellt wird, der auf einem NichtWindows-System läuft. Aufgrund einer kürzlich stattgefundenen Fusion mit einem anderen Autovermieter
muss das vorhandene System auf die Logik der neuen Anwendung zugreifen können, damit den Kunden
der fusionierten Unternehmen eine einheitliche Umgebung geliefert wird.
•
Partneranwendungen, die auf einer Vielzahl von Plattformen laufen, wobei sich jede in einem Unternehmen
bendet, das ein geschäftliches Arrangement mit dem Autovermieter eingegangen ist. Zu den Partnern
können Reisebüros, Fluggesellschaften und andere gehören, die für ihre Geschäfte Autovermietungsreservierungen vornehmen müssen.
Die verschiedenartigen Kommunikationsanforderungen für die neue Mietwagen-Reservierungsanwendung sind
nicht einfach. Für Interaktionen mit der Call-Center-Clientanwendung ist beispielsweise die Leistung wichtig,
während die Interoperabilität unkompliziert ist, da beide auf .NET Framework programmiert sind. Für die
Kommunikation mit der vorhandenen J2EE-basierten Reservierungsanwendung und den verschiedenen Partneranwendungen ist jedoch die Interoperabilität von höchster Bedeutung. Auÿerdem sind die Sicherheitsanforderungen sehr unterschiedlich, da sie je nach den lokalen Windows-basierten Anwendungen, einer J2EE-basierten
Anwendung, die auf einem anderen Betriebssystem läuft, und einer Vielzahl von Partneranwendungen, die über
das Internet hereinkommen, variieren. Sogar Transaktionsanforderungen könnten variieren, wenn nur die internen Anwendungen Transaktionsanforderungen stellen dürfen. Wie können diese verschiedenen geschäftlichen
und technischen Anforderungen erfüllt werden, ohne die Ersteller der neuen Anwendung vor unüberwindbare
Schwierigkeiten zu stellen?
WCF wurde für dieses komplexe, aber realistische Szenario entworfen und ist die Standardtechnologie für
Windows-Anwendungen, die Dienste verfügbar machen und die auf sie zugreifen. Dieses Thema bietet eine
Einführung in WCF, untersucht, was es bereitstellt und zeigt, wie es eingesetzt wird. In dieser Einführung
dient das gerade beschriebene Szenario als Beispiel. Das Ziel ist es, deutlich zu machen, was WCF ist, welche
Probleme es löst und zu zeigen, wie es diese Probleme löst.
Angehen des Problems: Die Grundlage für neue
Windows-basierte Anwendungen ist .NET Framework. Dementsprechend wird WCF hauptsächlich als ein auf
.NET Framework-CLR aufbauender Satz von Klassen implementiert. Da es die ihnen bekannte Umgebung erweitert, ermöglicht es WCF den Entwicklern, die derzeit objektorientierte Anwendungen über .NET Framework
Kommunikation zwischen einem WCF-Client und -Dienst: Wie das bereits beschriebene Szenario zeigt, dient WCF zum Lösen
erstellen, auch dienstorientierte Anwendungen auf gewohnte Weise zu programmieren.
einer Reihe von Herausforderungen für kommunizierende Anwendungen. Drei Punkte sind jedoch eindeutig die
wichtigsten Aspekte von WCF:
•
Vereinigung vorhandener .NET Framework-Kommunikationstechnologien.
•
Unterstützung für anbieterübergreifende Interoperabilität, einschlieÿlich der Zuverlässigkeit, Sicherheit
und Transaktionen.
•
Explizite Dienstausrichtung.
Da WCFs grundlegende Kommunikationsmechanismen SOAP-basierte Webdienste sind, können WCF-basierte
Anwendungen mit anderer Software kommunizieren, die in einer Vielzahl an Kontexten läuft. Eine unter WCF
erstellte Anwendung kann mit folgenden Anwendungen interagieren:
•
WCF-basierten Anwendungen, die in einem anderen Prozess auf dem gleichen Windows-Computer ausgeführt werden.
•
WCF-basierten Anwendungen, die auf einem anderen Windows-Computer ausgeführt werden.
•
Auf anderen Technologien erstellten Anwendungen, z. B. J2EE-Anwendungsservern, die Standardwebdienste unterstützen. Diese Anwendungen können auf Windows-Computern oder auf Computern, auf
denen andere Betriebssysteme laufen, ausgeführt werden.
Um mehr als nur grundlegende Kommunikation zuzulassen, implementiert WCF Webdiensttechnologien, die
durch WS-*-Spezikationen deniert werden. All diese Spezikationen wurden ursprünglich von Microsoft,
IBM und anderen Anbietern in Zusammenarbeit deniert. Wenn sich diese Spezikationen durchsetzen, gehen sie oft in den Besitz von Standardgremien wie dem World Wide Web Consortium (W3C) oder der
Organization for the Advancement of Structured Information Standards (OASIS) über. Diese Spezikationen betreen mehrere Bereiche, unter anderem grundlegendes Messaging, Sicherheit, Zuverlässigkeit, Transaktionen und das Arbeiten mit den Metadaten eines Diensts. Weitere Informationen nden Sie unter Interoperabilität und Integration. Weitere Informationen zu erweiterten Webdienstspezikationen nden Sie unter
http://go.microsoft.com/fwlink/?LinkId=86603 (möglicherweise in englischer Sprache).
68
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
8.2 Architektur
8.2 Architektur
Abbildung 8.2: Assembly
Windows Communication Foundation-Architektur Verträge und Beschreibungen
Verträge denieren verschiedene Aspekte des Nachrichtensystems. Der Datenvertrag beschreibt alle Parameter,
aus denen die einzelnen Nachrichten bestehen, die ein Dienst erstellen oder verarbeiten kann. Die Nachrichtenparameter werden in XSD-Dokumenten (XML-Schemadenitionssprache) deniert. Dadurch kann jedes XMLfähige System die Dokumente verarbeiten. Der Nachrichtenvertrag deniert anhand von SOAP-Protokollen
bestimmte Nachrichtenteile und ermöglicht eine detailliertere Steuerung der Teile einer Nachricht, wenn die
Interoperabilität diese Genauigkeit verlangt. Der Dienstvertrag gibt die tatsächlichen Methodensignaturen des
Dienstes an und wird als Schnittstelle in einer der unterstützten Programmiersprachen verteilt (z. B. Visual
Basic oder Visual C#).
Richtlinien und Bindungen legen die zur Kommunikation mit einem Dienst erforderlichen Bedingungen fest.
Beispielsweise muss die Bindung (mindestens) den verwendeten Transport (z. B. HTTP oder TCP) und eine
Codierung angeben. Richtlinien schlieÿen Sicherheitsanforderungen und andere Bedingungen ein, die für die
Kommunikation mit einem Dienst erfüllt werden müssen.
8.2.1 Dienstlaufzeit
Die Dienstlaufzeitebene umfasst Verhaltensweisen, die nur während der tatsächlichen Ausführung des Dienstes
auftreten, d. h. das Laufzeitverhalten des Dienstes. Die Drosselung steuert, wie viele Nachrichten verarbeitet
werden. Diese Zahl kann geändert werden, wenn die Nachfrage nach dem Dienst ein voreingestelltes Limit erreicht. Für den Fall eines internen Dienstfehlers gibt das Fehlerverhalten die zu ergreifenden Maÿnahmen an, z.
B. indem es steuert, welche Informationen an den Client übermittelt werden. (Zu viele Informationen könnten
einem böswilligen Benutzer einen Angri erleichtern.) Das Metadatenverhalten bestimmt, wie und ob Metadaten öentlich verfügbar gemacht werden. Wie viele Instanzen des Dienstes ausgeführt werden können, wird
vom Instanzenverhalten angegeben (Singleton gibt z. B. nur eine Instanz zur Verarbeitung aller Nachrichten
an). Das Transaktionsverhalten ermöglicht einen Rollback von durchgeführten Vorgängen im Fall eines Fehlers.
Mit dem Verteilungsverhalten wird die Verarbeitung von Nachrichten durch die WCF-Infrastruktur gesteuert.
Die Erweiterbarkeit ermöglicht eine Anpassung der Laufzeitprozesse. Beispielsweise werden mit der Nachrichteninspektion Teile einer Nachricht überprüft, und mit der Parameterlterung nden anhand von Filtern, die
auf Nachrichtenheader angewendet werden, voreingestellte Aktionen statt.
8.2.2 Messaging
Die Messagingebene besteht aus Kanälen. Ein Kanal ist eine Komponete, die eine Nachricht in bestimmter Weise verarbeitet, z. B. durch Authentizierung. Ein Reihe von Kanälen wird auch Kanalstapel genannt. Kanäle
arbeiten mit Nachrichten und Nachrichtenheadern. Die Dienstlaufzeitebene hingegen verarbeitet hauptsächlich den Inhalt des Nachrichtentexts. Es gibt zwei Arten von Kanälen: Transportkanäle und Protokollkanäle.
69
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
8.2 Architektur
Transportkanäle lesen und schreiben Nachrichten aus dem Netzwerk (oder einem Kommunikationspunkt zur
Auÿenwelt). Bei einigen Transporten wird ein Encoder verwendet, um Nachrichten (die als XML-Infosets dargestellt werden) in und aus der Bytestreamdarstellung des Netzwerks zu konvertieren. HTTP, benannte Pipes,
TCP und MSMQ sind Beispiele für Transporte. Beispiele für Codierungen sind XML und optimierte Binärdateien. Protokollkanäle implementieren Nachrichtenverarbeitungsprotokolle. Das erfolgt häug durch Lesen
oder Schreiben zusätzlicher Header in die Nachricht. Zu diesen Protokollen gehören beispielsweise WS-Security
und WS-Reliability. Die Messagingebene stellt die möglichen Formate und die Austauschmuster der Daten
dar. WS-Security ist eine Implementierung der WS-Security-Spezikation, die die Sicherheit auf der Nachrichtenebene aktiviert. Der WS-Reliable Messaging-Kanal stellt die Nachrichtenübermittlung sicher. Mit einer
Vielzahl von Codierungen ermöglichen es Encoder, die Nachrichtenanforderungen zu erfüllen. Der HTTP-Kanal
gibt die Verwendung des Hypertextübertragungsprotokolls zur Nachrichtenübermittlung an. Entsprechend gibt
der TCP-Kanal die Verwendung des TCP-Protokolls an. Der Transaktionsusskanal bestimmt die Muster von
Transaktionsnachrichten. Der Kanal für benannte Pipes ermöglicht die prozessübergreifende Kommunikation.
Der MSMQ-Kanal ermöglicht die Interoperation zwischen MSMQ-Anwendungen.
8.2.3 Hosting und Aktivierung
Ein Dienst ist letztendlich ein Programm. Wie andere Programme muss ein Dienst in einer ausführbaren Datei
ausgeführt werden. Dabei handelt es sich um einen so genannten selbst gehosteten Dienst. Dienste werden jedoch
auch gehosted oder in einer ausführbaren Datei von einem externen Agent verwaltet ausgeführt, z. B. IIS oder
Windows Activation Services (WAS). WAS ermöglicht es, WCF-Anwendungen auf Computern, auf denen WAS
ausgeführt wird, automatisch zu aktivieren. Sie können Dienste gegebenenfalls manuell als ausführbare Dateien
(EXE-Dateien) ausführen. Ein Dienst kann auch automatisch als Windows-Dienst ausgeführt werden. Auch
COM+-Komponenten können als WCF-Dienste gehostet werden.
Ein einfaches Beispiel für einen Ajax-fähigen WCF-Dienst nden Sie in Abschnitt 7.1 auf Seite 64.
70
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
9 Windows Workow Foundation
1 Windows
Workow Foundation (WF) ist ein Framework, das Benutzern das Erstellen von system- oder be-
nutzerbezogenen Workows in ihren Anwendungen für die Betriebssysteme Windows Vista, Windows XP und
Windows Server 2003 ermöglicht. Es besteht aus einem Namespace, einem prozessinternen Workowmodul und
Designern für Visual Studio. Windows Workow Foundation kann sowohl in einfachen Szenarios (z. B. zum
Anzeigen bestimmter Benutzeroberächensteuerelemente in Abhängigkeit von Benutzereingaben) als auch in
komplexen Szenarios (z. B. zur Bestellabwicklung und Lagerverwaltung) eingesetzt werden. Windows Workow
Foundation umfasst ein Programmiermodell, ein neu zu hostendes und anpassbares Workowmodul und die
Tools zum schnellen Erstellen workowfähiger Anwendungen unter Windows.
Zu den Szenarios, für die die Windows Workow Foundation eingesetzt werden kann, zählen Folgende:
•
Aktivieren von Workows innerhalb von Branchenanwendungen
•
Benutzeroberächen-Pageows
•
Dokumentorientierte Workows
•
Benutzerbezogene Workows
•
Zusammengesetzte Workows für dienstorientierte Anwendungen
•
Geschäftsregelbasierte Workows
•
Systemverwaltungsworkows
Gehen wir einen Schritt zurück. Was ist ein Workow? Ein Workow ist eine Folge von Schritten, Entscheidungen
und Regeln, die abgearbeitet werden müssen, um eine bestimmte Aufgabe zu erledigen.
Aufgabe 9.1
Beschreiben Sie die Schritte, Entscheidungen und Regeln, die für die Anmeldung zu einer Prüfung
notwendig sind.
Microsoft nennt die einzelnen Schritte eines Workows Activities. Ein Workowprojekt ist keine eigentliche
Anwendung, sondern benötigt immer einen Host, eine Asp.Net Applikation kann beispielsweise ein solcher Host
sein. Denken Sie beispielsweise an mögliche Verzweigungen in einer Web-Applikation, je nach Benutzereingaben
werden Sie auf eine andere Seite geleitet. Auch ein Neustart des Betriebssystems beendet einen Workow nicht.
Warum?
Es werden zwei Arten von Workows unterschieden: Statuscomputerworkows und Sequenzielle Workows.
9.1 Statuscomputerworkows
2 Im
Statuscomputerformat der Workowerstellung modelliert der Autor den Workow als Statuscomputer. Der
Workow selbst besteht aus einer Reihe von Zuständen. Ein Zustand wird als Ausgangszustand bezeichnet. Jeder
Zustand kann einen bestimmten Satz Ereignisse erhalten. Auf Grundlage eines Ereignisses kann ein Übergang
zu einem anderen Zustand erfolgen. Der Statuscomputerworkow kann über einen Endzustand verfügen. Beim
Übergang zum Endzustand wird der Workow abgeschlossen.
Das folgende Flussdiagramm ist ein Beispiel für einen Statuscomputerworkow.
Folgende Aktivitäten stehen out of the box zur Verfügung:
•
EventDrivenActivity Wird für Zustände verwendet, die für die Ausführung ein externes Ereignis benötigen. Die EventDrivenActivity-Aktivität muss über eine Aktivität verfügen, mit der die IEventActivitySchnittstelle als erste untergeordnete Aktivität implementiert wird. Weitere Informationen nden Sie unter
Verwenden der EventDrivenActivity-Aktivität.
1 Übersicht über die Windows
2 Statuscomputerworkows
Workow Foundation
71
9.2 Sequenzielle Workows
Abbildung 9.1: Beispiel für einen Statuscomputerworkow
•
SetStateActivity Gibt einen Übergang zu einem neuen Zustand an. Weitere Informationen nden Sie
unter Verwenden der SetStateActivity-Aktivität.
•
StateActivity Stellt einen Zustand auf einem Statuscomputer dar; enthält möglicherweise zusätzliche
Zustandsaktivitäten. Weitere Informationen nden Sie unter Verwenden der StateActivity-Aktivität.
•
StateInitializationActivity Wird ausgeführt, wenn ein Zustand eintritt. Enthält möglicherweise andere
Aktivitäten. Weitere Informationen nden Sie unter Verwenden der StateInitializationActivity-Aktivität.
•
StateFinalizationActivity Führt beim Verlassen einer StateActivity-Aktivität enthaltene Aktivitäten
aus. Weitere Informationen nden Sie unter Verwenden der StateFinalizationActivity-Aktivität.
9.2 Sequenzielle Workows
3 Mit
dem sequenziellen Workowformat wird eine Reihe enthaltener Aktivitäten der Reihenfolge nach ausge-
führt. Sie können einem sequenziellen Workow andere zusammengesetzte Aktivitäten hinzufügen, um Folgendes
zu erreichen: Parallelismus (ParallelActivity), ereignisgesteuerten Parallelismus (EventHandlingScopeActivity),
datengesteuerte Ausführung (ConditionedActivityGroup), ereignisgesteuerte Verzweigung (ListenActivity) und
vertraute imperative Ablaufsteuerungsmuster wie bedingte Verzweigung (IfElseActivity) und Iteration (WhileActivity, ReplicatorActivity). Zudem können benutzerdenierte zusammengesetzte Aktivitäten geschrieben
werden, mit denen spezielle Ablaufsteuerungsmuster implementiert werden.
Das folgende Flussdiagramm zeigt ein Beispiel eines sequenziellen Workows.
Abbildung 9.2: Beispiel für einen sequenziellen Workow
Ein sequenzieller Workow führt Aktivitäten bis zur Fertigstellung der letzten Aktivität sequenziell aus. Sequenzielle Workows sind auch im normalen Betrieb nicht notwendigerweise vollständig deterministisch. Beispielsweise kann eine ListenActivity-Aktivität oder eine ParallelActivity-Aktivität verwendet werden, wobei die
genaue Reihenfolge der Ereignisse in diesen Fällen variieren kann.
3 Sequenzielle
72
Workows
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
10 .Net Framework
Im letzten Jahrtausend wurde Cliententwicklung im Microsoftumfeld hauptsächlich mit VB6 betrieben. VB6
ist eine prozedurale interpretierte Sprache, mit der schon Vieles ging. Die aber die Entwicklung der Softwareentwicklung (OO, Modellierung, ...) nicht mitmachen konnte. Zusätzlich wurden performante Komponenten in
C++ entwickelt. Die Kommunkation zwischen den Komponenten wurde oft über COM realisiert. Die Konvertierung von Datentypen in VB6 und C++ war ein gängiges Problem. Auch die Installation und das Update von
Komponenten machte immer wieder Probleme (Versionskonikte, Registry). Der Siegeszug von Java mit der
Virtual Maschine und der damit verbundenen Plattformunabhängigkeit war ein groÿes Vorbild. Microsoft hat
viel von Java übernommen, einige Features dazu gepackt und daraus ein eigenes Framework entwickelt. .Net
adressiert die folgenden bis dahin aktuellen Probleme:
•
Austausch von Variablen/Objekten zwischen verschiedenen Komponenten, die evtl. in verschiedenen Programmiersprachen implementiert sind.
•
Interoperabilität, d.h. standardisierte Protokolle, Datenformate (XML-basiert)
•
Zustandslosigkeit des Webs
•
Ungleiche Programmierschnittstellen, zu viele APIs (COM, Win32, Microsoft Foundation Classes, ...)
•
Speicherverwaltung
•
Routinetätigkeiten, Implementation ähnlicher wiederkehrender Probleme
•
Softwareverteilung (Deployment)
Durch Mono gibt es .Net nicht nur für Windows, sondern auch für Unix/Linux. Die Entwickler von Mono sind
sehr aktiv, so gibt es jetzt schon eine Unterstützung von .Net 4.0 - was erst im April ausgerollt wird.
Im Originalton von Microsoft liest sich das wie folgt .Net: .NET Framework ist eine integrale WindowsKomponente, die die Entwicklung und Ausführung von Anwendungen und XML-Webdiensten der nächsten
Generation unterstützt. .NET Framework wurde im Hinblick auf folgende Zielsetzungen entwickelt:
•
Bereitstellung einer konsistenten, objektorientierten Programmierumgebung, in der Objektcode gespeichert wird. Die Ausführung erfolgt dann entweder lokal oder über Remotezugri bzw. lokal mit Verteilung
über das Internet.
•
Bereitstellung einer Codeausführungsumgebung, mit der Konikte bei der Softwarebereitstellung und
Versionskonikte auf ein Minimum beschränkt werden.
•
Bereitstellung einer Codeausführungsumgebung, die eine sichere Ausführung ermöglicht, und zwar auch
von Code, der von unbekannten oder nur halb-vertrauenswürdigen Dritten erstellt wurde.
•
Bereitstellung einer Codeausführungsumgebung, die nicht mehr die bei interpretations- oder skriptbasierten Umgebungen auftretenden Leistungsprobleme aufweist.
•
Schaung einer konsistenten Entwicklungsumgebung für die verschiedensten Anwendungsarten, wie beispielsweise Windows- und webbasierte Anwendungen.
•
Aufbau der gesamten Kommunikation auf Industriestandards, um die Integration von Code, der auf .NET
Framework basiert, in jeden anderen Code zu gewährleisten.
73
10.1 Vokabeln
10.1 Vokabeln
Grundlage ist die Common Language Infrastructure (CLI), die auch bei Standard ECMA-335 deniert ist:
•
Partition I: Konzepte und Architektur Beschreibt die Gesamtarchitektur der CLI. Speziziert dazu
das Common Type System (CTS), das Virtual Execution System (VES) und die Common Language
Specication (CLS).
•
Partition II: Metadatendenition und Semantik Enthält Informationen über Metadaten: Das physische
Layout der Dateien, die logischen Inhalte und deren Struktur.
•
Partition III: CIL Beschreibt die Instruktionen der Common Intermediate Language (CIL).
•
Partition IV: Bibliotheken Enthält eine Spezikation von Klassen und Klassenbibliotheken, die als Teil
der CLI standardisiert sind.
•
Partition V: Beschreibt das einheitliche Debuggingformat.
•
Partition VI: Anhänge.
Abbildung 10.1: CTS in Relation zu CLS
Das Common Type System (CTS) ist die Gesamtmenge aller möglichen Eigenschaften und Typen (siehe Abbildung 10.3). Die Common Language Specication (CLS) ist eine Untermenge der CTS-Eigenschaften und Typen,
die von jedem Compiler unterstützt werden müssen, um die Interoperabilität zwischen der .Net Applikationen
zu garantieren. Nicht CLS-konforme Typen sind beispielsweise Int8 oder Unsigned int64.
Die CLS stellt einen Satz von Merkmalen sicher, die in jeder Sprache, die in die Laufzeitumgebung integriert
wird, garantiert zur Verfügung stehen. Die CLS garantiert, dass jedes Programm bzw. jeder Programmteil (z. B.
eine einzelne Klasse), der CLS-konform entwickelt worden ist, in jeder anderen CLS-kompatiblen Programmier-
1
sprache vollständig genutzt werden kann . Beispielsweise gibt es nur einen unicode-basierten Stringtyp und alles
ist ein Objekt. Dadurch ist die .Net Plattform selbst sprachneutral und jede der dort verwendeten Sprachen ist
gleich berechtigt. Momentan sind 57 Sprachen bei Wikipedia (Liste von .NET-Sprachen) gelistet, die in einer
modizierten Implementation, im .Net Framework laufen. Darunter nden sich PHP, Python, Prolog, Fortran,
Java, Perl und Ruby.
Ähnlich zur Java Virtual Maschine gibt es bei .Net eine Common Language Runtime (CLR), die alles zur
Verfügung stellt, damit Komponenten miteinander interagieren können. Sie erzeugt Objekte, übernimmt Methodenaufrufe und verwaltet Speicher, Prozesse und Sicherheit.
Abbildung
??
zeigt wie das Framework arbeitet. Mit einem Vorcompiler wird Programmcode in die Common
Intermediate Language (CIL) (teilweise auch nur Intermediate Language (IL) genannt) übersetzt. CIL ist eine
objektorientierte Assemblersprache. Das physikalische Ergebnis dieses Compilevorgangs sind die sogenannten
Assemblies (siehe Abschnitt 10.2 auf Seite 78). Die Assemblies benden sich im lokalen Verzeichnis oder sind
im Global Assembly Cache (GAC) registriert. Der Just in time compiler (JIT) erzeugt daraus sogenannten
Managed Code, der in der Common Language Runtime (CLR) für das jeweilige Betriebssystem umgesetzt wird.
1 Die
74
Regeln der CLS gelten dabei immer nur für öentliche (public oder protected) Schnittstellen.
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
10.1 Vokabeln
Abbildung 10.2: Ausführungsmodell
Managed Code ist per Denition Code, der von der CLR interpretiert wird. Jeder andere Code ist unmanaged.
Unmanaged Code unterliegt also nicht der Aufsicht der CLR, ist plattformabhängig und typischerweise ohne
das .Net Framework erstellt. Der durch den JIT-Compiler erstellte Maschinencode wird bei Beendigung des
Prozesses, der die ausführbare Datei ausführt, verworfen. Daher muss die Methode beim erneuten Ausführen
der Anwendung wieder neu kompiliert werden. NGen erlaubt die Vorkompilierung von CIL-Anwendungen in
Maschinencode vor der Ausführungszeit typischerweise während der Installation. Dies führt zu zwei wesentlichen Leistungsvorteilen. Erstens wird die Dauer zum Starten der Anwendung verringert, da eine Kompilierung
zur Ausführungszeit vermieden wird. Zweitens wird die Speichernutzung verbessert, da ein gemeinsames Verwenden der Codepages durch mehrere Prozesse unterstützt wird. Obwohl NGen auf den ersten Blick einer
herkömmlichen statischen Back-End-Kompilierung zu entsprechen scheint, ist es tatsächlich etwas ganz anderes. Im Unterschied zu statisch kompilierten Binärdateien werden NGen-Images an die Maschine gebunden,
auf denen sie erstellt wurden, und können daher nicht allgemein bereitgestellt werden. Stattdessen muss das
Installationsprogramm einer Anwendung Befehle ausführen, um systemeigene Images der jeweiligen Assemblys
auf dem Clientcomputer zu erstellen (ausführlichere Artikel: Die Leistungsvorteile durch NGen NGen Revs Up
Your Performance with Powerful New Features).
Abbildung 10.3: Speicherverwaltung
Der Garbage Collector der CLR verwaltet die Reservierung und Freigabe von Arbeitsspeicher für die Anwendung. Wenn Sie den Operator new zum Erstellen eines Objekts verwenden, reserviert die CLR Arbeitsspeicher für
das Objekt auf dem verwalteten Heap. Solange ein Adressbereich im verwalteten Heap verfügbar ist, reserviert
die Laufzeit Arbeitsspeicher für neue Objekte. Arbeitsspeicher ist jedoch nicht unendlich verfügbar. Möglicherweise muss mithilfe der Garbage Collection Arbeitsspeicher freigegeben werden. Das Optimierungsmodul der
Garbage Collection bestimmt den besten Zeitpunkt für das Einsammeln anhand der erfolgten Reservierungen.
Beim Einsammeln durch die Garbage Collection wird nach Objekten im verwalteten Heap gesucht, die nicht
75
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
10.1 Vokabeln
mehr von der Anwendung verwendet werden. Anschlieÿend werden die für das Freigeben des Arbeitsspeichers
erforderlichen Operationen ausgeführt. Dafür weiÿ man nicht, wann der Speicher wirklich freigegeben wird. Das
Framework bietet auch direkten Zugri auf den GC, damit sollte man jedoch sehr sehr vorsichtig umgehen.
Garbage Collector:
gibt Speicher aller Objekte frei, auf die kein Verweis mehr existiert.
Eine genaue Beschreibung der Arbeitsweise des GC in C# bzw. Java nden Sie im Anhang auf Seite 84.
Aufgabe 10.1
•
Beschreiben Sie die wesentlichen Phasen im Lebenszyklus eines Objekts.
•
Klären Sie die Begrie Stack, Heap, managed Heap,
•
Wie ist die prinzipielle Arbeitsweise des GC?
•
Wie viele Generationen werden verwaltet?
•
Was landet auf dem Stack, was auf dem Heap?
•
Lesen Sie auch den Artikel des GC in Java: Gibt es prinzipielle Unterschiede, wenn ja, welche?
Das Sicherheitsmodell der CLR deckt folgende Bereiche ab:
•
Typsicherheit: C# erlaubt Zeiger nur innerhalb eines unsafe-Blocks, Basis ist die CLS. Die CLR führt eine
Typsicherheitsprüfung während des JIT-Vorgangs durch und weigert sich bei Fehlern, diesen auszuführen.
Sie ist manuell durchführbar mittels PEVerify.exe.
•
Code-Signierung ermöglicht es Sicherheitseinschränkungen für Assemblies und Code zu denieren. Diese
Einschränkungen und Regeln werden von der CLR erzwungen und sowohl durch die Entwicklerin als auch
durch die Systemadministratorin deniert.
•
Kryptographie
•
Code Access Security (CAS)
Abbildung 10.4: Code Access Security, Quelle: Uni Karlsruhe, Microsoft .Net Sicherheit
Abbildung 10.4
CAS schützt den Zugri auf Systemresourcen über Berechtigungen (Permissions). Berechtigungen können mit
Hilfe eines Berechtigungsatzes (Permission Set) gruppiert werden. Dieser Vorgang ist mit den von Windows her
bekannten Benutzern und Benutzergruppen vergleichbar. Ein Berechtigungssatz ist dabei grundsätzlich einer
Codegruppe (Code Group) zugeordnet. Eine Code-Gruppe ist eine logische Einstufung von Code anhand einer
Bedingung, z.B. alle Assemblies von http://www.somewebsite.com/ gehören zur Gruppe SomeCode. Sobald ein
Assembly geladen wird, untersucht die CLR dieses Assembly und legt fest, welche Codemerkmale (Evidence) es
besitzt Standardmäÿig sind im .NET Framework insgesamt sieben verschiedene Codemerkmale vordeniert:
Application Directory, Hash, Publisher, Site, Strong Name, URL und Zone. Die Zuordnung eines Codemerkmals zu einer Codegruppe und damit den Rechten, die das Assembly zur Laufzeit erhält erfolgt über die
sogenannte Zugehörigkeitsbedingung (Membership Condition). Wichtig dabei: Besitzt ein Assembly mehrere
76
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
10.1 Vokabeln
Codemerkmale, wird es auch mehreren Codegruppen gleichzeitig zugeordnet. Die jeweiligen Rechtemengen werden dabei zu einer Vereinigungsmenge zusammengeführt (ODER-Verknüpfung)! Durch dieses Verhalten kann
ein Assembly mehr Rechte zugewiesen bekommen, als ursprünglich vorgesehen. Aus diesem Grund kann eine
Codegruppe mit dem Attribut exklusiv versehen werden. Das Assembly bekommt dann nur die Berechtigungen
dieser einzelnen Gruppe zugewiesen. Mit PermView.exe kann man analysieren, welche Rechte ein Programm
benötigt. Der Lader liest die Metadaten und legt im Hauptspeicher eine interne Repräsentation der Klassen und
seiner Mitglieder an. Eine Klasse wird nur dann geladen, wenn sie auch referenziert wird.
Abbildung 10.5: Zusammenspiel von Loader, CAS und GAC (Quelle:Microsoft.NET Framework Runtime, Teil
1: Die ausführung von Assemblys)
Abbildung 10.5 zeigt, wie und wann die CLR Sicherheitschecks durchführt. Einen ausführlichen Artikel hierzu
nden Sie unter Understanding The CLR Binder.
Auch die Fehlerbehandlung passiert in der CLR. Früher gab es diverse Methoden, wie Win32 Error Codes,
COM HRESULTs oder in VB6 On Error GoTo. Dabei war es problematisch, dass diese leicht zu ignorieren
und nicht strukturiert waren, zu wenig Informationen enthielten und nicht sprach- und technologieübergreifend
zur Verfügung standen. Exceptions der CLR lösen dies auf: Signalisiert ein Objekt einen Ausnahmezustand, so
ist diese Ausnahme einheitlich für alle .Net Sprachen, müssen behandelt werden, können vererbt werden (über
Sprachgrenzen hinaus) und enthalten Zusatzinformationen (Stacktrace). Nicht behandelte Ausnahmen werden
an die aufrufende Komponente weitergereicht.
77
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
10.2 Assembly
10.2 Assembly
Abbildung 10.6: Assembly
Microsoft deniert ein Assembly so:
Assemblies sind die Grundbausteine von .NET Framework-Anwendungen. Sie bilden die Basiseinheit für Weitergabe, Versionskontrolle, Wiederverwendung, Aktivierungs-Scoping und Sicherheitsberechtigungen. Eine Assembly ist eine Auistung von Typen und Ressourcen, die so erstellt wurden, dass sie zusammenarbeiten und eine
logische funktionelle Einheit bilden. Eine Assembly stellt der Common Language Runtime die für das Erkennen
von Typimplementierungen erforderlichen Informationen zur Verfügung. Für die Common Language Runtime
sind Typen nur im Kontext einer Assembly vorhanden. Quelle: Assemblies kurz und knapp.
Ein Assembly ist somit . . .
•
eine Menge von Typen, die zusammenkompiliert werden, inklusive Ressourcen wie Bilder.
•
eine Laufzeiteinheit, die als Datei gespeichert wird (*.exe oder *.dll), auch portable Executable (PE)
genannt.
•
die kleinste Einheit in Bezug auf
Deployment,
dynamischem Laden,
Sicherheit
Versionierung
•
enthält Code (objekorientierter Assemblercode)
•
enthält Metadaten
•
enthält ein Manifest (Inhaltsverzeichnis)
•
ist vergleichbar mit einer Java JAR-Datei
•
ist eine .Net Komponente
Man unterscheidet zwischen privaten und öentlichen Assemblies. Öentliche Assemblies werden im GAC registriert, was zur Folge hat, dass sie über einen strong name verfügen müssen. Ein solcher Name ist eindeutig,
ähnlich einer GUID. Assemblies im GAC stehen allen Applikationen zur Verfügung. Private Assemblies benden
sich im Verzeichnis der Applikation, können nur von dieser Applikation verwendet werden und können nicht
signiert werden. Der GAC ist ein sicherer Speicher, der verschiedene Versionen einer Assembly enthalten kann.
Ein Strong Name setzt sich aus vier Teilen zusammen:
78
•
Dateiname, z.B. db.dll
•
Versionsnummer, z.B. 1.1.123.25
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
10.2 Assembly
•
der Kultur (de-de, en-us, ...), Sprach und länderspezische Information
•
einem öentlichen Schlüssel
Die Metadaten enthalten den public key, Informationen über exportierte Typen (Name, Sichtbarkeit, Basisklasse,
implementierte Interfaces, Members, Attribute), Abhängigkeiten zu anderen Assemblies und Sicherheitseinstellungen.
Abbildung 10.7: Der Assembly Resolver im Detail, Quelle: Assemblies Pakete einer .NET Anwendung
Abbildung 10.7 zeigt, wie die richtige Version einer Assembly gesucht wird.
Wenn verwalteter Code höhere Berechtigungen verlangt, ist es besser, diesen Code in einer anderen Assembly
als den Code zu speichern, für den die höheren Berechtigungen nicht erforderlich sind.
Bei der Assemblysicherheit unterscheidet man drei Arten:
•
SAFE ist der Standardberechtigungssatz und die restriktivste Einstellung. Code, der von einer Assembly
mit SAFE-Berechtigungen ausgeführt wird, kann nicht auf externe Systemressourcen wie z. B. Dateien,
das Netzwerk, Umgebungsvariablen oder die Registrierung zugreifen. SAFE-Code kann auf Daten aus
den lokalen SQL Server-Datenbanken zugreifen oder Berechnungen und Geschäftslogik ausführen, für die
kein Zugri auf Ressourcen auÿerhalb der lokalen Datenbanken erforderlich ist. Die meisten Assemblys
führen Berechnungs- und Datenverwaltungsaufgaben aus, ohne dass ein Zugri auf Ressourcen auÿerhalb
erforderlich ist. Aus diesem Grund wird empfohlen, SAFE als Berechtigungssatz für die Assembly zu
verwenden.
•
EXTERNAL_ACCESS ermöglicht Assemblys den Zugri auf bestimmte externe Systemressourcen
wie z. B. Dateien, Netzwerke, Webdienste, Umgebungsvariablen und die Registrierung. SAFE- und
EXTERNAL_ACCESS-Assemblys können nur Code enthalten, der überprüfbar typsicher ist. Dies bedeutet, dass diese Assemblys auf Klassen nur über denierte Einstiegspunkte zugreifen können, die für die
79
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
10.3 Anwendungsdomäne (AppDomain)
Typdenition gültig sind. Daher können sie nicht beliebig auf Speicherpuer zugreifen, die sich nicht im
Besitz des Codes benden.
•
UNSAFE ermöglicht Assemblys den uneingeschränkten Zugri auf Ressourcen. Code, der aus einer
UNSAFE-Assembly ausgeführt wird, kann nicht verwalteten Code aufrufen. Wenn Sie UNSAFE angeben, kann der Code in der Assembly auÿerdem Vorgänge ausführen, die von der CLR-Überprüfung als
typunsicher betrachtet werden. UNSAFE-Assemblys können auÿerdem potenziell das Sicherheitssystem
der CLR unterlaufen. UNSAFE-Berechtigungen sollten nur hochgradig vertrauenswürdigen Assemblys
durch erfahrene Entwickler oder Administratoren erteilt werden. Nur Mitglieder der festen Serverrolle
sysadmin können UNSAFE-Assemblys erstellen.
Immer dann, wenn Sie innerhalb einer Assembly auf Nicht-.Net-Code, wie z.B. COM-Komponenten zugreifen
möchten, müssen Sie leider UNSAFE wählen.
10.3 Anwendungsdomäne (AppDomain)
Anwendungsdomänen sind isolierte Bereiche im CLR-Prozess. Sie werden auch als leichtgewichtige Unterprozesse oder Pseudoprozesse bezeichnet. Wichtig ist, sie sind KEINE Prozesse! Innerhalb der CLR gibt es keine
Hardware-Kapselung. Anwendungsdomänen ...
•
Schirmen Applikationen gegeneinander ab
•
Umfassen (und isolieren) Assemblies, Module und Typen einer Applikation, Methodentabellen der geladenen Typen und Statische Felder und Objekte der geladenen Typen
•
Können entweder direkt im Code (System.AppDomain.CreateDomain) oder vom jeweiligen Host der CLR
erzeugt werden.
Es gibt keine direkte Kommunikation zwischen Anwendungsdomänen. Es ist auch kein direkter Zugri
auf
Assemblies
anderer
Anwendungsdomänen
möglich.
Die
CLR
verhindert
die
Übergabe
von
Objekt-
Referenzen zwischen Anwendungsdomänen. Eine Interaktion ist nur über einen speziell gesicherten InterDomain-Kommunikationsmechanismus möglich.
Bei dem Prinzip der Anwendungsdomänen werden verschiedene Anwendungen oder Ausführungseinheiten in den
gleichen Prozessraum geladen und in verschiedene Subprozesse gekapselt. Hierdurch entfallen Leistungeinbuÿen
durch Prozesswechsel. Die .NET Laufzeitumgebung sorgt dafür, dass die einzelnen Anwendungsdomänen nicht
in den Speicherbereich anderer Anwendungen im gleichen Prozessraum schreiben.
Typischerweise arbeitet man nur mit einer Anwendungsdomäne. Mehrere Anwendungsdomänen sind erforderlich, wenn Code mit anderen Sicherheits-Einstellungen/Zugrisrechten geladen wird, die Isolation zwischen
Code-Teilen explizit gewünscht wird, oder Code unabhängig voneinander terminieren können soll. Für Plug-Ins
beispielsweise bieten sich separate Anwendungsdomänen an, also eine AppDomain pro Plug-In. Das Stichwort
zur Kommunikation zwischen verschiedenen Anwendungsdomänen ist Remoting. Remoting ist die Bezeichnung
für (Netzwerk)komunikation der Programmiersprache C#. Kommunikation zwischen Objekten in verschiedenen
AppDomains oder in Prozessen auf verschiedenen Computern wird ermöglicht.
Abbildung
??
Wie genau, die Kommunikation zwischen verschiedenen Anwendungsdomänen erfolgt beschreibt der Artikel
Baukasten für verteilte Anwendungen:
Marshalling:
Sobald ein Aufruf eine Appdomain-Grenze überschreitet ist Marshalling notwendig.
Oder anders formuliert: Die Remoting-Mechanismen greifen. Bild
halling
2
??
zeigt diesen Zusammenhang. Das Mars-
kann dabei entweder by Value oder by Reference erfolgen und es kommt die zweite,wichtige Regel ins
Spiel: Marshalling ist immer mit Serialisierung und Deserialisierung verbunden.
2 Marshalling
ist das Verpacken eines Programmcodeaufrufs in eine Nachricht, sodass ein Aufruf zwischen zwei Programmen
möglich ist, die keinen gemeinsamen Speicher besitzen.
80
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
10.4 Basisklassen
Abbildung 10.8: Beschreibung.
10.4 Basisklassen
Ein Namensraum ist ein logisches Benennungsschema zum Gruppieren von verwandten Typen. In einer Datei
können mehrere Klassen und mehrere Namensräume deniert werden dies erhöht sicher nicht die Übersicht.
Namensräume und Klassen sind vom Verzeichnisbaum unabhängig! Mit dem Schlüsselwort using können in C#
Namensräume in andere Namensräume importiert werden. Es ist auch möglich, Aliase zu vergeben. Namensräume sind hierarchisch organisiert. Mit using System werden alle öentlichen Typen von System importiert.Möchte
man eine Klasse innerhalb von System verwenden, so ist diese in Bezug auf System voll anzugeben.
Die Basisklassen von .Net sind in solchen Namensräumen abgelegt. .Net Framework 4 and Extensions oder .Net
Framework 4 Universe veranschaulichen die aktuelle Struktur und Neuerungen.
Aufgabe 10.2
Vergleichen Sie die Begrie Namensraum in C# und Package in Java. Arbeiten Sie die Ge-
meinsamkeiten und die Unterschiede heraus.
10.5 Typsystem von C#
Abbildung 10.9: Links: Common Type System, Rechts: value-type und reference-type
Betrachtet man das CTS, also die Gesamtzahl aller Typen, so kann man diese für C# wie folgt klassizieren:
81
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
10.5 Typsystem von C#
•
•
Werttypen
Enumeration (enum)
Strukturen (struct)
einfache Typen (sbyte, int, char, . . . nicht string!)
Referenztypen
Klassen
Felder
Schnittstellen
Delegate
Dies hat jedoch nichts mit der Parameterübergabe byref bzw. byval zu tun.
Zwischen den einfachen Typen gibt es die typischen impliziten Konvertierungen. Wir erinnern uns daran, dass
alle Typen in C# von object abgeleitet sind. Dies erönet uns zusätzliche Möglichkeiten: Jeder Datentyp kann
als Objekt gespeichert oder übergeben werden.
Abbildung 10.10: Boxing und Unboxing
Der Boxing Vorgang beschreibt die Umwandlung von einem value-type zu einem Objekt. Bei dem Boxing
Vorgang wird die Instanz eines Objekts erstellt und der ursprüngliche Wert des value-types wird in das neue
Objekt kopiert. Man könnte sagen, dies sei der umgekehrte Vorgang. Beim Unboxing Vorgang wird ein value type
aus einem Objekt extrahiert. Der C# Compiler überprüft zusätzlich, ob die von Ihnen angeforderte Variable,
welche Sie aus dem Objekt extrahieren wollen, tatsächlich den angeforderten unboxed Type entsprechen kann.
Folie 10.10 veranschaulicht den Vorgang an einem Beispiel.
Boxing und Unboxing ist ein zentraler Teil im Type-System von C# (und der Runtime). Es stellt ein Bindeglied zwischen value-types und reference-types zur Verfügung. Value-types haben den Vorteil, dass sie wenig
Speicherplatz benötigen. In manchen Fällen ist es jedoch von Vorteil, dass value-types auch die die Eigenschaften von Objekten haben. Diese Übereinkunft macht den Hauptteil von C# aus, daÿ die Verknüpfung zwischen
value-types und reference-types dadurch geschieht, dass ein value-type aus einem und in ein Objekt ungewandelt
werden kann. Unter dem Motto Es ist zwar alles ein Objekt, aber nur dann wenn es eines sein muss. Quelle:
Datentypen
Zahlen - Werttypen:
Es gibt Werttypen für Zahlen mit verschiedenen Gröÿen und Genauigkeiten. Eine gröÿere Zahl oder eine Zahl
mit einer höheren Genauigkeit braucht mehr Speicher auf dem Stack. Jeder Werttyp hat eine Gröÿe. Es ist nicht
möglich, einen Wert eines gröÿeren Typs in eine kleinere Variable zu stecken, unabhängig davon, wie groÿ die
Zahl tatsächlich ist.
1
c Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Listings
3.1
C# Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2
Operatoren
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
3.3
Stringreferenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
3.4
Stringreferenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
3.5
Strings mit @ - ähnlich Heredoc von PHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
3.6
StringBuilder zur Verkettung von Strings
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
3.7
String auf leer prüfen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
3.8
is/as performant einsetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
3.9
Beispiel für Strukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
3.10 Generika . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
3.11 Listen
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.12 Enumerationen
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
16
17
3.13 Enumeration als Flags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
3.14 Trace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
3.15 Schlechter Code Fehler schlucken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19
3.16 Indexer
20
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.17 Iteratoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
20
3.18 Delegaten
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21
3.19 Delegaten, Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21
3.20 Multicastdelegaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
3.21 Ereignisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25
3.22 Asynchrone Delegate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
26
3.23 Benutzerdeniertes Attribute und Reektion
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30
3.24 Ausdruckslambdas
3.25 Anweisungslambdas
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30
3.26 Dispose-Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
31
3.27 using . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
33
3.28 Attribut Serializable
33
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.1
Add-In . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
4.2
Add-In nutzen
43
6.1
Datenzugri mit DataReader
6.2
Update mit DataSet
6.3
LINQ-Beispiele
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
55
6.4
Datenbindung an ein DataGrid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
6.5
Datenbindung in WPF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
58
6.6
Serverseitige Datenbindung in Asp.Net . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
58
7.1
Ajax-enabled WCF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
64
7.2
ASP.Net MVC
65
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
50
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
C# Event Implementation Fundamentals, Best Practices
and Conventions
By Jeffrey Schaefer
Download source - 12.5 KB
Shortened, full article see http://www.codeproject.com/KB/cs/event_fundamentals.aspx
Terminology and Definitions
The literature presenting events and related concepts frequently makes use of multiple words
or expressions to describe any given concept. The following list catalogs much of this
terminology with brief explanations of the concepts behind the expressions. The term, event,
typically means that a change in state of an object has occurred or is about to occur. The term
is also used in reference to some activity taking place within an object or application -activity like the processing of a gesture from an end user (e.g., button clicked), or the
reporting of progress during a long-running task. The term, state, refers to the current set of
values of one or more variables in an object or application. A change in state means the value
of one or more variables within an object has changed. In the event notification process,
changes in state, or expected changes in state, are primary motivations for raising events. So,
we have two ways to define an event relative to a change in state: immediately prior to a
change in state, or immediately after a change in state. While the former are referred to as preevents, the latter are referred to as post-events. event publisher, event source, subject
maintain their internal state, and notify other classes (subscribers) through the raising of
events or similar notification mechanisms. event subscriber, sink, listener, observer are the
classes or objects that are interested in changes in state (or expected changes in state) of the
event publishers. Event notifications (frequently expressed as, "fire an event" or "raise an
event" or "trigger an event") are generally in the form of the event publisher calling a method
in one or more subscribers. Consequently, the raising of an event ultimately means that code
in the event publisher causes code in one or more subscribers to run. When an event is raised,
the publisher will frequently include data (event data, event-related data, and event arguments
("event args"))that gets sent to the subscribers through the event notification process. This
data is presumably relevant to the particular event that was raised, and would be of interest to
the event subscribers.
For example, an event can be raised when a file gets renamed. Data relevant to that particular
"file renamed" event could include (1) the name of the file before the name was changed, and
(2) the name of the file after the name was changed. Those file names could comprise the
event data that are sent to the subscribers during the raising of the "file renamed" event.
To summarize; an "event handler" is the delegate upon which an event is based, while an
"event handling method" is a method called in the subscriber when an event is raised. Event
handlers are delegates, although delegates are not necessarily event handlers (there are many
uses of delegates beyond supporting events). Delegates are presented in more detail later in
this article, but only to the extent that they are relevant to events.
Events, as implemented in the .NET Framework and as described in this article, constitute
a .NET optimized implementation of the Observer Pattern that was documented by the "Gang
of Four" or "GoF" (Gamma et al.1995).
C# Event Implementation Fundamentals, Best Practices and Conventions
Seite 1 von 10
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Delegates
In order to understand events, as implemented in .NET applications, one must have a clear
understanding of the .NET delegate type and the role it plays in the implementation of
events.
Definition and Usage of Delegates
Delegates can be understood as intelligent containers that hold references to methods, as
opposed to containers that hold references to objects. Delegates can contain references to zero,
one, or many methods. In order for a method to be called by a particular delegate instance,
that method must be registered with the delegate instance. When registered, the method is
added to the delegate's internal collection of method references (the delegate's "invocation
list"). Delegates can hold references to static methods or instance methods in any class visible
to the delegate instance. Delegate instances can call their referenced methods either
synchronously, or asynchronously. When called asynchronously, the methods execute on a
separate thread. When a delegate instance is invoked ("called"), then all methods referenced
by the delegate are called automatically by the delegate. Delegates cannot contain references
to just any method. Delegates can hold references only to methods defined with a method
signature that exactly matches the signature of the delegate. Consider the following delegate
declaration:
public delegate void MyDelegate(string myString);
Notice that the delegate declaration looks like a method declaration, but with no method body.
The signature of the delegate determines the signature of methods that can be referenced by
the delegate. So, the sample delegate above (MyDelegate) can hold references only to
methods that return void while accepting a single string argument. Consequently, the
following method can be registered with an instance of MyDelegate:
private void MyMethod(string someString)
{
// method body here.
}
The following methods, however, cannot be referenced by a MyDelegate instance because
their signatures do not match that of MyDelegate.
private string MyOtherMethod(string someString)
{
// method body here.
}
private void YetAnotherMethod(string someString, int someInt)
{
// method body here.
}
After a new delegate type is declared, an instance of that delegate must be created so that
methods can be registered with, and ultimately invoked by, the delegate instance.
// instantiate the delegate and register a method with the new instance.
C# Event Implementation Fundamentals, Best Practices and Conventions
Seite 2 von 10
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
MyDelegate del = new MyDelegate(MyMethod);
After a delegate is instantiated, additional methods can be registered with the delegate
instance, like this:
del += new MyDelegate(MyOtherMethod);
At this point, the delegate can be invoked, like this:
del("my string value");
And, because both MyMethod and MyOtherMethod are registered with the MyDelegate
instance (named del), that instance will invoke both MyMethod and MyOtherMethod when the
above line executes, passing each the string value, "my string value."
Delegates and Overloaded Methods
In the case of an overloaded method, only the particular overload having a signature that
exactly matches the signature of the delegate can be referenced by (or registered with) the
delegate. When you write code that registers an overloaded method with a delegate instance,
the C# compiler will automatically select and register the particular overload with a matching
signature.
So, for example, if your application declared the following delegate type...
public delegate int MyOtherDelegate(); // returns int, no parameters
... and you registered an overloaded method named MyOverloadedMethod with an instance of
MyOtherDelegate, like this...
anotherDel += new MyOtherDelegate(MyOverloadedMethod);
... the C# compiler will register only the particular overload with a matching signature. Of the
following two overloads, only the first would be registered with the anotherDel instance of
the MyOtherDelegate type:
// requires no parameters - so can be registered with a MyOtherDelegate
// instance.
private int MyOverloadedMethod()
{
// method body here.
}
// requires a string parameter - so cannot be registered with a
MyOtherDelegate instance.
private int MyOverloadedMethod(string someString)
{
// method body here.
}
A single delegate cannot selectively register or call both (multiple) overloads. If you need to
call both (multiple) overloads, then you would need additional delegate types -- one delegate
C# Event Implementation Fundamentals, Best Practices and Conventions
Seite 3 von 10
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
type per signature. Your application-specific logic would then determine which delegate to
invoke, and therefore which overload is called (by the delegate with the corresponding
signature).
Why Delegates?
If this is your first introduction to delegates, you may be wondering, "Why bother? It's just
simpler to call the method directly -- so what is the benefit of going through a delegate?"
Necessary Indirection
A brief answer (to the "why bother?" question above) is that the code we write or components
we use cannot always "know" which specific method to call at a particular point in time. So,
one important perspective of delegates is that they provide a way for .NET components to call
your code -- without the .NET component having to know anything about your code beyond
the method signature (as mandated by the delegate type). For example, .NET Framework
components, like the Timer component, frequently need to execute code that you write.
Because the Timer component cannot possibly know which specific method to call, it
specifies a delegate type (and therefore signature of a method) to be invoked. Then you
connect your method -- with the requisite signature -- to the Timer component by registering
your method with a delegate instance of the delegate type expected by the Timer component.
The Timer component can then run your code by invoking the delegate which, in turn, calls
your method. Note that the Timer component still knows nothing about your specific method.
All the Timer component knows about is the delegate. The delegate, in turn, knows about
your method because you registered your method with that delegate. The end result is that the
Timer component causes your method to run, but without knowing anything about your
specific method.
Just like the Timer component example above, we can make use of delegates in a way that
enables us to write our code without our code having to "know" the specific method that will
ultimately be called at a specific point. Rather than calling a method at that point, our code
can invoke a delegate instance which, in turn, calls any methods that are registered with the
delegate instance. The end result is that a compatible method is called even though the
specific method to be called was not written directly into our code.
Synchronous and Asynchronous Method Invocation
All delegates inherently provide for both synchronous and asynchronous method invocation.
So, another common reason to call methods via delegate instances is to invoke methods
asynchronously -- in which case the called method runs on a separate thread pool thread.
Event Foundation
As you'll see later in this article, delegates play an integral role in the implementation of
events in the .NET Framework. In brief, delegates provide a necessary layer of indirection
between event publishers and their subscribers. This indirection is necessary in order to
maintain a clean separation between the publisher and subscriber(s) meaning that
subscribers can be added and removed without the publisher needing to be modified in any
way. In the case of event publication, the use of a delegate makes it possible for an event
C# Event Implementation Fundamentals, Best Practices and Conventions
Seite 4 von 10
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
publisher to know nothing about any of its subscribers while still broadcasting events and
associated event data to any/all subscribers.
Delegate Internals
Declaring a Delegate Results in A New Class Being Created
A delegate declaration that you write is sufficient to define an entire and new delegate class.
The C# compiler takes your delegate declaration and inserts a new delegate class in the output
assembly. The name of that new class is the name of the delegate type you supply in your
delegate declaration. The signature you specify in your delegate declaration becomes the
signature of the methods in the new class used to call any/all of the delegate's referenced
methods (specifically the Invoke and BeginInvoke methods). This new class extends
(inherits) System.MulticastDelegate. So most of the methods and properties available in
your new delegate class come from System.MulticastDelegate. The Invoke, BeginInvoke,
and EndInvoke methods are inserted by the C# compiler when it creates the new class in the
output assembly (these are the methods you can call to cause the delegate to invoke any/all
referenced methods -- Invoke for synchronous invocation, and BeginInvoke and EndInvoke
used in asynchronous invocations).
The new class created from your delegate declaration can be understood as being a completed
and full-blown MulticastDelegate implementation that has the type name you supplied in
your delegate declaration, and is capable of calling methods with the specific signature that
you also supplied in your delegate declaration.
As an example, when the C# compiler encounters the following delegate declaration...
public delegate string MyFabulousDelegate(int myIntParm);
... the compiler inserts a new class named MyFabulousDelegate into the output assembly.
The Invoke, BeginInvoke, and EndInvoke methods of the MyFabulousDelegate class
include the int parameter and returned string value in their respective method signatures.
It should be noted that MulticastDelegate is a special class in that compilers can derive
from it, but you cannot derive from it explicitly. Your use of the C# delegate keyword and
associated syntax is how you instruct the C# compiler to extend MulticastDelegate for your
purposes.
Meaning of Multicast
The meaning of "multicast" in System.MulticastDelegate is that the delegate is capable of
holding references to multiple methods -- not just one method. In the case of delegate
instances that hold references to multiple methods, all referenced methods are called when the
delegate instance is invoked.
Finally, C and C++ programmers will recognize that delegates are similar to C-style function
pointers. An important difference, though, is that a delegate is not simply a pointer to a raw
memory address. Instead, delegate instances are type-safe objects that are managed by
the .NET CLR and that specifically reference one or more "methods" (as opposed to
memory addresses.
C# Event Implementation Fundamentals, Best Practices and Conventions
Seite 5 von 10
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
The Relationship Between Delegates and Events
Events in .NET programming are based on delegates. Specifically, an event can be understood
as providing a conceptual wrapper around a particular delegate. The event then controls
access to that underlying delegate. When a client subscribes to an event, the event ultimately
registers the subscribing method with the underlying delegate. Then, when the event is raised,
the underlying delegate invokes each method that is registered with it (the delegate). In the
context of events, then, delegates act as intermediaries between the code that raises events and
the code that executes in response thereby decoupling event publishers from their subscribers.
Events do not, by themselves, maintain a list of subscribers. Instead, events control access to
some underlying list of subscribers and that list is typically implemented as a delegate
(although other list-type objects or collections can serve in place of a delegate).
Event Handlers (in general)
A delegate that exists in support of an event is referred to as an "event handler". To be clear,
an "event handler" is a delegate, although delegates are frequently not event handlers.
This article uses the expression, "event handler," only in reference to the delegate, while using
the expression, "event handling method," in reference to any method registered with the
delegate.
Custom Event Handlers
You can define your own event handlers (delegates), or you can use one of the event handlers
provided by the .NET Framework (i.e., System.EventHandler, or the generic
System.EventHandler<TEventArgs>). The following sample event declaration make use of
a custom event handler rather than using a Framework-provided event handler.
Consider the following:
Line 1: public delegate void MyDelegate(string whatHappened);
Line 2: public event MyDelegate MyEvent;
•
Line 1 declares a delegate type for which any method can be assigned -- provided that
the method returns void and accepts a single string argument.
Line 2 declares an event in terms of the delegate type. Notice that the event (which is named
MyEvent) is declared very much like a method declaration -- but with its data type specified as
the delegate type:
•
•
•
public scope specifying that objects outside of our class can subscribe to the event.
event keyword used to define the event.
MyDelegate data type of the event (this is the custom delegate type defined in Line
1.)
•
MyEvent
name of the event.
The delegate declared in Line 1 is just an ordinary delegate (as are all delegates), and can be
used for any purpose delegates can fulfill. Line 2 (i.e., the usage of the delegate type) is what
turns that delegate into an event handler. In order to communicate that a particular delegate
C# Event Implementation Fundamentals, Best Practices and Conventions
Seite 6 von 10
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
type is being used as an event handler, a naming convention has emerged whereby the
delegate type name ends with "Handler" (more on this later).
Standardized Event Handlers
While you can create your own event handlers (and sometimes you might need to), you
should use one of the EventHandler delegates provided by the .NET Framework in cases
where one of the Framework's event handlers would work with your particular event
implementation. Many events make use of event handlers that can have common or identical
signatures. So, rather than clutter your source code with many delegates that differ only by
type name, you can/should make use of the built-in event handlers, as doing so reduces the
amount of code you would need to write and maintain, and makes your code more easily
understood. If someone reading your code sees you are basing an event on the
System.EventHandler delegate, for example, then they automatically know a lot about your
event implementation without having to look further.
The Generic System.EventHandler<TEventArgs> Delegate
The declaration of this built-in delegate enforces the constraint that the type, TEventArgs, be
of type System.EventArgs (including, of course, subclasses thereof):
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)
where TEventArgs : EventArgs;
Now suppose you want to strongly type the sender, rather than having it typed as object.
You can leverage generics to create your own generic event handler:
public delegate void MyGenericEventHandler<T, U>(T sender,
U u) where U : EventArgs;
You can then use this custom generic event handler to additionally specify a type-safe sender
parameter (i.e., thereby limiting the type of object that can be communicated as having raised
the event):
public event MyGenericEventHandler<MyPublisher, MyEventArgs> MyEvent;
The intent here would be that this event will only be raised by objects of type MyPublisher.
Subscribers to the event would therefore be able to subscribe only to events published by the
MyPublisher class.
Event Arguments (EventArgs)
Event arguments -- sometimes referred to as "event args" -- constitute the data sent by the
publisher of an event to the subscribers during the raising of the event. Presumably this data is
relevant to the occurrence of the event. For example, when a "file was just renamed" event is
raised, the event arguments would likely include the name of the file before the name change,
as well as the name of the file after the name was changed. The event handling methods can
read the event arguments (referred to as "event data") to learn more about the event
occurrence.
C# Event Implementation Fundamentals, Best Practices and Conventions
Seite 7 von 10
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
The Role of System.EventArgs
You have two basic alternatives for including event arguments with your events.
1. You can encapsulate all event arguments as properties of a class that derives from
System.EventArgs. At runtime, an instance of that class is then sent to the event
subscribers when the event is raised. The event subscribers read the event arguments
as properties of that class.
2. You can avoid the use of System.EventArgs and, instead, declare individual event
arguments -- much as you would include arguments in a method declaration. This
approach is discouraged.
The first alternative listed above is strongly encouraged, and support for it is built into
the .NET Framework through the System.EventArgs class. Events implemented in .NET
Framework components, by convention, provide their event arguments as instances
System.EventArgs, or as event-specific subclasses of System.EventArgs.
Some events carry no data. In these cases, System.EventArgs is used as a placeholder,
primarily for purposes of keeping one consistent event handler signature across all events
regardless of whether the events carry data or carry no data. In cases of events with no data,
the event publisher sends the value, System.EventArgs.Empty, during the raising of the
event.
Event Declaration Syntax
Event Declaration Syntax Alternatives
The event keyword is used to formally declare an event. The C# compiler will translate the
declaration into the following three components in the output assembly.
1. Privately scoped event handler (or a functionally equivalent data structure). The
delegate is privately scoped in order to prevent external code from invoking the event,
and thereby preserving encapsulation.
2. publicly scoped Add method; used to add subscribers to the private event handler.
3. publicly scoped Remove method used to remove subscribers from the private event
handler.
Field-like syntax: public event TheEventHandler MyEvent;
The field-like syntax declares the event in one or two lines of code (one line for the event,
another for the asso ciated event handler -- if/when not using a built-in EventHandler
delegate). Alternatively, there exists a property-like syntax (details see full article).
Event Raising Code
For each event, the publisher should include a protected virtual method that is responsible for
raising the event. This will allow subclasses to [more] easily access base class events. Of
course the recommendation to make this method protected and virtual applies only to non
static events in unsealed classes.
protected virtual void OnMailArrived(MailArrivedEventArgs)
C# Event Implementation Fundamentals, Best Practices and Conventions
Seite 8 von 10
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
{ // Raise event here}
Once an event and any associated delegate and publishing method have been defined, the
publisher will need to raise the event. Raising the event should generally be a two step process.
The first step would be to check to see if there are any subscribers. The second step is to raise
the event, but only if there are any subscribers.
Any unhandled exceptions raised in the event handling methods in subscribers will be
propagated to the event publisher. The raising of the event should therefore be attempted only
within a try/catch block:
public void RaiseTheEvent(MyEventArgs eventArgs)
{
try
{
MyEventHandler handler = MyEvent;
if (handler != null)
{
handler (this, eventArgs)
}
}
catch
{
// Handle exceptions here
}}
Events can have multiple subscribers each of which is called, in turn, by the event handler
(delegate) when the event handler is invoked by the [handler (this, eventArgs)] line.
The event handler used in the above block of code would stop iterating over it's invocation list
(of subscribed event handling methods) when the first unhandled exception is raised by a
subscriber. So, if there were 3 subscribers, for example, and the 2nd one threw an unhandled
exception when invoked by the delegate, then the 3rd subscriber would never receive the
event notification. If you want for every subscriber to receive the event notification even if
other subscribers throw unhandled exceptions, then you could use the following logic which
explicitly loops through the event handler's invocation list:
public void RaiseTheEvent(MyEventArgs eventArgs)
{
MyEventHandler handler = MyEvent;
if (handler != null)
{
Delegate[] eventHandlers = handler.GetInvocationList();
foreach (Delegate currentHandler in eventHandlers)
{
MyEventHandler currentSubscriber = (MyEventHandler)currentHandler;
try
{
currentSubscriber(this, eventArgs);
}
catch (Exception ex)
{
// Handle exception here.
}
}
}
}
C# Event Implementation Fundamentals, Best Practices and Conventions
Seite 9 von 10
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Event Subscriber Registration and Unregistration
By design, the publisher of an event has absolutely no knowledge of any of the subscribers.
Consequently, it is the job of subscribers to register or unregister themselves with the
publisher of an event.
Registering A Subscriber
In order to subscribe to an event, the subscriber needs three things:
1. a reference to the object publishing the event of interest
2. an instance of the delegate upon which the event is defined
3. a method that will be called by the publisher when it raises the event
The subscriber then registers its event handler (delegate) instance with the publisher, like this:
thePublisher.EventName += new
MyEventHandlerDelegate(EventHandlingMethodName);
In the above line...
is the reference to the object that will raise the event of interest. Notice
how the event, EventName, is accessed as if it were a public property of
thePublisher.
•
thePublisher
•
The += operator is used to add the delegate instance to the invocation list of the event
handler in the publisher. Remember, multiple subscribers may register with the event.
Use the += operator to append the current subscriber to the underlying delegate's
invocation list.
MyEventHandlerDelegate is a reference the particular event hander delegate to be
used (if not one of the built-in EventHandler delegates).
Finally EventHandlingMethodName supplies the name of the method in the
subscribing class that is to be called upon the raising of the event.
•
•
WARNING: Do not use the = operator when registering an event subscriber with a publisher.
Doing so would replace any/all currently registered event subscribers with the current
subscriber. Instead, be sure to use the += operator to cause the current subscriber to be
appended to the event handler's invocation list.
Unregistering A Subscriber
A subscriber can unregister from the publisher, like this:
thePublisher.EventName -=
EventHandlerDelegate(EventHandlingMethodName);
The -= operator is used to remove the delegate instance from the invocation list in the
publisher.
Subscribers are automatically unregistered when an object is disposed
not already explicitly unregistered from the event.
if the subscriber was
C# Event Implementation Fundamentals, Best Practices and Conventions
Seite 10 von 10
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Understanding Routed Events and Commands In WPF
Brian Noyes, September 2008
http://msdn.microsoft.com/en-us/magazine/cc785480.aspx
One of the most daunting things about getting up to speed on Windows® Presentation
Foundation (WPF) is that there are so many new constructs to master. Even simple things like
Microsoft® .NET Framework properties and events have new counterparts in WPF with
added capabilities and associated complexity—specifically dependency properties and routed
events. Then there is all the brand new stuff, such as animations, styling, control templates,
and routed commands. There is a lot to learn.
In this article I am going to focus on two very important items in the list of new WPF
elements to master. These items—routed events and routed commands—are related to each
other. They form the basis for communication among the various parts of your user
interface—whether those parts are individual controls on one big Window class or whether
they are controls and their supporting code in separate, decoupled parts of your user interface.
For this article I am assuming you are already familiar with the fundamentals of WPF, such as
how to construct a UI using built-in WPF controls and declaring the layout of your UI in
XAML.
Routed Events Overview
When you are first getting started with WPF, you will likely use routed events without even
knowing you are using them. For example, if you add a button to your window in the Visual
Studio® designer and name it myButton and then double-click on it, the Click event will get
hooked up in your XAML markup and an event handler for the Click event will be added to
the codebehind of your Window class. This should feel no different than hooking up events in
Windows Forms and ASP.NET. It is actually a little closer to the coding model for ASP.NET
but more like the runtime model of Windows Forms. Specifically, in your XAML markup for
the button, you end up with code like this:
<Button Name="myButton" Click="myButton_Click">Click Me</Button>
The XAML declaration for hooking up an event looks just like a property assignment in
XAML but results in a normal event hookup on the object that specified the event handler.
This hookup actually happens in a compile-time-generated partial class for your window. To
see this, go to the constructor for your class, right-click on the InitializeComponent method
call, and select Go To Definition from the context menu. The editor will display a generated
code file (with a naming convention of .i.g.cs or .i.g.vb) containing the code that is normally
generated at compile time. Scroll down in the displayed partial class to the Connect method
where you will see the following:
#line 6 "..\..\Window1.xaml" this.myButton.Click += new
System.Windows.RoutedEventHandler( this.myButton_Click);
This partial class is generated from the XAML at compile time and contains those elements of
the XAML that need design-time compilation. Most of your XAML ends up as a binaryembedded resource in your compiled assembly and is merged at run time with the compiled
code from the binary representation of your markup.
If you take a look at the codebehind for the window, the Click handler looks like this:
private void myButton_Click( object sender, RoutedEventArgs e) { }
So far this looks like any other .NET event hookup—you have an explicitly declared delegate
hooked up to an event on an object, and the delegate points to a handling method. The only
thing to tip you off that you are using routed events is the type of the event argument for the
Understanding Routed Events and Commands In WPF
Seite 1 von 12
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Click event, which is RoutedEventArgs. What is so special about routed events then? To
understand that, you first need to understand the elemental composition model of WPF.
WPF Element Trees
If you start with a new window in a project and drag a button onto the window in the designer,
you end up with an element tree in the XAML that looks like this (attributes omitted for
clarity):
<Window> <Grid> <Button/> </Grid> </Window>
Each of these elements represents a runtime instance of a corresponding .NET type, and the
declared hierarchy of elements forms what is called the logical tree. In addition, many
controls in WPF are either ContentControls or ItemsControls, meaning they can have child
elements. For example, a Button is a ContentControl, and it can have a complex child element
as its content. You could expand the logical tree, as you see here:
<Window> <Grid> <Button> <StackPanel> <Image/> <TextBlock/> </StackPanel>
</Button> </Grid> </Window>
This results in a UI like that shown in Figure 1.
Figure 1 Simple Window with Button Content
As you can imagine, the tree could start to take on multiple
branches (another Button in the Grid), and the logical tree
can grow significantly in complexity. The thing to realize
about WPF elements in the logical tree is that what you see
is not really what you get at run time. Each of those elements
usually expands to a more complicated tree of visual elements at run time. In this example,
the logical tree of elements expands to the visual tree of elements shown in Figure 2.
Figure 2 Simple Window Visual Tree
I used a tool called Snoop (blois.us/Snoop) to see the
elements of the visual tree shown in Figure 2. You
can see that the window (EventsWindow) actually
wraps its content in a Border and an AdornerDecorator and presents the content inside of that with
a ContentPresenter. The button does something
similar, wrapping its content in a ButtonChrome
object and presenting the content with a
ContentPresenter.
When I click on my button, I may not actually be clicking on the Button element at all; I may
be clicking on a child element in the visual tree, possibly even one that is not shown in my
logical tree (such as the ButtonChrome). For example, say that I click the mouse on top of the
image inside my button. The click really manifests itself initially as a MouseLeftButtonDown
event inside the Image element. But somehow that needs to get translated into a Click event at
the Button level. This is where the routing in routed events comes in.
Understanding Routed Events and Commands In WPF
Seite 2 von 12
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Event Routing
Understanding a little about the logical and visual trees is important because routed events get
routed based primarily on the visual tree. Routed events support a RoutingStrategy of Bubble,
Tunnel, or Direct.
Bubble is the most common and means that an event will bubble (propagate) up the visual
tree from the source element until either it has been handled or it reaches the root element.
This allows you to handle an event on an object further up the element hierarchy from the
source element. For example, you could attach a Button.Click handler on the enclosing Grid
element instead of directly on the button itself. Bubble events just have names that indicate
their action (for example, MouseDown).
Tunnel events go in the other direction, starting at the root element and traversing down the
element tree until they are handled or reach the source element for the event. This allows
upstream elements to intercept the event and handle it before the event reaches the source
element. Tunnel events have their names prefixed with Preview by convention (such as
PreviewMouseDown).
Direct events behave like normal events in the .NET Framework. The only potential handler
for the event is a delegate that is hooked up to the event.
Usually if a Tunnel event is defined for a particular event, there is a corresponding Bubble
event. In that case, the Tunnel event fires first, starting at the root and working its way down
to the source element looking for a handler. Once it has been handled or has reached the
source element, the Bubble event is fired, working its way up from the source element and
looking for a handler. A Bubble or Tunnel event does not stop its routing just because an
event handler is called. If you want to stop the bubbling or tunneling process, you mark the
event as handled in your event handler using the event arguments you are passed:
private void OnChildElementMouseDown(object sender, MouseButtonEventArgs e)
{ e.Handled = true; }
Once your handler marks an event as handled, it will not be raised to any more handlers. Well,
that is only partially true. In reality, event routing continues behind the scenes, and you can
explicitly hook up event handlers in code with an override of the UIElement.AddHandler
method that has an additional flag to effectively say, "Call me even if the event has been
marked as handled." You specify that flag with a call like the following:
m_SomeChildElement.AddHandler(UIElement.MouseDownEvent,
(RoutedEventHandler)OnMouseDownCallMeAlways,true);
The first parameter to AddHandler is the RoutedEvent you want to handle. The second is a
delegate to the event-handling method (which will need to have the correct signature for the
event's delegate). The third parameter indicates whether you want to be notified even if
another handler has marked the event as handled. The element on which you call AddHandler
is the one that will be watching for the event to flow by during routing.
Routed Events and Composition
Let's walk through how the Button.Click event comes into being to drive home why all this is
important. As I mentioned before, a user will initiate a Click event with a
MouseLeftButtonDown event on some child element in the visual tree of your Button, such as
the Image in the previous example.
When the MouseLeftButtonDown event happens inside the Image element, a
PreviewMouseLeftButtonDown event starts at the root and tunnels down to the Image. If no
handlers set the Handled flag to true for the preview event, the MouseLeftButtonDown event
then starts bubbling up from the Image element until it gets to the Button. The button handles
that event, sets the Handled flag to true, and raises its own Click event. The sample code for
this article includes an application with handlers hooked up throughout the routing chain to
help you visualize this process.
Understanding Routed Events and Commands In WPF
Seite 3 von 12
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
The implications are quite powerful. For example, if I choose to replace the default button
appearance by applying a control template that contains an Ellipse element, I don't have to do
anything to ensure that clicks outside the Ellipse don't fire the Click event. Clicks just outside
the edge of the Ellipse will still be inside the rectangular bounds of my button, but Ellipse has
its own hit detection for MouseLeftButtonDown, and the empty portions of the button outside
the Ellipse do not.
So only clicks inside the Ellipse raise the MouseLeftButtonDown event. It is still handled by
the Button class to which the template is attached, so you will get predictable behavior from
even your customized button. This is also an extremely important concept to remember when
writing your own custom composite controls because you will likely need to do things similar
to what Button is doing to handle events from the child elements that get placed inside your
control.
Attached Events
In order to enable elements to handle events that are declared in a different element, WPF
supports something called attached events. Attached events are routed events that support a
hookup in XAML on elements other than the type on which the event is declared. For
example, if you want the Grid element to listen for a Button.Click event to bubble past, you
would simply hook it up like the following:
<Grid Button.Click="myButton_Click"> <Button Name="myButton" >Click
Me</Button> </Grid>
The resulting code in the compile-time-generated partial class now looks like this:
#line 5 "..\..\Window1.xaml"
((System.Windows.Controls.Grid)(target)).AddHandler( System.Windows.Control
s.Primitives.ButtonBase.ClickEvent, new
System.Windows.RoutedEventHandler(this.myButton_Click));
Attached events simply give you a little more flexibility in where you hook up your event
handlers. But if the elements are contained in the same class (as in this example), it may not
be apparent what difference it makes because, in either case, the handling method is still just a
method on the Window class.
It matters in two ways. First, event handlers are called based on where the handling element is
in the bubbling or tunneling element chain. Second, this lets you do something like handle
events from objects that may be encapsulated down inside a control you are using. For
example, you could handle Button.Click events like those shown on the Grid, but those
Button.Click events could be bubbling out from inside of a user control contained in your
window. Not all events are declared as attached events. In fact, most are not. But attached
events can be quite handy when you need to do event handling somewhere else besides the
control's source.
Routed Commands Overview
Now that you have seen routed events, you are ready to understand routed commands. WPFrouted commands give you a specific mechanism for hooking up UI controls such as toolbar
buttons and menu items to handlers without introducing a lot of tight coupling and repetitive
code into your application. Routed commands give you three main things on top of normal
event handling:
•
•
Routed command source elements (invokers) can be decoupled from command targets
(handlers)—they do not need direct references to one another, as they would if they
were linked by an event handler.
Routed commands will automatically enable or disable all associated UI controls when
the handler indicates the command is disabled.
Understanding Routed Events and Commands In WPF
Seite 4 von 12
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
•
Routed commands allow you to associate keyboard shortcuts and other forms of input
gestures (ink gestures, for example) as another way to invoke the command.
In addition, a specific flavor of routed command, the RoutedUICommand class, adds the
ability to define a single Text property to be used for the text prompting of any controls that
are invokers for the command. The Text property can also be localized more easily than
visiting each associated invoker control.
To declare a command on an invoker, you simply set a Command property on the control that
will fire the command:
<Button Command="ApplicationCommands.Save">Save</Button>
The Command property is supported by MenuItem, Button, RadioButton, CheckBox,
Hyperlink, and a number of other controls.
For an element that you want to act as a command handler, you set up a CommandBinding:
<UserControl ...> <UserControl.CommandBindings> <CommandBinding
Command="ApplicationCommands.Save" CanExecute="OnCanExecute"
Executed="OnExecute"/> </UserControl.CommandBindings> ... </UserControl>
The CanExecute and Executed properties of a command binding point to methods in the
codebehind of the declaring class that are called during the command-handling process. The
important thing here is that the command invoker does not need any knowledge of or
reference to the command handlers, and a handler does not have to know what element is
going to invoke the command.
CanExecute is called to determine whether or not the command should be enabled. To enable
the command, set the CanExecute property of the event arguments to true, as shown here:
private void OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{ e.CanExecute = true; }
A command is also enabled if there is a command handler with a defined Executed method
but no CanExecute method (CanExecute is implicitly true in that case). The Executed method
is where you take whatever action is appropriate based on that command being invoked. This
may be saving a document, submitting an order, sending an e-mail, or some other action with
which the command is associated.
Routed Command in Action
To make this more concrete and to quickly see the benefits of routed commands, let's look at a
simple example. In Figure 3 you see a simple UI with two input textboxes and a toolbar
button for performing a Cut action on the text in the textboxes.
Figure 3 Simple App with Cut Command
Toolbar Button
To get this hooked up using events, you would
need to define a Click handler for the toolbar
button, and that code would need references to
the two textboxes. You would have to determine
which textbox has the focus and call appropriate
clipboard operations based on the text selection
in the control. You would also have to worry about enabling and disabling the toolbar button
at appropriate times based on where the focus is and whether anything was selected in the
textbox. Ugly, messy, tightly coupled code.
It doesn't sound too bad for this simple form, but what if those textboxes are now down inside
a user control or a custom control and your window codebehind doesn't have direct access to
them? You would either have to expose an API at your user control boundary to make
Understanding Routed Events and Commands In WPF
Seite 5 von 12
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
hooking things up from the container possible or expose the textboxes publicly—neither
approach is ideal.
With commands, all you need to do is set the Command property on the toolbar button to the
Cut command that is defined in WPF and you are done:
<ToolBar DockPanel.Dock="Top" Height="25"> <Button
Command="ApplicationCommands.Cut"> <Image Source="cut.png"/> </Button>
</ToolBar>
You could now run the app and see that the toolbar button is initially disabled. After you
select some text in one of the textboxes, the toolbar button becomes enabled and, if you click
it, the text is actually cut to the clipboard. And it would work for any textbox anywhere in
your UI. Wow—pretty cool, huh?
What is happening here is that the TextBox class implementation has a built-in command
binding for the Cut command and encapsulates the clipboard handling for that command (and
Copy and Paste as well) for you. So how does the command only get invoked on the in-focus
textbox, and how does the message get to the textbox to tell it to handle the command? That is
where the routed part of routed commands comes into play.
Command Routing
The difference between routed commands and routed events is in how the command gets
routed from the command invoker to the command handler. Specifically, routed events are
used under the covers to route messages between the command invokers and the command
handlers (through the command binding that hooks it into the visual tree).
There could be a many-to-many relationship here, but only one command handler will
actually be active at any given time. The active command handler is determined by a
combination of where the command invoker and command handler are in the visual tree, and
where the focus is in the UI. Routed events are used to call the active command handler to ask
whether the command should be enabled, as well as to invoke the command handler's
Executed method handler.
Usually, a command invoker looks for a command binding between its own location in the
visual tree and the root of the visual tree. If it finds one, the bound command handler will
determine whether the command is enabled and will be called when the command is invoked.
If the command is hooked up to a control inside a toolbar or menu (or, more generally, a
container that sets FocusManager.IsFocusScope = true), then some additional logic runs that
also looks along the visual tree path from the root to the focus element for a command binding.
In the simple application from Figure 3, what happens is this: because the Cut command
button is in a toolbar, CanExecute and Execute are handled by the TextBox instance that has
the focus. If the textboxes in Figure 3 were contained within a user control, then you would
have an opportunity to set up a command binding on the window, the user control that
contains the Grid, the Grid that contains the textboxes, or on the individual textboxes.
Whichever textbox has the focus will determine the end of the focus path originating from the
root.
An important thing to understand about the routing of WPF routed commands is that once a
single command handler is invoked, then no other handlers will be called. So if the user
control handles the CanExecute method, the TextBox CanExecute implementation will no
longer be called.
Defining Commands
Both the ApplicationCommands.Save and the ApplicationCommands.Cut commands are two
of many commands provided by WPF. The five built-in command classes in WPF along with
some examples of the commands they contain are shown in Figure 4.
Figure 4 WPF Command Classes
Understanding Routed Events and Commands In WPF
Seite 6 von 12
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Command Class
Example Commands
ApplicationCommands Close, Cut, Copy, Paste, Save, Print
NavigationCommands BrowseForward, BrowseBack, Zoom, Search
EditingCommands
AlignXXX, MoveXXX, SelectXXX
MediaCommands
Play, Pause, NextTrack, IncreaseVolume, Record, Stop
ComponentCommands MoveXXX, SelectXXX, ScrollXXX, ExtendSelectionXXX
The XXX indicates a collection of operations such as MoveNext and MovePrevious. The
commands in each class are defined as public static (Shared in Visual Basic®) properties so
that you can easily hook them up. You can define your own custom commands easily by
following the same approach. I'll show an example of this a little later.
You can also use these commands with a shorthand notation, like the following:
<Button Command="Save">Save</Button>
When you use this shortened version, a type converter in WPF will try to locate the named
command against the collection of built-in commands. The net result in this case is exactly the
same. I prefer to use the long-named versions to make the code more explicit and
maintainable. Then there is no ambiguity as to where that command is defined. Even the builtin commands have some duplication between the EditingCommands and
ComponentCommands classes.
Command Plumbing
Routed commands are a specific implementation of the ICommand interface defined by WPF.
ICommand has the following definition:
public interface ICommand { event EventHandler CanExecuteChanged; bool
CanExecute(object parameter); void Execute(object parameter); }
The built-in WPF command types are RoutedCommand and RoutedUICommand. Both of
these classes implement the ICommand interface and use routed events I described earlier to
do the routing.
A command invoker is expected to call CanExecute to determine whether to enable or disable
any associated command invocation code. The command invoker can determine when to
invoke that method by subscribing to the CanExecuteChanged event. In the RoutedCommand
class, CanExecuteChanged is fired based on changes of state or focus in the UI. When the
command is invoked, the Executed method is called and gets dispatched to the handler
through a routed event along the visual tree.
Classes that support the Command property, such as the ButtonBase class, implement the
ICommandSource interface:
public interface ICommandSource { ICommand Command { get; } object
CommandParameter { get; } IInputElement CommandTarget { get; } }
The Command property associates the invoker with the command it will invoke.
CommandParameter allows the invoker to pass some data along with the invocation of the
command. The CommandTarget property lets you override the default routing based on the
focus path and tell the commanding system to use the specified element as the handler of the
command, instead of leaving it up to the routed event and focus-based determination of the
command handler.
Routed Command Challenges
Routed commands work nicely for simple user interface scenarios, hooking up toolbar and
menu items, and handling those things that are inherently coupled to the keyboard focus (such
as clipboard operations). Where routed commands are insufficient, however, is when you start
building complex user interfaces, where your command handling logic is in supportng code
for your view definitions and your command invokers are not always inside of a toolbar or
Understanding Routed Events and Commands In WPF
Seite 7 von 12
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
menu. This often comes up when using UI composition patterns such as Model View
Controller, or MVC (msdn.microsoft.com/magazine/cc337884), Model View Presenter, or
MVP (msdn.microsoft.com/magazine/cc188690), or Presentation Model, which is also known
as Model View ViewModel in WPF circles (msdn.microsoft.com/library/cc707885).
The problem when you get into that arena is that the enabling and handling logic for a
command may not be part of the visual tree directly; rather, it may be located in a presenter or
presentation model. Additionally, the state that determines whether the command should be
enabled may have no relation to where the command invokers and views are in the visual tree.
You may also have scenarios where more than one handler is valid at a given time for a
particular command.
To see where you might get yourself in trouble with routed commands, take a look at Figure
5. It is a simple window that contains a couple of user controls that represent views in an
MVP or MVC pattern. The main window contains a File menu and toolbar with Save
command buttons in them. The main window also has an input textbox at the top of the
window, along with a Button that has its Command set to the Save command.
Figure 5 Composite User
Interface (Click the image
for a larger view)
Tip: Hooking Up Anonymous
Methods
In the code in Figure 6 I use
a trick learned from my
colleague Juval Lowy—
hooking up an empty
anonymous method to the
delegate in its declaration:
Action<string>
m_ExecuteTargets =
delegate { };
By doing so, you never have
to check for null before invoking the delegate because there will always be one no-op
subscriber in its invocation list. You also avoid a potential race condition with unsubscribing
in a multithreaded environment if you were doing null checking instead.
For more details on that trick, see Programming .NET Components, Second Edition, by Juval
Lowy.
The rest of the UI is provided by two views, and each is an instance of a simple user control.
The border color has been set differently for each user control instance in order to make it
easy to see what portion of the UI they provide. Each of the user control instances has a Save
button that has its Command property set to the Save command.
The challenge introduced by routed commands being tied to a location in the visual tree
becomes apparent in this simple example. In Figure 5, the window itself does not have a
CommandBinding for the Save command. However, it does contain two invokers (the menu
and toolbar) for that command. In this situation, I don't want the top-level window to have to
know what to do when the command is invoked. I want to leave it up to the child views,
represented by the user controls, to handle the command. The user control class in this
example has a CommandBinding for the Save command, and that CommandBinding returns
true for CanExecute.
Understanding Routed Events and Commands In WPF
Seite 8 von 12
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
However, in Figure 5 you can see that while the focus is at the window level in the textbox at
the top, the command invokers at this level are disabled. Also, the Save buttons in the user
controls are enabled even though the focus is not yet inside the user control.
If you change the focus to one of the textboxes inside one of the user control instances, then
the command invokers in the menu and toolbar become enabled. But the Save button in the
window itself does not become enabled. In fact, in this case there is no way to get the Save
button next to the textbox at the top of the window to enable with normal routing.
The reason again has to do with the location of the individual controls. With no command
handlers at the window level, while the focus is outside of the user controls, there is no
command handler up the visual tree or on the focus path to enable the controls that are hooked
up as command invokers. The command is therefore disabled by default as far as those
controls are concerned. However, for the command invokers inside the user controls, the
command is enabled because the handler is up the visual tree from them.
Once you switch the focus to a textbox inside one of the user controls, the user control, which
is between the window and the textbox on the focus path, provides the command handler that
enables the command for the toolbar and menu, because they check the focus path as well as
the path from their location in the visual tree to the root. And there is no way to enable the
button at the window level because it will never have a handler between it and the root.
If trying to follow all that visual tree and focus-path mumbo jumbo made your head hurt for
this simple little example, imagine what you will have to go through to reason out command
enabling and invoking for a seriously complicated UI with command invokers and handlers in
many different locations in the visual tree. Let's just say the movie Scanners and exploding
heads come to mind.
Avoiding Command Problems
To avoid the potential visual tree location problems with routed commands, you'll need to
keep things simple. You will generally need to make sure your command handlers are on the
same element or further up the visual tree from the element that will invoke the command.
One way to do this is to inject a command binding at the window level by using the
CommandManager.RegisterClassCommandBinding method from the control contributing a
command handler.
The exception to this is if you implement a custom control that itself accepts keyboard focus
(like a textbox). In that case, if you want to embed command handling on the control itself
and that command handling is only relevant when the focus is on your control, then you can
do so and it will work out just like the Cut command example shown earlier.
You can also overcome focus issues by explicitly specifying a command handler through the
CommandTarget property. For example, for the window-level Save button that was never
enabled in Figure 5, you could change its command hookup to the following:
<Button Command="Save" CommandTarget="{Binding ElementName=uc1}" Width="75"
Height="25">Save</Button>
In this code, the Button specifically sets its CommandTarget to a UIElement instance that has
a command handler in it. In this case, it is specifying the element named uc1, which happens
to be one of the two user control instances in the example. Because that element has a
command handler that always returns CanExecute = true, the Save button at the window level
becomes always enabled and will only call that control's command handler, regardless of
where the invoker is relative to the command handler.
Going beyond Routed Commands
As a result of the limitations of routed commands, a number of companies building complex
UIs with WPF have started using custom ICommand implementations that allow them to
Understanding Routed Events and Commands In WPF
Seite 9 von 12
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
provide their own routing mechanisms, particularly ones that are not tied to the visual tree and
that can support multiple command handlers.
Creating a custom command implementation isn't hard. You implement the ICommand
interface on a class, provide a way for command handlers to hook up, and then do the routing
when the command is invoked. You also must decide what criteria you will use for
determining when to raise the CanExecuteChanged event.
A good starting point for creating a custom command is to use delegates. A delegate already
supports invocation of a target method, and it also supports multiple subscribers.
Figure 6 shows a custom command class called StringDelegateCommand that uses delegates
to allow multiple handlers to hook up. It supports passing a string argument to the handlers,
and it will use the CommandParameter of the invoker to determine what message is passed to
the handlers.
Figure 6 Custom Command Class
public class StringDelegateCommand : ICommand
{
Action<string> m_ExecuteTargets = delegate { };
Func<bool> m_CanExecuteTargets = delegate { return false; };
bool m_Enabled = false;
public bool CanExecute(object parameter)
{
Delegate[] targets = m_CanExecuteTargets.GetInvocationList();
foreach (Func<bool> target in targets)
{
m_Enabled = false;
bool localenable = target.Invoke();
if (localenable)
{
m_Enabled = true;
break;
}
}
return m_Enabled;
}
public void Execute(object parameter)
{
if (m_Enabled)
m_ExecuteTargets(parameter != null ? parameter.ToString() : null);
}
public event EventHandler CanExecuteChanged = delegate { };
...
}
You can see I've chosen to use a Func<bool> delegate to hook up the handlers that will
determine whether the command is enabled or not. In the implementation of CanExecute, the
class loops through the handlers hooked up to the m_CanExecuteTargets delegate and sees
whether any one handler wants to execute. If so, it returns true for the
StringDelegateCommand to be enabled. When the Execute method is called, it simply checks
to see if the command is enabled and, if so, invokes all the handlers hooked up to the
m_ExecuteTargets Action<string> delegate.
To hook up handlers to the CanExecute and Execute methods, the StringDelegateCommand
class exposes the event accessors shown in Figure 7 to allow handlers to simply subscribe or
unsubscribe from the underlying delegates. Notice that the event accessor also gives you the
opportunity to trigger the CanExecuteChanged event whenever a handler subscribes or
unsubscribes.
Figure 7 Command Event Accessors
public event Action<string> ExecuteTargets
{
add { m_ExecuteTargets += value; }
Understanding Routed Events and Commands In WPF
Seite 10 von 12
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
remove { m_ExecuteTargets -= value; }
}
public event Func<bool> CanExecuteTargets
{
add
{
m_CanExecuteTargets += value;
CanExecuteChanged(this, EventArgs.Empty);
}
remove
{
m_CanExecuteTargets -= value;
CanExecuteChanged(this, EventArgs.Empty);
}
}
A Routed Handler Sample
I've got this class hooked up in a sample application in the code download. The sample has a
simple view with a presenter behind it (along the lines of MVP, but without the model). The
presenter exposes a presentation model to the view for data binding (you can think of a
presentation model as sitting between a presenter and a view, whereas the model of MVP sits
behind the presenter from the view). The presentation model typically exposes properties to
which the view can data bind. In this case, it just exposes a single property for the command
so that it can be easily hooked up in the XAML of the view through data binding:
<Window x:Class="CustomCommandsDemo.SimpleView" ...> <Grid> <Button
Command="{Binding CookDinnerCommand}" CommandParameter="Dinner is
served!" ...>Cook Dinner</Button> <Button Click="OnAddHandler" ...>Add Cook
Dinner Handler</Button> </Grid> </Window>
The Binding statement will just look for a property on the current DataContext, named
CookDinnerCommand, and will try to cast that to an ICommand if it finds one. The
CommandParameter was mentioned before, and it is a way for the invoker to pass some data
along with the command. In this case, notice that I just pass the string that will be passed to
the handlers through the StringDelegateCommand.
The codebehind of the view (the Window class) is shown here:
public partial class SimpleView : Window { SimpleViewPresenter m_Presenter
= new SimpleViewPresenter(); public SimpleView() { InitializeComponent();
DataContext = m_Presenter.Model; } private void OnAddHandler(object sender,
RoutedEventArgs e) { m_Presenter.AddCommandHandler(); } }
The view constructs its presenter, gets the presentation model from the presenter, and sets that
as the DataContext. It also has the button Click handler, which calls into the presenter, asking
it to add a handler for the command.
Composite Events and Commands
I've been working with the Microsoft patterns & practices group this year to help develop
Composite Application Guidance for WPF, which is a set of guidance for developing complex
composite applications in WPF. This set of guidance contains libraries, called the Composite
Application Libraries (CAL), offering services and helper classes for composite applications.
Read more about Composite Application Guidance for WPF in Glenn Block's article,
"Patterns for Building Composite Applications with WPF," at
msdn.microsoft.com/magazine/cc785479.
Figure 8 shows this application in action. The first window is in the initial state with no
command handlers hooked up. You can see that the first button (the invoker) is disabled
because there are no command handlers. Then when you press the second button, it calls into
the presenter and hooks up a new command handler. The first button is then enabled, and
when you click it, it invokes the command handler to which it is loosely coupled through a
data binding and the underlying command's subscriber list.
Understanding Routed Events and Commands In WPF
Seite 11 von 12
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Figure 8 Custom Command
Sample in Action (Click the
image for a larger view)
The presenter code is shown
in Figure 9. You can see that
the presenter constructs a
presentation model and
exposes it to the view through a Model property. When AddCommandHandler is called from
the view (in response to the second button Click event), it adds a subscriber to the
CanExecuteTargets and ExecuteTargets events on the model. These subscription methods are
simple methods located in the presenter that return true and display a MessageBox,
respectively.
Figure 9 Presenter Class
public class SimpleViewPresenter { public SimpleViewPresenter() { Model =
new SimpleViewPresentationModel(); } public SimpleViewPresentationModel
Model { get; set; } public void AddCommandHandler()
{ Model.CookDinnerCommand.CanExecuteTargets += CanExecuteHandler;
Model.CookDinnerCommand.ExecuteTargets += ExecuteHandler; } bool
CanExecuteHandler() { return true; } void ExecuteHandler(string msg)
{ MessageBox.Show(msg); } }
This example shows that a combination of data binding, UI patterns, and custom commands
can give you a clean, decoupled approach for commands without being tied to the limitations
of routed commands. Because the command is hooked up in the XAML through a binding,
you can even extend this approach to define your views with XAML only (no codebehind),
use bound commands from the XAML to trigger actions in your presentation model, and
initiate the action you would have normally done in codebehind from your presentation model
instead.
You'll need a controller for constructing the views and providing them with their presentation
model, but you would be able to write interactive views without the need for codebehind. If
you have no need for codebehind, there is much less opportunity for you to add tightly
coupled and untestable spaghetti code in your codebehind file, as so often happens in UI apps.
This approach is just beginning to be explored in WPF. But it is definitely something to
consider, and you should be on the lookout for more examples.
Brian Noyes is chief architect of IDesign (www.idesign.net), a Microsoft Regional Director
(www.theregion.com), and a Microsoft MVP. He is the author of Developing Applications
with Windows Workflow Foundation, Smart Client Deployment with ClickOnce, and Data
Binding with Windows Forms 2.0. He is also a frequent speaker at industry conferences
worldwide. Contact Brian through his blog at briannoyes.net.
Understanding Routed Events and Commands In WPF
Seite 12 von 12
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Contrasting the ADO.NET DataReader and DataSet
John Papa
http://msdn.microsoft.com/en-us/magazine/cc188717.aspx
Magazine Issues 2004 June
I am often asked by developers whether the ADO.NET DataReader or the DataSet is the
better tool. Some developers say the DataReader is better because it is lightweight, while still
others say they prefer the DataSet for its inherent flexibility. The truth is that both have their
place in Microsoft® .NET development as their usefulness depends on the situation.
The ADO 2.x recordset object is able to operate in either a connected or a disconnected mode.
It can remain connected to the underlying database while traversing a forward-only rowset or
it can retrieve a rowset into a client-side, in-memory cursor and disconnect itself from the
database. Among the hurdles you may well encounter in migrating from classic ADO to
ADO.NET is gaining a full understanding of how the operations that the ADO recordset
performed are now handled in ADO.NET.
Instead of a single rowset container, ADO.NET offers two distinctly separate data storage
objects: the DataReader and the DataSet. This month I'll focus on the purpose of these two
data retrieval classes in ADO.NET and help you to decide which is the best choice to use in a
particular situation. I will explore how to retrieve data into both the DataReader and the
DataSet, beginning by discussing the DataReader's unique capabilities. I will also compare the
connected DataReader to the disconnected DataSet, weighing the pros and cons of using each
in different scenarios.
Being Connected
Before deciding when to use a DataReader, it is smart to understand its features and
limitations. The DataReader has a defined set of operations that revolve around its connected,
forward-only, read-only nature (the read-only DataReader is also known as the firehose cursor
of ADO.NET). A DataReader is a stream of data that is returned from a database query. When
the query is executed, the first row is returned to the DataReader via the stream. The stream
then remains connected to the database, poised to retrieve the next record. The DataReader
reads one row at a time from the database and can only move forward, one record at a time.
As the DataReader reads the rows from the database, the values of the columns in each row
can be read and evaluated, but they cannot be edited.
Unlike the DataSet, the DataReader is not implemented directly in the System.Data
namespace. Rather, the DataReader is implemented through a specific managed provider's
namespace such as System.Data.SqlClient.SqlDataReader. Because all DataReaders,
including the OleDbDataReader, the SqlDataReader, and other managed provider's
DataReaders implement the same IDataReader interface, they should all provide the same
base set of functionality. Each DataReader is optimized for a specific data provider. If the
database you are developing against has a managed provider for ADO.NET, then you should
take advantage of it. Otherwise, you can use the System.Data.OleDb or the System.Data.Odbc
namespaces, which expose more generic managed providers that can access a variety of data
sources. If you are developing against SQL Server™ or Oracle, it would be more efficient to
use the provider that was made specifically for these databases. In this column, I will query
the SQL Server Northwind database using the System.Data.SqlClient namespace.
The fact that the SqlDataReader is part of a specific managed provider's feature set further
differentiates it from the DataSet. The SqlDataReader can only retrieve one row at a time
from the data source and in order for it to get the next record, it has to maintain its connection
to the data source. The DataSet, however, doesn't need to know about where it gets its data.
Contrasting the ADO.NET DataReader and DataSet
Seite 1 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
The DataReader can only get its data from a data source through a managed provider. The
DataSet can also get its data from a data source via a managed provider, but the data source
can also be loaded manually, even from an XML file on a hard drive. If the .NET Framework
does not provide a managed provider that is specifically designed for your database, it is
certainly worth checking to see if the manufacturer or a third party has one available since
they should perform better than the generic OLE DB and ODBC providers.
In ASP.NET, DataReader objects can be used for more robust situations such as binding
themselves to an ASP.NET DataGrid or a DropDownList server control. The following code
demonstrates how to retrieve a list of products from the Northwind database using a
SqlDataReader object:
string sSQL = "SELECT * FROM Products";
string sConnString =
"Server=(local);Database=Northwind;Integrated Security=SSPI;";
using (SqlConnection oCn = new SqlConnection(sConnString))
{
SqlCommand oSelCmd = new SqlCommand(sSQL, oCn);
oSelCmd.CommandType = CommandType.Text;
oCn.Open();
SqlDataReader oDr = oSelCmd.ExecuteReader();
DataGrid1.DataSource = oDr;
DataGrid1.DataBind();
}
Both a SqlConnection and a SqlCommand object are created. The SqlConnection is opened
and the SqlCommand object executes the SQL query, returning the first row to the
SqlDataReader. At this point the connection to the database is still open and associated with
the SqlDataReader. This code shows how a SqlDataReader can be bound to a bindable object
such as an ASP.NET DataGrid.
Alternatively, a DataReader could be used to retrieve the rows and then loop through them
manually, one by one. It can support several resultsets as well. For example, a list of products
and categories could be retrieved from a database. The following code retrieves a
SqlDataReader and loops through its rows, writing the first column's value for each row to the
console:
SqlDataReader oDr = oCmd.ExecuteReader();
while(oDr.Read()) {
Console.WriteLine(oDr[0]);
}
Supporting Multiple Resultsets
The DataReader supports access to multiple resultsets, one at a time, in the order they are
retrieved. This code is easily modified to handle multiple resultsets. The following code
retrieves a SqlDataReader and loops through its rows, again writing the first column's value
for each row to the console:
SqlDataReader oDr = oCmd.ExecuteReader();
do {
while(oDr.Read())
{
Console.WriteLine(oDr[0]);
}
Console.WriteLine(oDr[0]);
}
while(oDr.NextResult());
Once all of the rows from the first resultset are traversed, the rowset from the second query is
retrieved and its rows are traversed and written. This process can continue for several
resultsets using a single SqlDataReader.
Contrasting the ADO.NET DataReader and DataSet
Seite 2 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
The Read method of the SqlDataReader loads the next record so that it can be accessed and
moves the position of the cursor ahead. It returns a Boolean value indicating the existence of
more records. This feature can help circumvent a common problem in classic ADO
development: the endless loop. In classic ADO, when looping through a recordset object
developers would often omit the MoveNext method and run the code only to remember a
second too late that this would cause the recordset to loop infinitely on the same record.
ADO.NET is kind to developers as its DataReader object's Read method automatically moves
the position to the next record so this situation can't occur. The NextResult method of the
SqlDataReader object retrieves the subsequent rowset and makes it accessible to the
SqlDataReader. It also returns a Boolean value indicating if there are additional resultsets to
traverse, like the Read method does.
The DataReader in the previous code sample shows how to get the value for a column from
the DataReader using its ordinal index position. Can the DataReader be indexed by the
column name or can the index of the column be retrieved? The answer to both of these
questions is yes. The code in Figure 1 shows how a DataReader retrieves data and displays
the CompanyName for each row in the diagnostics' output window.
Figure 1 Retrieving and Displaying Data
string sConnString =
"Server=(local);Database=Northwind;Integrated Security=SSPI;";
using(SqlConnection oCn = new SqlConnection(sConnString))
{
SqlCommand oCmd = new SqlCommand("SELECT * FROM Customers", oCn);
oCn.Open();
SqlDataReader oDr =
oCmd.ExecuteReader(CommandBehavior.CloseConnection);
while(oDr.Read())
{
System.Diagnostics.Debug.WriteLine("Company Name " +
oDr["CompanyName"]);
System.Diagnostics.Debug.WriteLine("Company Name: name = " +
oDr.GetName(1));
System.Diagnostics.Debug.WriteLine("Company Name: index = " +
oDr.GetOrdinal("CompanyName"));
}
}
To demonstrate these techniques, this code displays the CompanyName value, column name,
and index. The first line in the loop writes the CompanyName using the value representing the
name of the CompanyName column. This could also have been accomplished by passing the
index of 1. I avoid this as it is less clear which column you are accessing, although using the
string value is slower than using the index. The second line in the loop writes the name of the
column at position 1 (CompanyName) using the GetName method. The third line gets the
index of the CompanyName column using the GetOrdinal method of the SqlDataReader. You
might also notice that the CommandBehavior is set to CloseConnection, ensuring that the
connection will be closed when the SqlDataReader is closed. These methods are simple but
they make the SqlDataReader a power tool.
The Disconnected Side
The DataSet is the main data storage tool in the ADO.NET disconnected architecture. Unlike
the DataReader, the DataSet is not connected directly to a database through a Connection
object when you populate it. Instead, to fill a DataSet from a database you first create a
DataAdapter object (such as a SqlDataAdapter) for the provider and associate it with a
SqlConnection object. Then the SqlDataAdapter can broker the data retrieval for the DataSet
Contrasting the ADO.NET DataReader and DataSet
Seite 3 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
by issuing a SqlCommand against the database through the SqlConnection, retrieving the data,
and filling the DataSet.
You can think of the SqlDataAdapter as a bridge between the connected and disconnected
objects. One of its purposes is to serve as the route for a rowset to get from the database to the
DataSet. For example, when the SqlDataAdapter's Fill method is executed it opens its
associated SqlConnection object (if not already open) and issues its associated SqlCommand
object against the SqlConnection. Behind the scenes, a SqlDataReader is created implicitly
and the rowset is retrieved one row at a time in succession and sent to the DataSet. Once all of
the data is in the DataSet, the implicit SqlDataReader is destroyed and the SqlConnection is
closed.
The following code shows how a DataSet can be filled from the Products table of the
Northwind database. Notice that there is no explicit SqlDataReader object in this code
sample:
string sSQL = "SELECT * FROM Products";
string sConnString =
"Server=(local);Database=Northwind;Integrated Security=SSPI;";
SqlDataAdapter oDa = new SqlDataAdapter();
DataSet oDs = new DataSet();
using(SqlConnection oCn = new SqlConnection(sConnString))
{
SqlCommand oSelCmd = new SqlCommand(sSQL, oCn);
oSelCmd.CommandType = CommandType.Text;
oDa.SelectCommand = oSelCmd;
oDa.Fill(oDs, "Products");
}
The DataSet can read and load itself from an XML document as well as export its rowset to
an XML document. Because the DataSet can be represented in XML, it can be easily
transported across processes, a network, or even the Internet via HTTP. Unlike the
DataReader, the DataSet is not read-only. A DataSet can be modified, and rows can be added
or deleted. Changes to a DataSet can be sent to the database via a managed provider's objects.
Another key difference between the DataSet and the DataReader is that the DataSet is fully
navigable. Its rows can be traversed forward or backward. The DataReader can be traversed
forward only. In addition, a DataSet is highly flexible in that its DataTable objects can be
filtered vertically or horizontally and they can be sorted or even searched. The DataSet is
independent of any one data provider as it relies on a DataAdapter specific to each provider to
broker the data between the DataSet and the database.
Not only can the DataSet be loaded from XML, it can also be loaded manually. Notice in
Figure 2 how a DataTable is created and its columns added manually. A primary key
constraint is established as well as an auto-incrementing value for the key field. Then the
DataTable is added to an empty DataSet (though is doesn't have to be empty) and the rows are
added one by one to the DataTable, all without ever connecting to a data source.
Figure 2 Creating a DataSet Manually
//-- Create the table
DataTable oDt = new DataTable("Employees");
DataRow oRow;
oDt.Columns.Add("EmployeeID", System.Type.GetType("System.Int32"));
oDt.Columns.Add("FirstName", System.Type.GetType("System.String"));
oDt.Columns.Add("LastName", System.Type.GetType("System.String"));
oDt.Constraints.Add("PK_Employees", oDt.Columns["EmployeeID"], true);
oDt.Columns["EmployeeID"].AutoIncrement = true;
oDt.Columns["EmployeeID"].AutoIncrementSeed = -1000;
oDt.Columns["EmployeeID"].AutoIncrementStep = -1;
oDs.Tables.Add(oDt);
//-- Add the rows
oRow = oDs.Tables["Employees"].NewRow();
Contrasting the ADO.NET DataReader and DataSet
Seite 4 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
oRow["FirstName"] = "Haley";
oRow["LastName"] = "Smith";
oDs.Tables["Employees"].Rows.Add(oRow);
oRow = oDs.Tables["Employees"].NewRow();
oRow["FirstName"] = "Madelyn";
oRow["LastName"] = "Jones";
oDs.Tables["Employees"].Rows.Add(oRow);
//-- Bind it to a DataGrid
grdEmployees.DataSource = oDs.Tables["Employees"];
Because the DataSet is disconnected, its use can reduce the demand on database servers. It
does, however, increase the memory footprint in the tier where it is stored, so be sure to
account for that when designing around a DataSet as your data store. Scaling up on the middle
tier using parallel servers and load balancing is a common way to handle the increased load so
that session-based information can be stored in objects such as the DataSet.
When to Use the DataSet
Your situation will dictate when and where you'll use a DataSet versus a DataReader. For
example, the DataSet's disconnected nature allows it to be transformed into XML and sent
over the wire via HTTP if appropriate. This makes it ideal as the return vehicle from businesstier objects and Web services. A DataReader cannot be serialized and thus cannot be passed
between physical-tier boundaries where only string (XML) data can go.
DataSet objects are a good choice when the data must be edited or rows added or deleted from
the database. It is not the only choice, however, as a DataReader could be used to retrieve the
data and changes would be sent to the database via a SqlDataAdapter through a separate, selfmaintained DataRow array. That process can be quite messy because the SqlDataReader
cannot allow edits as the DataSet can. As mentioned earlier, the DataSet is also a good choice
when you need data manipulation such as filtering, sorting, or searching. Since the DataSet
can be traversed in any direction, all of these features are available. This flexibility also makes
the DataSet an easy choice when the situation calls for multiple iterations of the rowset. A
DataReader can move forward only, so to loop through it more than once, the DataReader
would be closed and reopened and the query would hit the database a second time.
If a rowset is intended to be bound to a single ASP.NET server control and the data is readonly, the DataReader could suffice. If a rowset is intended to be bound to more than one readonly ASP.NET server control, you should consider using a DataSet instead. If a DataReader
was bound to more than one control (such as three DropDownList controls), the same query
would hit the database three times since the DataReader can only move forward. The DataSet
also works well when a rowset must be persisted between page calls to the Session or Cache
objects.
Of course, because the DataReader is associated with a specific data source, it cannot be
created, filled, or traversed without a connection to the data source. Unlike the DataReader, a
DataSet can be created manually without a connection to the source. In a situation such as an
online shopping cart in which a custom data store is required, a DataSet could be created
manually and its rows added.
Another good use of the DataSet is the situation in which data must be retrieved and a
complex action performed on each row. For example, an application might retrieve a hundred
stock and mutual fund symbols from a 401k table that needs to be edited. This data might
have to include the stock and mutual fund prices on screen, as well. A DataSet could be used
to store the rowset and some code could loop through the DataSet and perform a lookup of
each stock's price through a third-party Web service. Finally, one of the more compelling
reasons to use a DataSet instead of a DataReader is that the DataSet can be serialized when
Contrasting the ADO.NET DataReader and DataSet
Seite 5 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
the rowset needs to be passed around a network or the Internet. A DataReader cannot be
serialized to XML due to its connected nature.
The DataSet is not the best solution in every situation, and there are several situations in
which DataReaders should really be considered. One is when an application implements
a .NET architecture without data binding—situations in which manual updates to the database
are performed and controls are loaded by looping through rowsets. DataReaders are a good
choice when an application has to be sensitive to changes in the underlying database.
There are other times when a DataReader can be the right choice, such as when populating a
list or retrieving 10,000 records for a business rule. When a huge amount of data must be
retrieved to a business process, even on a middle tier, it can take a while to load a DataSet,
pass the data to it on the business tier from the database, and then store it in memory. The
footprint could be quite large and with numerous instances of it running (such as in a Web
application where hundreds of users may be connected), scalability would become a problem.
If this data is intended to be retrieved and then traversed for business rule processing, the
DataReader could speed up the process as it retrieves one row at a time and does not require
the memory resources that the DataSet requires.
When output or return parameters need to be retrieved, a DataReader will not allow you to get
them until the DataReader and its connection are closed. If data must be bound to a read-only
list in a Web Form, a DataReader is a very good choice. Binding a DataReader to a
DropDownList or even a read-only DataGrid in ASP.NET works well as the data can be
retrieved and displayed in the list but does not need to be persisted for editing. DataSets are
ideal if data needs to be edited, sorted, filtered, or searched.
As I have shown, when data must be passed without a connection to a database or when rich
features for manipulating the data are required, a DataSet fits the bill nicely. The DataReader
works well when a simpler purpose for the data exists such as populating a dropdown list or
retrieving tens of thousands of rows for processing. Your decision should be based on the
particular factors of the situation of the application.
Contrasting the ADO.NET DataReader and DataSet
Seite 6 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Configuration Overview: ASP.NET
By Brij
http://www.codeproject.com/KB/server-management/websitecofig.aspx
Introduction
Here in this article, I will be exploring the configuration files of a website. ASP.NET website
configuration is normally a combination of two files:
•
•
machine.config
web.config
Here, I'll concentrate on web.config and give an overview of machine.config.
Every time we install the .NET framework, there is a machine.config file that is created in
"C:\WINDOWS\Microsoft.NET\Framework\[Version]\CONFIG", which mainly defines:
•
•
•
Supported configuration file sections,
the ASP.NET worker process configuration, and
registers different providers that are used for advanced features such as profiles,
membership, and role based security.
To explore the web.config might take a book, but here, I'll try to explore all the important
sections that play a pivotal role for an ASP.NET website and its deployment.
Every web application inherits settings from the machine.config file, and application level
setting is done in the web.config file. We can also override configurations in the
machine.config file in the web.config file. But, a few settings can not be overridden because
certain settings are process model settings and can't be changed on a per application basis.
The entire contents of a configuration file, whether it is machine.config or web.config, is
nested in a <configuration> element.
ASP.NET Multilayered Configuration system
ASP.NET uses a multilayered configuration system that allows for using different settings for
different parts of an application. For this, we must have an additional subdirectory inside the
virtual directory, and these subdirectories will contain their own config files with additional
settings. ASP.NET uses configuration inheritance so that each subdirectory acquires the
settings from the parent directory.
Configuration Overview: ASP.NET
Seite 1 von 9
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Let's take an example. We have a web request http://localhost/X/Y/Z/page.aspx, where X is
the root directory of the application. In this case, multiple levels of settings come into the
picture.
1. The default machine.config settings are applied first.
2. Next, the web.config of the root level is applied. This web.config resides in the same
config directory as the machine.config file.
3. Now, if there is any config file in the application root X, these settings are applied.
4. If there is any config file in the subdirectory Y, these settings are now applied.
5. If there is any config file in the application root Z, those settings are then applied.
But here, there is a limitation: we can have unlimited number of subdirectories having
different settings, but the configuration at step 1 and 2 are more significant because some of
the settings can not be overridden, like the Windows account that is to be used to execute the
code, and other settings can be only overridden at the application root level, like the type of
authentication to be used etc.
Different config files are useful when we apply different security settings to different folders.
The files that need to be secured would then be placed in a separate folder with a separate
web.config file that defines the more stringent security settings to these files and vice versa.
In the web.config, under the <configuration> element, there is another element
<system.web>, which is used for ASP.NET settings and contains separate elements for each
aspect of the configuration.
Configuration Overview: ASP.NET
Seite 2 von 9
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Important Configuration Tags
There are a lot of configuration tags that are provided by the web.config file, like
authentication, authorization, browserCaps, clientTarget etc., but all of these don't
have that much importance (and also can't be covered in a single article ), so here, I have only
concentrated on the main tags of the config file.
<authentication>
This element is used to verify the client's identity when the client requests a page from the
server. This is set at the application level. We have four types of authentication modes:
“None”, “Windows”, “Forms”, and “Passport”.
If we don't need any authentication, this is the setting we use:
<authentication mode="None"/>
Normally, Windows authentication is used, for which, we need to check the checkbox:
Integrated Windows Authentication.
<authentication mode="Windows"/>
This authentication is handled by IIS. When the user sends a request to the server, IIS
authenticates it and sends the authentication identity to the code.
IIS gives us four choices
for the authentication
modes: Anonymous, Basic,
Digest, and Windows
Integrated. If the user
selects Anonymous, then
IIS doesn't perform any
authentication. For Basic
authentication, the user has
to provide a username and
password. This
authentication is very
unsecure, because the user
credentials are sent in clear
text format over the
network. Digest
authentication is same as
Basic, except it hashes the
user's password and
transmits the hashed
version over the wire. So, it
is more secure than Basic.
For Windows Integrated
Configuration Overview: ASP.NET
Seite 3 von 9
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
authentication, passwords never cross the network. The user must still have a username and
password, but the application uses either the Kerberos or a challenge/response protocol to
authenticate the user.
Forms authentication uses web application forms to collect user credentials, and on the basis
of the credential, it takes action on a web application.
<authentication mode="Forms">
<forms name="Form" loginUrl="index.asp" />
</authentication>
Passport authentication is provided by Microsoft. A redirect URL should be specified, and
is used when the requested page is not authenticated, and then it redirects to this URL.
<authentication mode="Passport">
<passport redirectUrl="internal" />
</authentication>
Here, users are authenticated using the information in Microsoft's Passport database. The
advantage is, we can use existing user credentials (such as an email address and password)
without forcing users to go through a separate registration process. The disadvantage is we
need to go through the licensing agreement with Microsoft and pay a yearly fee based on the
use.
For using Passport authentication, you first install the Passport Software Development Kit
(SDK) on your server. The SDK can be downloaded from here. It includes full details of
implementing passport authentication in your own applications.
<authorization>
The <authorization> tag controls client access to web page resources. This element can be
declared at any level (machine, site, application, subdirectory, or page).
authorization>
<allow users="comma-separated list of users"
roles="comma-separated list of roles"
verbs="comma-separated list of verbs"/>
<deny users="comma-separated list of users"
roles="comma-separated list of roles"
verbs="comma-separated list of verbs"/>
</authorization>
<allow>: Using this tag, we can control access to resources on the basis of the following
verbs. In these attributes, we use symbols: ? and *.? means for anonymous users/resources,
and * means for all users.
•
•
•
users: This contains the list of user names (comma separated) that are allowed to
access the resources.
roles: This contains the list of roles (comma separated) that are allowed to access the
resources.
verbs: This contains the list of HTTP verbs to which the action applies (comma
separated). It is used to create a rule that applies to a specific type of HTTP request
(GET, POST, HEAD, OR DEBUG).
Configuration Overview: ASP.NET
Seite 4 von 9
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
<deny>:
•
•
•
Using this tag, we can control access to resources on the basis of the following verbs:
users:
This contains the list of users names (comma separated) that are denied access
to the resources.
roles: This contains the list of roles (comma separated) that are denied access to the
resources.
verbs: This contains the list of HTTP verbs to which the action applies (comma
separated). It is used to create a rule that applies to a specific type of HTTP request
(GET, POST, HEAD, OR DEBUG).
<compilation>
In this section, we can configure the settings of the compiler. Here, we can have lots of
attributes, but the most common ones are debug and defaultLanguage. Setting debug to
true means we want the debugging information in the browser, but it has a performance
tradeoff, so normally, it is set as false. And, defaultLanguage tells ASP.NET which
language compiler to use: VB or C#.
<customErrors>
This tags includes the error settings for the application, and is used to give custom error pages
(user-friendly error pages) to end users. In the case that an error occurs, the website is
redirected to the default URL. For enabling and disabling custom errors, we need to specify
the mode attribute.
<customErrors defaultRedirect="url" mode="Off">
<error statusCode="403" redirect="/accesdenied.html" />
<error statusCode="404" redirect="/pagenotfound.html" />
</customErrors>
•
•
•
"On" means this settings is on, and if there is any error, the website is redirected to
the default URL.
"Off" means the custom errors are disabled.
"RemoteOnly" shows that custom errors will be shown to remote clients only.
<error statusCode="403" redirect="/accesdenied.html" />
<error statusCode="404" redirect="/pagenotfound.html" />
This means if there is an error of 403, then the website will redirected to the custom page
accessdenied.html. Similarly for 404 as defined above.
Note: If an error occurs in the custom error page itself, ASP.NET won't able to handle it. It
won't try to reforward the user to the same page. Instead, it'll show the normal default client
error page with a generic message.
<globalization>
This section is used when we want to use encoding or specify a culture for the application.
This is a very vast topic, and can take an article itself for explaining it. Here, we define the
character set for the server to send the response to the client, which is by default is UTF-8,
Configuration Overview: ASP.NET
Seite 5 von 9
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
and the settings of which the server should use to interpret and display culturally specific
strings, such as numbers and dates.
globalization requestEncoding="utf-8" responseEncoding="utf-8" />
<httpRuntime>
This section can be used to configure the general runtime settings of the application. The main
two are:
<httpRuntime appRequestQueueLimit="50" executionTimeout="300" />
As the name suggests, the attribute appRequestQueueLimit defines the number of requests
that can be queued up on the server for processing. If there are 51 or more requests, then
server would return the 503 error ("Server too busy").
The attribute executionTimeout defines the number of minutes ASP.NET will process a
request before it gets timeout.
<trace>
As the name suggestz, it is used for tracing the execution of an application. We have here two
levels of tracing: page level and application level. Application level enables the trace log of
the execution of every page available in the application. If pageOutput="true", trace
information will be displayed at the bottom of each page. Else, we can view the trace log in
the application root folder, under the name trace.axd.
<trace enabled="false" requestLimit="10" pageOutput="false"
traceMode="SortByTime" locaOnly="true" />
Set the attribute localOnly to false for not viewing the trace information from the client.
For enabling trace at page level, set Trace="True" in the Page tag (on the top of the page).
<identity>
Using this tag, we can control the identity of the application. By default, Impersonation is
disabled. Using Impersonation, an ASP.NET application can execute optionally with the
identity of a client on whose behalf they are operating.
<identity impersonate="false" userName="domain\username"
password="password" />
<sessionState>
In this section, we tell ASP.NET where to store the session. By default, it's inproc which
means storing the session values on the server. But we have four options:
•
•
"Off" means session is not enabled for the application.
"inproc" means storing the session values on the server.
Configuration Overview: ASP.NET
Seite 6 von 9
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
•
•
"StateServer" means session states are stored in a remote server.
"SQLServer" means session states are stored in a SQL Server database. For this, we
need to install the InstallSQLState.sql script in the SQL Server database. It is mainly
used when the we use web farms (an application deployed on multiple servers), but it
makes the performance slow as compared to "inproc".
Here are the other settings:
•
•
•
•
•
"cookieless": when it is true, it means the session used is without cookies.
“timeout” specifies after how much time the session would expire if the application is
not accessed during that period.
"stateConnectionString" needs to be specified when the session mode is
StateServer.
"sqlConnectionString" is the connection string of the SQL Server database if the
session mode is sqlserver.
"stateNetworkTimeout" attribute, when using the StateServer mode to store
session state, specifies the number of seconds the TCP/IP network connection between
the web server and the state server can be idle before the session is abandoned. The
default is 10.
<sessionState mode="Off"
cookieless="true"
timeout="100"
stateConnectionString="tcpip=server:port"
sqlConnectionString="sql connection string"
stateNetworkTimeout="number of seconds"/>
<appSettings>
This section is used to store custom application configuration like database connection strings,
file paths etc. This also can be used for custom application-wide constants to store
information over multiple pages. It is based on the requirements of the application.
<appSettings>
<add key="Emailto" value="[email protected]" />
<add key="cssFile" value="CSS/text.css" />
</appSettings>
It can be accessed from code like:
ConfigurationSettings.AppSettings("Emailto");
All the values returned from it are strings.
Custom Configuration Sections
We might need some custom configuration sections based on the requirements. One of the
simplest ways we can do this is to create our own named sections, and we can use existing
NameValueSectionHandler components to parse them, and they can be used as key/value
pairs to be accessed at run-time.
Configuration Overview: ASP.NET
Seite 7 von 9
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
This can be read very easily accessed from the code-behind as:
private string ReadCustomSection()
{
string strKey = "mySectionKey1";
NameValueCollection nvcmySection = (NameValueCollection)
ConfigurationSettings.GetConfig("mySection");
string strValueofKey = nvcmySection[strKey];
return strValueofKey;
}
There are more ways for using custom configuration sections. Check this article:
CustomConfigurationSection.
Encrypting Configuration Sections
Some times, we put some sensitive data in the web.config file like connection strings, user
specific details etc. It is recommended to encrypt these sections. ASP.NET supports two
encryption techniques.
•
•
RSA
DPAPI
The way the operations perform is very simple. When retrieving information from a config
file, ASP.NET automatically decrypts it and gives the plain text to the code. Similarly, if we
do any updates on the config file from code, it is done the same way. We cannot update a
config file directly. But, we can use WAT for updating it.
Programmatic encryption techniques: If we want to do encryption programmatically, then
we need to retrieve the corresponding ConfigurationSection.SectionInformation object
and call the ProtectSection() method. If we want to decrypt a section, then we can call the
method UnprotectSetion(). Sample code is shown here:
Configuration myConfig =
WebConfigurationManager.OpenWebConfiguration(Request.ApplicationPath);
Configuration Overview: ASP.NET
Seite 8 von 9
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
ConfigurationSection mySettings = myConfig.GetSection("mySection");
if (mySettings.SectionInformation.IsProtected)
{
mySettings.SectionInformation.UnprotectSection();
}
else
{
mySettings.SectionInformation.ProtectSection("DataProtectionConfigurationPr
ovider"); ;
}
myConfig.Save();
Command line utilities: We can also use a command line utility like aspnet_regiis.exe for
encryption of a config file, which is a CAB file found in
C:\[WinDir]\Microsoft.NET\Framework\[Version]. For using this tool, we must create a
virtual directory for the application. You can refer my article, Deploying Website at IIS, for
more information.
When using aspnet_regiis to protect some sections of a config file, we need to specify some
command line arguments such as:
•
•
•
The -pe switch specifies the configuration section to encrypt.
The -app switch specifies our web application virtual path.
The -prov switch specifies the provider name.
Here is the command line for an application located at http://localhost/MyApp:
A Few Important Points
•
•
•
•
•
Some settings can not be encrypted because they are used outside ASP.NET (mainly
by the IIS web server), like <httpruntime>.
Config files are case sensitive.
The web.config file is protected by IIS, so it won't be accessible by a client system. So,
if a user tries to access it, anaccess denied message will be shown.
If we change the config file at the server, we don't need to restart the web server
because IIS monitors the changes in the web.config, and for performance measures, it
cache it.
Microsoft also provides a tool known as Website Administration Tool (WAT) that lets
us configure various part of the web.config using a web interface. To run it, select
Website->ASP.NET Configuration, in Visual Studio.
Configuration Overview: ASP.NET
Seite 9 von 9
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Erstellen von Webanwendungen ohne Webformulare
Chris Tavares
MSDN Magazin, März 2008
http://msdn.microsoft.com/de-de/magazine/cc337884.aspx
Dieser Artikel basiert auf der Vorabversion von ASP.NET MVC Framework. Änderungen der
Details in diesem Artikel sind vorbehalten.
Dieses neue Framework basiert auf dem Model View Controller (MVC)-Muster, daher der
Name ASP.NET MVC. Das MVC-Muster wurde ursprünglich in den 70er Jahren als Teil von
Smalltalk erfunden. Wie in diesem Artikel aufgezeigt wird, passt es hervorragend zur
Funktionsweise des Internets. MVC teilt die Benutzeroberfläche in drei unterschiedliche
Objekte ein: den Controller, der Eingaben empfängt und behandelt, das Modell, das die
Domänenlogik enthält, und die Ansicht, die die Ausgabe generiert. Im Kontext des Internets
ist die Eingabe eine HTTP-Anforderung, und der Anforderungsfluss sieht wie Abbildung 1
aus.
Abbildung 1 MVCMusteranforderungsfluss (Klicken Sie zum
Vergrößern auf das Bild)
Dies unterscheidet sich erheblich vom
Prozess in Webformularen. Im
Webformularmodell geht die Eingabe zur
Seite (Ansicht), und die Ansicht ist sowohl
für die Verarbeitung der Eingabe als auch
das Generieren der Ausgabe verantwortlich.
Bei MVC sind die Verantwortlichkeiten
getrennt.
Wahrscheinlich gehen Ihnen jetzt zwei Dinge
durch den Kopf. Entweder: „Das ist großartig.
Wie genau geht das?“ oder: „Warum sollte
ich drei Objekte schreiben, wenn ich zuvor
nur eins schreiben musste?“ Beides sind gute
Fragen und können am besten anhand eines
Beispiels erklärt werden. Um die Vorzüge
diese Methode zu veranschaulichen, zeige
ich Ihnen hier das Schreiben einer kleinen
Webanwendung mithilfe von MVC Framework auf.
Erstellen eines Controllers
Um folgen zu können, müssen Sie Visual Studio 2008 installieren und eine Kopie von
MVC Framework abrufen. Zum Zeitpunkt dieses Artikels ist es als Teil des Community
Technology Preview (CTP) der ASP.NET-Erweiterungen vom Dezember 2007 verfügbar
(asp.net/downloads/3.5-extensions). Laden Sie die CTP-Erweiterungen und das MVC-Toolkit
herunter, das einige nützliche Hilfsobjekte enthält. Nachdem Sie CTP heruntergeladen und
installiert haben, wird im Dialogfeld für neue Projekte ein neues ASP.NET MVC
Webanwendungsprojekt angezeigt.
Durch Auswählen dieses MVC-Webanwendungsprojekts erhalten Sie eine Lösung, die etwas
anders aussieht als eine normale Website oder Anwendung. Die Lösungsvorlage erstellt eine
Webanwendung mit einigen neuen Verzeichnissen (siehe Abbildung 2). Vor allem enthält
Erstellen von Webanwendungen ohne Webformulare
Seite 1 von 14
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
das Controllerverzeichnis die Controllerklassen, und das Ansichtenverzeichnis (und alle seine
Unterverzeichnisse) enthält die Ansichten.
Abbildung 2 Die MVC-Projektstruktur
Sie schreiben jetzt einen sehr einfachen Controller, der
einen Namen zurückgibt, der auf der URL übergeben wird.
Klicken Sie mit der rechten Maustaste auf den Ordner
„Controller“. Durch Auswahl von „Element
hinzufügen“ wird das bekannte Dialogfeld „Neues
Element hinzufügen“ mit einigen neuen Elementen
angezeigt, einschließlich einer MVC-Controllerklasse und
verschiedenen MVC-Ansichtskomponenten. In diesem Fall
fügen Sie eine Klasse namens „HelloController“ hinzu:
using System;
using System.Web;
using System.Web.Mvc;
namespace HelloFromMVC.Controllers
{
public class HelloController : Controller
{
[ControllerAction]
public void Index()
{
...
}
}
}
Eine Controllerklasse ist viel leichter als eine Seite.
Eigentlich sind nur das Ableiten von System.Web.Mvc.Controller und das Weitergeben des
Attributs [ControllerAction] an die Aktionsmethoden wirklich erforderlich. Eine Aktion ist
eine Methode, die als Reaktion auf eine Anforderung an eine bestimmte URL aufgerufen wird.
Aktionen sind für die erforderliche Verarbeitung und das anschließende Rendern einer
Ansicht verantwortlich. Zunächst schreiben Sie eine einfache Aktion, die den Namen an die
Ansicht übergibt, wie Sie hier sehen:
[ControllerAction]
public void HiThere(string id)
{
ViewData["Name"] = id;
RenderView("HiThere");
}
Die Aktionsmethode erhält den Namen von der URL über den id-Parameter (später genaueres
über das Wie), speichert ihn in der ViewData-Sammlung und rendert dann eine Ansicht
namens HiThere.
Bevor das Aufrufen dieser Methode oder das Aussehen der Ansicht erläutert werden, soll
näher auf Testfähigkeit eingegangen werden. Erinnern Sie sich an die vorherigen
Anmerkungen darüber, wie schwer Webformularseitenklassen zu testen sind? Nun, Controller
lassen sich viel leichter testen. In der Tat ist es ohne zusätzliche Infrastruktur möglich, einen
Controller direkt zu instanziieren und Aktionsmethoden aufzurufen. Es sind kein HTTPKontext und kein Server erforderlich, lediglich eine Testumgebung. Als Beispiel habe ich für
diese Klasse in Abbildung 3 einen Visual Studio Team System (VSTS)-Komponententest
eingefügt.
Figure 3 Controllerkomponententest
namespace HelloFromMVC.Tests
{
[TestClass]
Erstellen von Webanwendungen ohne Webformulare
Seite 2 von 14
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
public class HelloControllerFixture
{
[TestMethod]
public void HiThereShouldRenderCorrectView()
{
TestableHelloController controller = new
TestableHelloController();
controller.HiThere("Chris");
Assert.AreEqual("Chris", controller.Name);
Assert.AreEqual("HiThere", controller.ViewName);
}
}
class TestableHelloController : HelloController
{
public string Name;
public string ViewName;
protected override void RenderView(
string viewName, string master, object data)
{
this.ViewName = viewName;
this.Name = (string)ViewData["Name"];
}
}
}
Hier gehen verschiedene Dinge vor. Der eigentliche Test ist sehr einfach: Instanziieren Sie
den Controller, rufen Sie die Methode mit den erwarteten Daten auf, und überprüfen Sie dann,
ob die richtige Ansicht gerendert wurde. Führen Sie die Überprüfung durch Erstellen einer
testspezifischen Unterklasse aus, die die RenderView-Methode überschreibt. Dadurch kann
die eigentliche Erstellung von HTML umgangen werden. Achten Sie nur darauf, dass die
richtigen Daten an die Ansicht gesendet wurden und dass die richtige Ansicht gerendert
wurde. Für diesen Test werden die zugrunde liegenden Details der Ansicht außer Acht
gelassen.
Erstellen einer Ansicht
Selbstverständlich ist es letzten Endes erforderlich, etwas
HTML zu erstellen, also erstellen Sie die HiThere-Ansicht.
Dafür erstellen Sie zunächst im Ordner „Ansichten“ der
Lösung einen neuen Ordner namens „Hello“. Standardmäßig
wird eine Ansicht vom Controller im Ordner
„Ansichten\<Controllerpräfix>“ gesucht (das Controllerpräfix
ist der Name der Controllerklasse ohne das Wort „Controller“).
Nach Ansichten, die von HelloController gerendert wurden,
wird also in „Ansichten\Hello“ gesucht. Die Lösung sieht aus
wie in Abbildung 4.
Abbildung 4 Hinzufügen einer Ansicht zum
Projekt (Klicken Sie zum Vergrößern auf das Bild)
Das HTML für die Ansicht sieht folgendermaßen aus:
<html >
<head runat="server">
<title>Hi There!</title>
Erstellen von Webanwendungen ohne Webformulare
Seite 3 von 14
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
</head>
<body>
<div>
<h1>Hello, <%= ViewData["Name"] %></h1>
</div>
</body>
</html>
Mehrere Dinge sollten Ihnen sofort auffallen. Es gibt keine runat ="Server"-Tags. Es gibt kein
Formulartag. Es gibt keine Steuerelementdeklarationen. Eigentlich sieht das Ganze eher nach
klassischem ASP als nach ASP.NET aus. Beachten Sie, dass MVC-Ansichten nur für das
Generieren von Ausgaben verantwortlich sind. Es sind also keine Ereignisverarbeitung oder
komplexen Steuerelemente erforderlich, wie es bei Webformularen der Fall ist.
Das MVC Framework verwendet das .aspx-Dateiformat als hilfreiche Textvorlagensprache.
Sie können sogar Codebehind verwenden, doch standardmäßig sieht die Codebehind-Datei
folgendermaßen aus:
using System;
using System.Web;
using System.Web.Mvc;
namespace HelloFromMVC.Views.Hello
{
public partial class HiThere : ViewPage
{
}
}
Es gibt keine Seiten-Init- oder Lademethoden, keine Ereignishandler, nichts außer der
Deklaration der Basisklasse, die nicht Page sondern ViewPage ist. Für eine MVC-Ansicht
reicht dies völlig aus. Führen Sie die Anwendung aus, navigieren Sie zu
http://localhost:<Port>/Hello/HiThere/Chris, und Sie erhalten eine Anzeige wie in
Abbildung 5.
Abbildung 5 Erfolgreiche MVC-Ansicht (Klicken Sie zum Vergrößern auf das Bild)
Wenn Sie statt Abbildung 5 eine Ausnahme sehen, geraten Sie nicht in Panik. Wenn die
Datei „HiThere.aspx“ in Visual Studio als aktives Dokument festgelegt ist, versucht
Visual Studio nach Drücken auf F5 direkt auf die .aspx-Datei zuzugreifen. Da MVCAnsichten erfordern, dass der Controller vor der Anzeige ausgeführt wird, können Sie nicht
direkt zur Seite navigieren. Bearbeiten Sie einfach die URL, damit sie aussieht wie in
Abbildung 5, und es müsste funktionieren.
Woher wusste das MVC Framework, dass die Aktionsmethode aufgerufen werden soll? Es
gab nicht einmal eine Dateierweiterung für diese URL. Die Antwort ist URL-Routing. Wenn
Sie sich die Datei „global.asax.cs“ anschauen, sehen Sie den Code, der in Abbildung 6
dargestellt ist. Die globale RouteTable speichert eine Sammlung von Route-Objekten. Jede
Route beschreibt ein URL-Formular und wozu es dient. Standardmäßig werden der Tabelle
zwei Routen hinzugefügt. Die erste ist der eigentliche Trick. Sie bestimmt, dass für jede URL,
die aus drei Teilen nach dem Servernamen besteht, der erste ein Controllername sein sollte,
der zweite ein Aktionsname und der dritte der ID-Parameter:
Figure 6 Route-Tabelle
public class Global : System.Web.HttpApplication
{
Erstellen von Webanwendungen ohne Webformulare
Seite 4 von 14
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
protected void Application_Start(object sender, EventArgs e)
{
// Change Url= to Url="[controller].mvc/[action]/[id]"
// to enable automatic support on IIS6
RouteTable.Routes.Add(new Route
{
Url = "[controller]/[action]/[id]",
Defaults = new { action = "Index", id = (string)null },
RouteHandler = typeof(MvcRouteHandler)
});
RouteTable.Routes.Add(new Route
{
Url = "Default.aspx",
Defaults = new {
controller = "Home",
action = "Index",
id = (string)null },
RouteHandler = typeof(MvcRouteHandler)
});
}
}
Url = "[controller]/[action]/[id]"
Durch diese Standardroute wurde die HiThere-Methode aufgerufen. Erinnern Sie sich an
diese URL: http://localhost/Hello/HiThere/Chris? Diese Route hat „Hello“ dem Controller
zugeordnet, „HiThere“ der Aktion und „Chris“ der ID. Das MVC Framework hat dann eine
HelloController-Instanz erstellt, die HiThere-Methode aufgerufen und Chris als Wert des IDParameters übergeben.
Diese Standardroute bietet viel, doch Sie können auch Ihre eigenen Routen hinzufügen. Sie
möchten zum Beispiel eine wirklich freundliche Website erstellen, auf der die Benutzer
einfach ihren Namen eingeben, um persönlich begrüßt zu werden. Wenn Sie diese Route am
Anfang der Routingtabelle einfügen,
RouteTable.Routes.Add(new Route
{
Url = "[id]",
Defaults = new {
controller = "Hello",
action = "HiThere" },
RouteHandler = typeof(MvcRouteHandler)
});
dann können Sie einfach zu „http://localhost/Chris“ navigieren, und die Aktion ist nach wie
vor aufgerufen. Eine freundliche Begrüßung wird angezeigt.
Woher wusste das System, welcher Controller und welche Aktion aufgerufen werden sollten?
Die Antwort liegt im Standardparameter. Dieser verwendet die neue anonyme Typsyntax
C# 3.0, um ein Pseudowörterbuch zu erstellen. Das Standardobjekt auf der Route kann
willkürliche zusätzliche Informationen enthalten doch für MVC kann es auch einige bekannte
Einträge umfassen: Controller und Aktion. Wenn in der URL kein Controller oder keine
Aktion angegeben werden, dann wird der Name in den Standardeinstellungen verwendet.
Daher müssen sie nicht in der URL vorhanden sein, damit die Anforderung dem richtigen
Controller und der richtigen Aktion zugeordnet wird.
Noch eine Anmerkung: Erinnern Sie sich daran, dass ich gesagt habe, „am Anfang der
Tabelle einfügen“? Wenn dies am Ende der Tabelle eingefügt wird, erhalten Sie einen Fehler.
Routing funktioniert nach dem Prinzip, wer zuerst kommt, mahlt zuerst. Bei der Verarbeitung
von URLs arbeitet das Routingsystem die Tabelle von oben nach unten durch, und die erste
passende Route gewinnt. In diesem Fall passt die Standardroute „[controller]/[action]/[id]“,
Erstellen von Webanwendungen ohne Webformulare
Seite 5 von 14
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
weil es Standardwerte für die Aktion und die ID gibt. Folglich wird nach ChrisController
gesucht, und da kein Controller vorhanden ist, wird ein Fehler angezeigt.
Ein umfangreicheres Beispiel
Nach dieser Ausführung zu den Grundlagen von MVC Framework soll im Folgenden ein
umfangreicheres Beispiel erörtert werden, das nicht nur lediglich eine Zeichenfolge anzeigt.
Ein Wiki ist eine Website, die im Browser bearbeitet werden kann. Seiten können problemlos
hinzugefügt oder bearbeitet werden. Ich habe mithilfe des MVC Framework ein kleines
Beispiel-Wiki geschrieben. Der Bildschirm „Diese Seite bearbeiten“ wird in Abbildung 7
gezeigt.
Abbildung 7 Bearbeiten der Homepage (Klicken Sie zum Vergrößern auf das Bild)
Sehen Sie sich den Codedownload für diesen Artikel an, um die Implementierung der
zugrunde liegenden Wiki-Logik nachzuvollziehen. Hier soll erläutert werden, wie
MVC Framework das Platzieren des Wiki im Internet unterstützt hat. Zuerst habe ich die
URL-Struktur entworfen. Ich hatte folgendes Ziel:
•
•
•
•
/[pagename] zeigt die Seite mit diesem Namen an.
/[pagename]?Version=n zeigt die angeforderte Version der Seite an, wobei 0 = die
aktuelle Version, 1 = die vorherige ist und so weiter.
/Edit/[pagename] öffnet den Bearbeitungsbildschirm für diese Seite.
/CreateNewVersion/[pagename] ist die URL, an die gesendet wird, um eine
Bearbeitung einzureichen.
Sehen Sie sich zunächst die grundlegende Anzeige einer Wiki-Seite an. Dafür wurde eine
neue Klasse namens „WikiPageController“ erstellt. Anschließend wurde eine Aktion namens
„ShowPage“ hinzugefügt. Zunächst sah die WikiPageController-Klasse wie in Abbildung 8
aus. Die ShowPage-Methode ist ziemlich einfach. Die WikiSpace- und WikiPage-Klassen
repräsentieren jeweils einen Satz Wiki-Seiten und eine spezielle Seite (sowie deren
Überarbeitungen). Diese Aktion lädt das Modell hoch und ruft RenderView auf. Aber wozu
dient die Zeile „new WikiPageViewData“?
Figure 8 WikiPageController-Implementierung von ShowPage
public class WikiPageController : Controller
{
ISpaceRepository repository;
public ISpaceRepository Repository
Erstellen von Webanwendungen ohne Webformulare
Seite 6 von 14
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
{
get {
if (repository == null)
{
repository = new FileBasedSpaceRepository(
Request.MapPath("~/WikiPages"));
}
return repository;
}
set { repository = value; }
}
[ControllerAction]
public void ShowPage(string pageName, int? version)
{
WikiSpace space = new WikiSpace(Repository);
WikiPage page = space.GetPage(pageName);
RenderView("showpage",
new WikiPageViewData
{
Name = pageName,
Page = page,
Version = version ?? 0
});
}
}
Im vorherigen Beispiel wurde eine Möglichkeit gezeigt, Daten vom Controller zur Ansicht zu
übergeben: das ViewData-Wörterbuch. Wörterbücher sind praktisch, aber auch gefährlich. Sie
können beliebige Einträge enthalten, es wird kein IntelliSense® für die Inhalte bereitgestellt,
und weil das ViewData-Wörterbuch vom Typ „Wörterbuch<zeichenfolge, Objekt>“ ist, muss
alles konvertiert werden, um die Inhalte nutzen zu können.
Wenn Sie wissen, welche Daten in der Ansicht benötigt werden, können Sie stattdessen ein
stark typisiertes ViewData-Objekt verwenden. In diesem Fall wurde ein einfaches Objekt
erstellt, WikiPageViewData, wie in Abbildung 9 zu sehen ist. Dieses Objekt übergibt die
Wiki-Seiteninformationen zusammen mit einigen Dienstprogrammmethoden an die Ansicht,
um beispielsweise die HTML-Version des Wiki-Markups abzurufen.
Figure 9 WikiPageViewData-Objekt
public class WikiPageViewData {
public string Name { get; set; }
public WikiPage Page { get; set; }
public int Version { get; set; }
public WikiPageViewData() {
Version = 0;
}
public string NewVersionUrl {
get {
return string.Format("/CreateNewVersion/{0}", Name);
}
}
public string Body {
get { return Page.Versions[Version].Body; }
}
Erstellen von Webanwendungen ohne Webformulare
Seite 7 von 14
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
public string HtmlBody {
get { return Page.Versions[Version].BodyAsHtml(); }
}
public string Creator {
get { return Page.Versions[Version].Creator; }
}
public string Tags {
get { return string.Join(",", Page.Versions[Version].Tags); }
}
}
Die Ansichtsdaten sind nun definiert. Wie lassen sie sich verwenden? In
„ShowPage.aspx.cs“ sehen Sie Folgendes:
namespace MiniWiki.Views.WikiPage {
public partial class ShowPage : ViewPage<WikiPageViewData>
{
}
}
Beachten Sie, dass die Basisklasse nach dem Typ „ViewPage<WikiPageViewData>“ definiert
wurde. Die ViewData-Eigenschaft der Seite ist also vom Typ „WikiPageViewData“ und kein
Wörterbuch wie im vorangegangenen Beispiel.
Das eigentliche Markup in der .aspx-Datei ist relativ einfach:
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
AutoEventWireup="true" CodeBehind="ShowPage.aspx.cs"
Inherits="MiniWiki.Views.WikiPage.ShowPage" %>
<asp:Content
ID="Content1"
ContentPlaceHolderID="MainContentPlaceHolder"
runat="server">
<h1><%= ViewData.Name %></h1>
<div id="content" class="wikiContent">
<%= ViewData.HtmlBody %>
</div>
</asp:Content>
Beachten Sie, dass der Indizierungsoperator [] nicht verwendet wird, um auf ViewData zu
verweisen. Da nun ein stark typisiertes ViewData-Objekt vorliegt, kann stattdessen einfach
direkt auf die Eigenschaft zugegriffen werden. Es sind keine Datentypkonvertierungen
erforderlich, und durch Visual Studio wird IntelliSense bereitgestellt.
Aufmerksame Beobachter werden das Tag <asp:Content> in dieser Datei bemerkt haben.
Masterseiten funktionieren tatsächlich mit MVC-Ansichten. Dazu können Masterseiten auch
selbst Ansichten sein. Sehen Sie sich den Masterseiten-Codebehind an:
namespace MiniWiki.Views.Layouts
{
public partial class Site :
System.Web.Mvc.ViewMasterPage<WikiPageViewData>
{
}
}
Das dazugehörige Markup ist in Abbildung 10 aufgeführt. Im Moment erhält die Masterseite
genau das gleiche ViewData-Objekt wie die Ansicht. Die Basisklasse der Masterseite wurde
als „ViewMasterPage<WikiPageViewData>“ deklariert, um die richtige Art ViewData zu
erhalten. Von dort aus richten Sie die verschiedenen DIV-Tags ein, um die Seite anzulegen,
füllen die Versionsliste aus und beenden mit dem üblichen Inhaltsplatzhalter.
Figure 10 Site.Master
<%@ Master Language="C#"
AutoEventWireup="true"
Erstellen von Webanwendungen ohne Webformulare
Seite 8 von 14
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
CodeBehind="Site.master.cs"
Inherits="MiniWiki.Views.Layouts.Site" %>
<%@ Import Namespace="MiniWiki.Controllers" %>
<%@ Import Namespace="MiniWiki.DomainModel" %>
<%@ Import Namespace="System.Web.Mvc" %>
<html >
<head runat="server">
<title><%= ViewData.Name %></title>
<link href="http://../../Content/Site.css" rel="stylesheet"
type="text/css" />
</head>
<body>
<div id="inner">
<div id="top">
<div id="header">
<h1><%= ViewData.Name %></h1>
</div>
<div id="menu">
<ul>
<li><a href="http://Home">Home</a></li>
<li>
<%= Html.ActionLink("Edit this page",
new { controller = "WikiPage",
action = "EditPage",
pageName = ViewData.Name })%>
</ul>
</div>
</div>
<div id="main">
<div id="revisions">
Revision history:
<ul>
<%
int i = 0;
foreach (WikiPageVersion version in ViewData.Page.Versions)
{ %>
<li>
<a href="http://<%= ViewData.Name %>?version=<%= i %>">
<%= version.CreatedOn %>
by
<%= version.Creator %>
</a>
</li>
<% ++i;
} %>
</ul>
</div>
<div id="maincontent">
<asp:ContentPlaceHolder
ID="MainContentPlaceHolder"
runat="server">
</asp:ContentPlaceHolder>
</div>
</div>
</div>
</body>
</html>
Auch zu beachten ist der Aufruf an Html.ActionLink. Dies ist ein Beispiel für ein
Renderinghilfsprogramm. Die verschiedenen Ansichtsklassen haben zwei Eigenschaften,
Html und Url. Jede verfügt über nützliche Methoden, Teile des HTML auszugeben. In diesem
Fall nimmt Html.ActionLink ein Objekt (hier eines anonymen Typs) und führt es zurück
Erstellen von Webanwendungen ohne Webformulare
Seite 9 von 14
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
durch das Routingsystem. Dies erzeugt eine URL, die zum angegebenen Controller und zur
Aktion weiterleitet. Auf diese Weise führt der Link „Diese Seite bearbeiten“ immer zum
richtigen Ort, egal wie die Routen geändert werden.
Sie haben wahrscheinlich auch bemerkt, dass ich auf das manuelle Erstellen eines Links
zurückgreifen musste (die Links zu vorherigen Seitenversionen). Leider funktioniert das
aktuelle Routingsystem nicht so gut zum Erzeugen von URLs, wenn Abfragezeichenfolgen
verwendet werden. Dies dürfte in späteren Versionen des Frameworks behoben werden.
Erstellen von Formularen und Zurücksenden
Sehen Sie sich jetzt die EditPage-Aktion auf dem Controller an:
[ControllerAction]
public void EditPage(string pageName)
{
WikiSpace space = new WikiSpace(Repository);
WikiPage page = space.GetPage(pageName);
RenderView("editpage",
new WikiPageViewData {
Name = pageName,
Page = page });
}
Wieder macht die Aktion nicht viel – sie rendert lediglich die Ansicht mit der speziellen Seite.
In der Ansicht, die in Abbildung 11 zu sehen ist, wird es interessanter. Diese Datei erstellt ein
HTML-Formular, aber es gibt kein Runat ="Server". Das Url.Action-Hilfsprogramm wird
zum Erzeugen der URL verwendet, an die das Formular zurücksendet. Es gibt
unterschiedliche Anwendungen verschiedener HTML-Hilfsprogramme, beispielsweise
TextBox, TextArea und SubmitButton. Sie dienen erwartungsgemäß dazu, ein HTML für
verschiedene Eingabefelder zu generieren.
Figure 11 EditPage. aspx
<%@ Page Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
AutoEventWireup="true"
CodeBehind="EditPage.aspx.cs"
Inherits="MiniWiki.Views.WikiPage.EditPage" %>
<%@ Import Namespace="System.Web.Mvc" %>
<%@ Import Namespace="MiniWiki.Controllers" %>
<asp:Content ID="Content1"
ContentPlaceHolderID="MainContentPlaceHolder"
runat="server">
<form action="<%= Url.Action(
new { controller = "WikiPage",
action = "NewVersion",
pageName = ViewData.Name })%>" method=post>
<%
if (ViewContext.TempData.ContainsKey("errors"))
{
%>
<div id="errorlist">
<ul>
<%
foreach (string error in
(string[])ViewContext.TempData["errors"])
{
%>
<li><%= error%></li>
<% } %>
</ul>
</div>
<% } %>
Erstellen von Webanwendungen ohne Webformulare
Seite 10 von 14
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Your name: <%= Html.TextBox("Creator",
ViewContext.TempData.ContainsKey("creator") ?
(string)ViewContext.TempData["creator"] :
ViewData.Creator)%>
<br />
Please enter your updates here:<br />
<%= Html.TextArea("Body", ViewContext.TempData.ContainsKey("body") ?
(string)ViewContext.TempData["body"] :
ViewData.Body, 30, 65)%>
<br />
Tags: <%= Html.TextBox(
"Tags", ViewContext.TempData.ContainsKey("tags") ?
(string)ViewContext.TempData["tags"] :
ViewData.Tags)%>
<br />
<%= Html.SubmitButton("SubmitAction", "OK")%>
<%= Html.SubmitButton("SubmitAction", "Cancel")%>
</form>
</asp:Content>
Lästiger bei der Webprogrammierung sind Fehler in einem Formular. Sie möchten
Fehlermeldungen anzeigen, jedoch die zuvor eingegebenen Daten beibehalten. Es ist uns allen
schon passiert, dass wir in einem Formular mit 35 Feldern einen Fehler gemacht haben,
woraufhin eine Reihe von Fehlermeldungen und ein neues, leeres Formular angezeigt werden.
MVC Framework bietet TempData als einen Ort zum Speichern der zuvor eingegebenen
Informationen an, um das Formular erneut auszufüllen. Dadurch wurde ViewState in
Webformularen sehr einfach, da das Speichern der Inhalte von Steuerelementen mehr oder
weniger automatisch erfolgte.
Das Gleiche soll in MVC erreicht werden, und dabei ist TempData von Nutzen. TempData ist
ein Wörterbuch, ähnlich wie das nicht typisierte ViewData. Die Inhalte von TempData
werden jedoch nur für eine einzige Anforderung gespeichert und anschließend gelöscht. In
Abbildung 12, in der NewVersion-Aktion sehen Sie, wie dies funktioniert.
Figure 12 NewVersion-Aktion
[ControllerAction]
public void NewVersion(string pageName) {
NewVersionPostData postData = new NewVersionPostData();
postData.UpdateFrom(Request.Form);
if (postData.SubmitAction == "OK") {
if (postData.Errors.Length == 0) {
WikiSpace space = new WikiSpace(Repository);
WikiPage page = space.GetPage(pageName);
WikiPageVersion newVersion = new WikiPageVersion(
postData.Body, postData.Creator, postData.TagList);
page.Add(newVersion);
} else {
TempData["creator"] = postData.Creator;
TempData["body"] = postData.Body;
TempData["tags"] = postData.Tags;
TempData["errors"] = postData.Errors;
RedirectToAction(new {
controller = "WikiPage",
action = "EditPage",
pageName = pageName });
return;
}
}
RedirectToAction(new {
Erstellen von Webanwendungen ohne Webformulare
Seite 11 von 14
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
controller = "WikiPage",
action = "ShowPage",
pageName = pageName });
}
Zunächst wird ein NewVersionPostData-Objekt erstellt. Dies ist ein weiteres Hilfsobjekt mit
Eigenschaften und Methoden, die die Inhalte des Beitrags speichern, sowie einer Überprüfung.
Um das postData-Objekt zu laden, wird eine Hilfsfunktion aus dem MVC Toolkit verwendet.
UpdateFrom ist eigentlich eine Erweiterungsmethode, die vom Toolkit bereitgestellt wird,
und es wird Reflektion verwendet, um die Namen der Formularfelder an die Namen der
Eigenschaften auf dem Objekt anzupassen. Das Ergebnis ist, dass alle Feldwerte in das
postData-Objekt geladen werden. Die Verwendung von UpdateFrom hat jedoch den Nachteil,
dass Formulardaten direkt von HttpRequest abgerufen werden, wodurch Komponententests
erschwert werden.
NewVersion überprüft zunächst SubmitAction. Dies wird als „OK“ angezeigt, wenn der
Benutzer auf die Schaltfläche „OK“ geklickt hat und die bearbeitete Seite senden will. Gibt es
hier einen anderen Wert, leitet die Aktion zu ShowPage zurück, wodurch die ursprüngliche
Seite erneut angezeigt wird.
Wenn der Benutzer auf „OK“ geklickt hat, prüfen Sie die postData.Errors-Eigenschaft. Diese
führt einige einfache Prüfungen der Beitragsinhalte aus. Sind keine Fehler vorhanden, wird
die neue Version der Seite verarbeitet und in das Wiki geschrieben. Sind jedoch Fehler
vorhanden, wird es interessant.
In diesem Fall legen Sie die verschiedenen Felder des TempData-Wörterbuchs so fest, das sie
die Inhalte von PostData enthalten. Dann leiten Sie zur Bearbeitungsseite zurück. Da
TempData jetzt festgelegt ist, zeigt die Seite erneut das Formular an, diesmal initialisiert mit
den vom Benutzer zuvor gesendeten Werten.
Die Verarbeitung von Beiträgen, die Prüfung und TempData sind relativ arbeitsintensiv und
erfordern mehr manuelle Arbeit als wirklich nötig ist. Zukünftige Versionen sollten
Hilfsmethoden enthalten, die zumindest einen Teil der Überprüfung von TempData
automatisieren. Ein letzter Hinweis zu TempData: Die Inhalte von TempData werden in der
serverseitigen Benutzersitzung gespeichert. Wenn Sie die Sitzung ausschalten, funktioniert
TempData nicht.
Controllererstellung
Die Grundlagen des Wiki funktionieren jetzt, doch es gibt einige Unklarheiten in der
Implementierung, die noch verdeutlicht werden sollen. Zum Beispiel wird die RepositoryEigenschaft verwendet, um die Logik des Wiki vom physischen Speicher zu entkoppeln. Sie
können Repositorys bereitstellen, die Inhalte im Dateisystem speichern (so wie in diesem
Beispiel), in einer Datenbank oder wo immer Sie möchten. Dabei sind leider zwei Probleme
zu lösen:
Zunächst ist die Controllerklasse eng mit der konkreten FileBasedSpaceRepository-Klasse
gekoppelt. Es ist ein Standard erforderlich, der verwendet werden kann, wenn die Eigenschaft
nicht festgelegt ist. Was noch schlimmer ist, der Pfad zu den Dateien auf dem Datenträger ist
hier ebenfalls hartcodiert. Dies sollte zumindest aus der Konfiguration hervorgehen.
Zweitens ist das Repository tatsächlich eine erforderliche Abhängigkeit, ohne die das Objekt
nicht ausgeführt wird. Ein guter Entwurf verdeutlicht, dass das Repository ein
Konstruktorparameter sein sollte und keine Eigenschaft. Doch es kann dem Konstruktor nicht
hinzugefügt werden, weil das MVC Framework einen argumentlosen Konstruktor auf
Controllern erfordert.
Glücklicherweise gibt es eine Erweiterungsmöglichkeit, die Ihnen aus der Klemme hilft: die
Controller Factory. Eine Controller Factory dient zum Erstellen von Controllerinstanzen. Sie
müssen lediglich eine Klasse erstellen, die die IControllerFactory-Schnittstelle implementiert
Erstellen von Webanwendungen ohne Webformulare
Seite 12 von 14
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
und beim MVC System registrieren. Sie können Controller Factorys für alle Controller oder
nur für spezielle Typen registrieren. Abbildung 13 zeigt eine Controller Factory für
WikiPageController, die das Repository jetzt als Konstruktorparameter übergibt.
Figure 13 Controller Factory
public class WikiPageControllerFactory : IControllerFactory {
public IController CreateController(RequestContext context,
Type controllerType)
{
return new WikiPageController(
GetConfiguredRepository(context.HttpContext.Request));
}
private ISpaceRepository GetConfiguredRepository(IHttpRequest request)
{
return new FileBasedSpaceRepository(request.MapPath("~/WikiPages"));
}
}
In diesem Fall ist die Implementierung ziemlich einfach, dadurch können jedoch die
Erstellungscontroller aktiviert werden, die erheblich leistungsfähigere Tools verwenden (in
bestimmten Dependency Injection-Containern). Jetzt haben Sie alle Informationen, um die
Abhängigkeiten für den Controller in ein Objekt zu trennen, das leichter verwaltet und
gewartet werden kann.
Als letzter Schritt muss die Factory beim Framework registriert werden. Sie erledigen dies
über die Klasse ControllerBuilder, indem Sie in der Application_Start-Methode der
Global.asax.cs-Klasse folgende Zeile hinzufügen (entweder vor oder nach den Routen):
ControllerBuilder.Current.SetControllerFactory(
typeof(WikiPageController), typeof(WiliPageControllerFactory));
Dadurch wird eine Factory für WikiPageController registriert. Wenn es in diesem Projekt
andere Controller gibt, wird diese Factory nicht verwendet, da sie nur für den
WikiPageController-Typ registriert ist. Sie können auch SetDefaultControllerFactory aufrufen,
wenn Sie eine Factory festlegen wollen, die für jeden Controller verwendet werden soll.
Andere Erweiterungspunkte
Die Controller Factory ist nur der Anfang der Frameworkerweiterbarkeit. In diesem Artikel
kann nicht ins Detail gegangen werden, daher wird nur auf die Höhepunkte verwiesen. Wenn
Ihre Ausgabe nicht HTML sein soll oder wenn Sie ein anderes Vorlagenmodul als
Webformulare verwenden möchten, können Sie das ViewFactory-Objekt des Controllers
anders einrichten. Sie können die IViewFactory-Schnittstelle implementieren und erhalten
dann vollständige Kontrolle über die Generierung der Ausgabe. Dies ist zum Generieren von
RSS, XML oder sogar Grafik nützlich.
Wie Sie bereits gesehen haben, ist das Routingsystem ziemlich flexibel. Doch im
Routingsystem ist nichts für MVC spezifisch. Jede Route verfügt über eine RouteHandlerEigenschaft. Bisher habe ich diese immer auf MvcRouteHandler festgelegt. Aber es ist
möglich, die IRouteHandler-Schnittstelle zu implementieren und das Routingsystem mit
anderen Webtechnologien zu koppeln. Eine zukünftige Version des Frameworks soll einen
WebFormsRouteHandler enthalten. Andere Technologien werden in Zukunft die Vorteile des
generischen Routingsystems nutzen.
Controller müssen nicht von System.Web.Mvc.Controller ableiten. Ein Controller muss
lediglich die Schnittstelle IController implementieren, die nur über eine einzige Methode
namens „Execute“ verfügt. Von da aus können Sie beliebig vorgehen. Wenn Sie andererseits
nur einige Verhaltensweisen der grundlegenden Controller-Klasse anpassen möchten, bietet
der Controller viele virtuelle Funktionen, die überschrieben werden können:
Erstellen von Webanwendungen ohne Webformulare
Seite 13 von 14
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
•
•
•
Mit OnPreAction, OnPostAction und OnError können Sie generische Vor- und
Nachbearbeitung mit jeder Aktion koppeln, die ausgeführt wird. OnError bietet einen
Controller-übergreifenden Fehlerbehandlungsmechanismus.
HandleUnknownAction wird aufgerufen, wenn eine URL an den Controller
weitergeleitet wird, dieser Controller jedoch die in der Route angeforderte Aktion
nicht ausführt. Standardmäßig führt diese Methode zu einer Ausnahme, aber Sie
können sie außer Kraft setzen und somit nach Belieben anpassen.
InvokeAction ist die Methode, die herausfindet, welche Aktionsmethode aufgerufen
werden soll, und sie aufruft. Wenn Sie den Prozess anpassen möchten (zum Beispiel
um die Anforderung für die Attribute [ControllerAction] zu beseitigen), können Sie
dies hier tun.
Es gibt weitere virtuelle Methoden auf dem Controller, doch dies sind vorrangig
Testmöglichkeiten und keine Erweiterungspunkte. Zum Beispiel ist RedirectToAction virtuell,
damit Sie eine abgeleitete Klasse erstellen können, die nicht wirklich umleitet. So können Sie
Aktionen testen, die umleiten, ohne dass ein kompletter Webserver ausgeführt werden muss.
Abschied von Webformularen?
Jetzt fragen Sie sich vielleicht: „Was passiert mit den Webformularen? Werden sie durch
MVC ersetzt?“ Die Antwort lautet „Nein“! Webformulare sind eine geläufige Technologie,
und Microsoft wird sie weiter unterstützen und verbessern. Es gibt viele Anwendungen, bei
denen Webformulare sehr gut funktionieren. Die Berichterstattungsanwendung für
Intranetdatenbanken kann zum Beispiel mithilfe von Webformularen in einem Bruchteil der
Zeit erstellt werden, die für das Schreiben in MVC erforderlich wäre. Zudem unterstützen
Webformulare zahlreiche Steuerelemente, von denen viele hoch entwickelt sind und eine
Menge Arbeit ersparen.
Wann sollten Sie also MVC den Webformularen vorziehen? Häufig ist das abhängig von
Ihren Anforderungen und Vorlieben. Haben Sie Schwierigkeiten, Ihre URLs nach Ihren
Vorlieben anzupassen? Möchten Sie Komponententests auf Ihrer Benutzeroberfläche
durchführen? Beide dieser Szenarios legen die Verwendung von MVC nahe. Arbeiten Sie
vielleicht viel mit der Darstellung von Daten, mit bearbeitungsfähigen Rastern und
aufwändigen Strukturansichtsteuerelementen? Dann sind wahrscheinlich im Moment
Webformulare die bessere Wahl.
Mit der Zeit wird MVC Framework wahrscheinlich im Bereich der
Benutzeroberflächensteuerung aufholen, doch vermutlich wird der Einstieg nie so einfach sein
wie mit Webformularen, bei denen zahlreiche Funktionen per Drag & Drop zugänglich sind.
Doch bis dahin bietet ASP.NET MVC Framework Webentwicklern eine neue Möglichkeit,
Webanwendungen in Microsoft .NET Framework zu erstellen. Das Framework wurde für
Testfähigkeit entworfen, nutzt HTTP anstatt es zu abstrahieren und kann an fast jedem Punkt
erweitert werden. Es ist eine notwendige Ergänzung zu Webformularen für die Entwickler,
die vollständige Kontrolle über ihre Webanwendungen erhalten möchten.
Erstellen von Webanwendungen ohne Webformulare
Seite 14 von 14
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Exploring Windows Communication
Foundation
By mastergaurav
http://www.codeproject.com/KB/WCF/edujini_wcf_scart_01.aspx
Case Study
We shall take a simple shopping cart as a case-study. We start with simple data-types and
slowly build on to work with custom, more complex data-types. In the sequence of articles,
we shall explore various key aspects of WCF.
Service Definition
Our shopping-cart service, to begin with, publishes the following operations:
bool
CheckUserExists(string username);
DateTime? GetLastTransactionTime(string username);
The job of the first operation is to check whether or not the user is registered in our database
or not. The second operation returns the date and time of the last transaction done by the last
user, if any. Yes, you note the operation correctly, the return type is not System.DateTime
but System.DateTime?, the C# syntax for System.Nullable<System.DateTime>.
To publish any service in WCF, we require four steps in general:
1.
2.
3.
4.
Service contract definition
Contract implementation
Service configuration
Service hosting
Service Contract Definition
In WCF, service contract is the term used for the final service being published. Note that a
service may have one or more related operations, referred to as operation contracts in WCF.
If you are working in Visual Studio, create a new project of type "Class Library". Yes, you
read it right, it is "Class Library" and not "WCF Service Library".
We start by creating an interface IShoppingCartService as follows:
using DateTime;
namespace ShoppingCartLibrary
{
public interface IShoppingCartService
{
bool CheckUserExists(string username);
Exploring Windows Communication Foundation
Seite 1 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
DateTime? GetLastTransactionTime(string username);
}
}
Contract Implementation
The next obvious step is to provide implementation to the service. We create a class
ShoppingCartImpl that implements the interface IShoppingCartService.
using DateTime;
namespace ShoppingCartLibrary
{
public class ShoppingCartImpl : IShoppingCartService
{
public bool CheckUserExists(string username)
{
if(username == "gvaish" || username == "edujini")
{
return true;
}
return false;
}
public DateTime? GetLastTransactionTime(string username)
{
if(username == "gvaish")
{
return DateTime.Now.AddDays(-3);
}
if(username == "edujini")
{
return DateTime.Now.AddHours(-3);
}
return null;
}
}
}
Service Configuration
We need configuration at two levels:
1. Using attributes, we need to tell WCF about the service contract and operation
contracts. The information would be used to define service, operations, messages and
types
2. Using configuration entries, we need to tell WCF about the implementation class to be
used and binding details. These details would be used for hosting purposes.
Contract Configuration
If you are using Visual Studio, add a reference to the assembly System.ServiceModel.dll from
GAC. Add the attribute System.ServiceModel.ServiceContractAttribute to the
interface. It tells the WCF that there is a service associated with the corresponding type. Add
the attribute System.ServiceModel.OperationContractAttribute to the methods in the
Exploring Windows Communication Foundation
Seite 2 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
interface. It informs the WCF about the operations to be published in the service. It will take
care of messages and types associated on its own.
So, our final code looks as follows:
using DateTime;
using ServiceModel;
namespace ShoppingCartLibrary
{
[ServiceContract]
public interface IShoppingCartService
{
[OperationContract]
bool CheckUserExists(string username);
[OperationContract]
DateTime? GetLastTransactionTime(string username);
}
}
Service Configuration
We shall provide these details to our hosting application.
Compile the files to the assembly. I name it ShoppingCartLibrary.dll. If you are
compiling the code at the commandline, do not forget to add reference to
System.ServiceModel.dll.
Service Hosting
Having defined our service completely, we need a runtime (engine) to host the service - an
application that can allow the clients to connect and invoke the operations. For this, we shall
create a console application. The application has just one class, MainClass, with the Main
method.
For Visual Studio users, we name the project as ShoppingCartHost. Rename the class
to MainClass.
Program
Add a reference to System.ServiceModel.dll and to the ShoppingCartLibrary.dll Write the
following code to the MainClass.
Collapse
using System;
using System.ServiceModel;
using ShoppingCartLibrary;
namespace ShoppingCartHost
{
public class MainClass
{
public static void Main(string[] args)
{
Uri uri = new Uri("http://localhost:8080/ShoppingCartService");
Exploring Windows Communication Foundation
Seite 3 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
using(ServiceHost host = new ServiceHost
(typeof(ShoppingCartImpl), uri))
{
host.Open();
Console.Write("Hit <Enter> to stop service...");
Console.ReadLine();
}
}
}
}
Now, we need the service configuration as mentioned earlier. Write the following code to the
application configuration file.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="HttpGetBehaviour">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="ShoppingCartLibrary.ShoppingCartImpl"
behaviorConfiguration="HttpGetBehaviour">
<endpoint binding="mexHttpBinding"
contract="ShoppingCartLibrary.IShoppingCartService"
address=""/>
</service>
</services>
</system.serviceModel>
</configuration>
Compile your code. Start the console application. Browse to the location
http://localhost:8080/ShoppingCartService.
Exploring Windows Communication Foundation
Seite 4 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Hurray! We are ready with our first service. Let's now design a client to consume the service.
Client Application
For our client, we need two items: the proxy client code and the configuration items. No no,
you don't write any of them. You can generate them using the WCF tool svcutil.exe. You can
locate this tool in the folder where your Visual Studio IDE is installed (default location
being %PROGRAMFILES%\Microsoft Visual Studio 8\Common7\IDE) or where the .NET
3.0 SDK is installed (default location being %PROGRAMFILES%\Microsoft Windows
SDK\v6.0\bin).
Ensure that the location to svcutil.exe is in your path. Invoke the following at commandline.
svcutil.exe /nologo /config:App.config
http://localhost:8080/ShoppingCartService?wsdl
It will generate two files: ShoppingCartImpl.cs and App.config. We just need a MainClass
with Main method on the client side to consume the service published. Write the following
code to the class:
using System;
namespace ShoppingCartConsumer
{
public class MainClass
{
public static void Main(string[] args)
{
Exploring Windows Communication Foundation
Seite 5 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
ShoppingCartServiceClient client = new
ShoppingCartServiceClient();
string[] users = new string[] { "gvaish", "edujini", "other" };
foreach(string user in users)
{
Console.WriteLine("User: {0}", user);
Console.WriteLine("\tExists: {0}",
client.CheckUserExists(user));
DateTime? time = client.GetLastTransactionTime(user);
if(time.HasValue)
{
Console.WriteLine("\tLast Transaction: {0}",
time.Value);
} else
{
Console.WriteLine("\tNever transacted");
}
}
Console.WriteLine();
}
}
}
Don't forget to add a reference to System.ServiceModel.dll. Build your project. Execute the
client. You should get an output similar to that shown below:
Note that this is only a start... so, please have a little patience if you are looking for advanced
topics.
Notes
We chose to have an interface as our service contract. In general, we can have a class directly
as a service contract. However, it is always a good idea to have an interface and then provide
an implementation. In this way, we can keep and maintain the declaration of the contract and
implementation of the contract independently and modularly.
Summary
With WCF, it is possible to publish and consume web services in a very simple way.
Publishing a web service is not as simple as in ASP.NET. However, as may be seemingly
noticeable, the WCF may turn out to be much more flexible than ASP.NET service. One of
the flexibilities that we saw was to host it outside ASP.NET Runtime.
Exploring Windows Communication Foundation
Seite 6 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Windows Workflow Foundation
Dino Esposito
http://msdn.microsoft.com/de-de/magazine/cc163640(en-us).aspx
In the January 2006 issue, Don Box and Dharma Shukla introduced Windows® Workflow
Foundation and discussed the overall architecture of the framework and its constituent
components ( WinFX Workflow: Simplify Development With The Declarative Model Of
Windows Workflow Foundation). It inspired me to take this topic one step further and discuss
how you can use Windows Workflow Foundation to address a common business scenario
where automatic processes intersect with human activity. It provides a framework for
developing and executing a wide range of applications based on complex processes. Typical
examples are document management, business-to-business, and consumer applications. You
can use Visual Studio® 2005 to help design the underlying workflow in addition to the toplevel applications and assemblies involved.
A Common Business Scenario
Organizations typically have a number of internal processes for tasks such as order processing,
purchase requests, travel expenses, and the like. Workflow brings order to these independent
processes in a transparent, dynamic, and robust fashion.
Let's consider a typical helpdesk workflow process. It begins when the helpdesk employee
gets a customer call and opens a ticket recording the name of the customer, the time of the call,
and a brief description of the issue. Once the ticket has been created, the employee forgets
about it and waits for another incoming call. At the end of the day, he logs off the computer
and goes home. At the same time, in another department, perhaps in another city, a bunch of
technicians are alerted to open issues. Each active technician picks up a request and either
solves it or escalates it to a second level of help. How would you write code to implement this
process?
You might have a Windows Forms application that collects input data about the call and
creates a record in a database—the ticket, with time, description, status, and a unique ID.
Users of a second Windows Forms application will see a real-time list of pending requests and
pick one up. The operator then does whatever is in his power to resolve the issue (call back
the customer, retrieve requested information, send e-mail, or perform some remote activity)
and indicates whether the problem was solved or will need to be investigated further. This
decision can be represented with an imperative action such as clicking a button to update the
ticket in the same underlying database. Finally, if there's another category of users to involve,
a custom made front end will give them a chance to indicate that the issue has been
successfully closed or aborted.
Although this process clearly represents a workflow with some human-driven decision points,
it can be easily implemented with traditional sequential code written with classic
programming languages and databases.
Applying Windows Workflow Foundation
When you have a workflow-based system composed of activities, like you have with
Windows Workflow Foundation, you can implement an application using a powerful mix of
imperative code and declarative maps of activities and the declarative rules that bind them.
The major benefit is that you can model (even visually) the solution and have Windows
Workflow embed a run-time server to interpret your graph and proceed along the links that
you defined among building blocks. The more complex the process, the simpler it is to devise
Windows Workflow Foundation
Seite 1 von 8
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
and implement a flow for it. The more dynamically changeable the process, the less code
you'll need to write and maintain. Let's see how to implement a Windows Workflow
Foundation solution for the helpdesk scenario.
The Helpdesk Solution
The helpdesk workflow I've built begins by creating a ticket and then stop while waiting for a
response from a connected user or technician. Whether the ticket is closed or escalated, the
workflow gets an external event and updates the internal state of the application to track the
event. In light of this, the workflow requires an interaction with the outside world. These
kinds of asynchronous activities are one of the problems inherent in real-world workflow
processes that Windows Workflow Foundation addresses. Because interaction with an entity
outside the system is required, the host application and the workflow can define a contract for
any data exchange that proves necessary. The IHelpDeskService interface you see here
describes the communication interface that is established between the workflow and its host:
[DataExchangeService]
public interface IHelpDeskService
{
event EventHandler<HelpDeskTicketEventArgs> TicketClosed;
event EventHandler<HelpDeskTicketEventArgs> TicketEscalated;
void CreateTicket( string description, string userRef, string createdBy);
void CloseTicket(string ticketID); void EscalateTicket(string ticketID);
}
Now let's begin by writing a service class named HelpDeskService, as shown in Figure 1.
The HelpDeskService class is added to the workflow runtime and represents the point of
contact between the host application and the workflow. The host application invokes the
public methods on the class to fire external events to the workflow. Fired events signal
domain-specific events that will guide the flow of operations. Methods on the
IHelpDeskService interface represent the only pieces of code that a workflow can invoke on
external components through the InvokeMethod activity. Factored out in this way, the logic of
the HelpDeskService adds a lot of flexibility to the workflow as it basically lets the workflow
call back the host for the actual implementation of basic actions. By including a class that
implements the IHelpDeskService interface different hosts can create, solve, or escalate
tickets using different algorithms and storage media while keeping the workflow logic intact.
You can compile the interface and the class in a distinct assembly or just keep these files
inside the same assembly where the workflow is defined.
Figure 1 Implementing Workflow with HelpDeskService
using System;
using System.Threading;
using System.Workflow.ComponentModel;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Messaging;
namespace MySamples
{
public class HelpDeskService : IHelpDeskService
{ // Implement events
public event EventHandler<HelpDeskTicketEventArgs> TicketClosed;
public event EventHandler<HelpDeskTicketEventArgs> TicketEscalated;
public void RaiseCloseTicketEvent(Guid instanceId)
{ // Raise the event to the workflow
HelpDeskTicketEventArgs args = new HelpDeskTicketEventArgs(instanceId,
"");
if (TicketClosed != null)
TicketClosed(this, args);
}
public void RaiseEscalateTicketEvent(Guid instanceId)
{ // Raise the event to the workflow
Windows Workflow Foundation
Seite 2 von 8
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
HelpDeskTicketEventArgs args = new HelpDeskTicketEventArgs(instanceId,
"");
if (TicketEscalated != null)
TicketEscalated(this, args);
}
void IHelpDeskService.CreateTicket( string description, string userRef,
string createdBy)
{ // Fill up a ticket: same ID as the workflow
string ticketID = WorkFlowEnvironment.CurrentInstanceId.ToString();
// Create ticket on the DB
HelpDeskHelpers.CreateTicket( ticketID, description, userRef,
createdBy);
}
void IHelpDeskService.CloseTicket(string ticketID)
{ // Update ticket on the DB
HelpDeskHelpers.UpdateTicket(ticketID, TicketStatus.Closed);
}
void IHelpDeskService.EscalateTicket(string ticketID)
{ // Update ticket on the DB
HelpDeskHelpers.UpdateTicket(ticketID, TicketStatus.Escalated);
}
}
}
HelpDeskWorkflow is a sequential workflow hosted by a Windows Forms app. Figure 2
shows its graphical model. It begins with an InvokeMethod activity that causes the app to
create the ticket based on information provided by the user. After that, the ticket sits in a
database waiting for a technician to pick it up and either solve it or escalate it.
Figure 2 Helpdesk Workflow in Visual
Studio 2005
After creating the ticket, the workflow waits,
potentially for a long time—even hours or
days. What happens during this time? Should
the workflow instance stay loaded in
memory? If the workflow becomes idle, you
can unload it from memory—a process also
known as passivation. A local service, such
as the helpdesk service, remains the main
point of contact between the host application
and the sleeping workflow.
The Human Factor
When the human factor interacts with the
host application and does something to wake
up the workflow, the local service posts a
request to the runtime to resume the
passivated workflow. The Windows
Workflow Foundation toolbox contains an
activity, called Listen, that just idles the
workflow and listens for incoming wake-up calls. The block labeled WaitForSolution in
Figure 2 is an instance of the Listen activity.
Windows Workflow Foundation
Seite 3 von 8
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
The Listen activity is basically useless without one or more child branches, each representing
a possible event that can be hooked up at this point. In the helpdesk example, the Listen
activity contains two branches—ticket closed and ticket escalated. Each branch is an
EventDriven activity. Both Listen and EventDriven activities require no special settings and
are mere containers of child activities. To catch an external event, the workflow needs an
EventSink component.
In Figure 2, the TicketClosed block is an EventSink bound to the TicketClosed event in the
local helpdesk service. Figure 3 lists the EventSink properties set here. To begin, you select
the interface of the service that exposes the event. Setting the InterfaceType property is a
requirement; in addition, you can only select an interface decorated with the
[DataExchangeService] attribute, as in Figure 1.
Figure 3 EventSink Properties
Once the data exchange interface is set, the
EventName property is prefilled with all
events found on the interface. You pick up
the event of choice and set its parameters. If
you want a further notification about when
the event has been processed, you set the
Invoked property, too.
The EventSink returns control to the
workflow after a period of idleness due to
waiting for human intervention. Following
the event, you proceed with any further activity that suits the workflow. For example, in the
helpdesk application I call another method on the local service to update the status of the
ticket in the ticket database.
In a real-world scenario, there's at least one database the workflow needs to interact with,
directly or indirectly. In this example, I'm using a SQL Server™ 2000 table, Tickets, that
stores a row for each ticket being processed. By design, the ID of the ticket matches the ID of
the workflow that is used to manage it.
The Helpdesk Front End
Once compiled, the workflow is nothing more than a reusable assembly and thus can be
referenced by any type of .NET-based application. Let's now imagine a front-end application
for helpdesk operators. The application is a Windows Forms program that allows users to fill
in a form and create a ticket to initiate the workflow, as shown in Figure 4.
Figure 4 HelpDesk
Front-End App
The workflow calls into
the CreateTicket method
of the local service and
has the service add a
new record to the tickets
database. After the
operator has opened a
new ticket, he continues
Windows Workflow Foundation
Seite 4 von 8
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
answering phone calls and replying to e-mails while the workflow is started and becomes idle
waiting for a human intervention. Each ticket is represented with a different instance of the
workflow. For simplicity, the ID of the workflow is also the ID of the ticket.
State Persistence
At the end of the day, a workflow is a tree of activities, so how is its lifetime managed? The
Windows Workflow Foundation run-time engine manages the execution of any workflow and
allows workflows to remain active for a long time and even survive machine reboots. The
run-time engine is powered by pluggable services that provide an overall rich execution
environment—transactions, persistence, tracking, timer, and threading.
It's unlikely that you want your workflow to stay in the memory of the host process until some
intervention causes it to proceed. As mentioned, many real-world human-based workflows
may take hours or longer for this to happen. As in the preceding example, the helpdesk
operator starts the workflow and loads the workflow class inside the front-end application
process. That operator may then shut down his machine and go home. However, the ticket
must remain active and available to other operators, even from within another application. For
this to happen, the workflow must support serialization to a durable medium.
A workflow may be a long-running operation and it is impractical for that object to remain
active in memory for days. First of all, a host process can't generally afford to cache all the
objects that accumulate in days of continued activity; second, the host process might shut
down or reboot more than once.
The solution is to configure the runtime to unload workflows as they go idle. In this case, the
host program will use the following code to initialize the runtime:
WorkflowRuntime wr = new WorkflowRuntime();
wr.StartRuntime();
You can also set a few event handlers on the WorkflowRuntime class to run some code when
the workflow is idle, persisted, or unloaded. Configured in this way, the runtime will
automatically serialize the workflow to a storage medium as the workflow encounters a Listen
activity or enters a wait state. Another alternative is to instruct the host program to
programmatically persist the workflow at a well-known point of execution. In this case, you
call the EnqueueItemOnIdle method on the WorkflowInstance class. You get an instance of
the WorkflowInstance class as the return value of the CreateWorkflow method on the runtime class:
WorkflowInstance inst = theRuntime.CreateWorkflow(type); ...
inst.EnqueueItemOnIdle();
inst.Unload();
Just remember though that a call to EnqueueItemOnIdle doesn't necessarily persist the
workflow immediately. Persistence is immediate only if the workflow is idle or suspended.
Otherwise, the runtime takes note and persists the workflow instance later when the workflow
is suspended or idle. Note that EnqueueItemOnIdle is limited to saving the state of the
workflow instance and doesn't unload it from memory. To unload a workflow instance, you
need to call the Unload method. One of the standard run-time services supported by the
workflow runtime is the workflow persistence service. You turn it on through the code in
Figure 5.
Figure 5 Workflow Persistence Service
private WorkflowRuntime InitWorkflowRuntime()
{ // Get a new workflow runtime
WorkflowRuntime wr = new WorkflowRuntime();
// Add custom HelpDesk
service theHelpDeskService = new HelpDeskService();
wr.AddService(theHelpDeskService);
// Add system SQL state service
Windows Workflow Foundation
Seite 5 von 8
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
SqlWorkflowPersistenceService stateService = new
SqlWorkflowPersistenceService("Data Source=localhost;" + "Initial
Catalog=WFState;UID=...;");
wr.AddService(stateService);
// Start wr.StartRuntime();
return wr;
}
private void btnCreate_Click(object sender, EventArgs e)
{ // Fill the Parameters collection for this instance of the workflow
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("TicketDescription", txtDesc.Text);
parameters.Add("TicketUser", txtUser.Text);
parameters.Add("TicketCreatedBy", txtCreatedBy.Text);
// Get the type of the workflow
Type type = typeof(MySamples.HelpDeskWorkflow);
// Start the workflow instance
WorkflowInstance inst = theWorkflowRuntime.CreateWorkflow(type,
parameters);
// Feedback to the user
string msg = String.Format("The ticket '{0}' has been successfully " +
"created!", inst.InstanceId);
MessageBox.Show(msg, "HelpDesk", MessageBoxButtons.OK,
MessageBoxIcon.Information);
FillGrid();
}
The WorkflowPersistence service adds serialization capabilities to all the instances of
workflows that execute inside a given run-time engine. The workflow service core
functionality is represented by the WorkflowPersistenceService class. Windows Workflow
Foundation provides an implementation of it through the SqlWorkflowPersistenceService.
This class saves workflow data to a SQL Server 2000 or SQL Server 2005 database with a
well-known schema. There are a couple of scripts and dialog-based utilities that you can use
to easily create the target database. You can download everything you might need from
Windows Workflow Foundation Web. An instance of the SQLWorkflowPersistenceService
must be created and registered with the runtime before the runtime is actually started. The
constructor of the persistence service takes the connection string as its sole argument.
As mentioned, run-time services are a pluggable part of the overall Windows Workflow
Foundation architecture, so you can create your own services and replace standard services
with your own.
Resuming the Workflow Instance
In the scenario being discussed, another operator—the technician—is expected to pick up any
open tickets and do whatever he can to solve or escalate them. The technician will use another
application or, at least, another instance of the app shown in Figure 4. In both cases, another
instance of the workflow runtime must be created with persistence support and the right
workflow state must be loaded and resumed. As you can see, this can only happen if the ID of
the idled workflow has been saved. In the helpdesk sample app, by design, the ticket ID
matches the workflow ID and each ticket corresponds to an instance of the workflow created
to handle it.
Windows Workflow Foundation
Seite 6 von 8
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Figure 6 The Problem
Solver
The Problem Solver
application (see Figure
6) initializes the
workflow runtime using
nearly the same code as
the HelpDesk front-end
application in Figure 4.
The only minor
difference is that in the
Problem Solver
application I register
handlers for the
WorkflowLoaded and
WorkflowCompleted
events on the runtime:
WorkflowRuntime wr =
new
WorkflowRuntime(); wr.WorkflowLoaded += OnWorkflowLoaded;
wr.WorkflowCompleted += OnWorkflowCompleted;
The operator selects a ticket from the displayed list and works with the user who raised it.
When done, she clicks to solve or escalate the ticket, thus terminating the workflow. The
event handler should retrieve the saved instance of the workflow based on the ticket ID and
wake it up from the idle state. Here's the code you need:
string workflowID = (string)ticketList.SelectedValue;
Guid id = new Guid(workflowID);
Type type = typeof(MySamples.HelpDeskWorkflow);
try {
WorkflowInstance inst = theWorkflowRuntime.GetLoadedWorkflow(id);
theHelpDeskService.RaiseCloseTicketEvent(inst.InstanceId);
}
catch { ... }
The core part of the preceding code is in the call to the GetLoadedWorkflow method.
GetLoadedWorkflow takes the GUID that represents the workflow instance and retrieves it
from the configured durable medium, if it is not currently in memory. In this case, the
workflow is loaded into memory and scheduled for execution. This happens even if the
workflow instance has been previously aborted. The WorkflowLoaded event fires when the
workflow is loaded back into memory.
GetLoadedWorkflow returns an object of type WorkflowInstance that you can use for
inspecting the current status of the workflow as well as its activities. At this point, you raise
one of the events the workflow was waiting for. The corresponding EventSink activity will
trap the event and proceed until the end of the workflow is reached or a subsequent wait point
is encountered.
When the workflow completes, it is automatically removed from the Windows Workflow
Foundation persistence database. The WorkflowCompleted event fires when the workflow has
reached its end. In the helpdesk scenario, the workflow completes after the operator clicks to
solve or escalate (see Figure 6).
From a development perspective, it is essential to note two things. First, to raise an event to
the workflow, you have to post the request to a pooled thread:
public void RaiseCloseTicketEvent(Guid instanceId)
{
Windows Workflow Foundation
Seite 7 von 8
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
ThreadPool.QueueUserWorkItem(JustCloseTheTicket, new
HelpDeskTicketEventArgs(instanceId, "Jim"));
}
public void JustCloseTheTicket(object o)
{
HelpDeskTicketEventArgs args = o as HelpDeskTicketEventArgs;
if (TicketClosed != null)
TicketClosed(null, args);
}
Second, the WorkflowCompleted event may not be raised on the main Windows Forms thread,
so you cannot directly update any of the UI controls. (This is due to the fact that Windows
Forms controls are not thread-safe.) Updating Windows Forms controls from within a thread
different from the one which created them is definitely possible, but requires an indirect call
to the method that updates controls:
private delegate void UpdateListDelegate();
private void UpdateList()
{
this.Invoke(new UpdateListDelegate(FillList));
}
You call UpdateList from within the WorkflowCompleted event handler. The Invoke method
on the Form class guarantees that FillList gets called on the right thread so that any control
can be safely refreshed.
Conclusion
Windows Workflow Foundation allows you to visually design complex algorithms to solve
business problems and model processes. A workflow is a tool used to describe a flow of data
and actions. Therefore, any scenario where an IF or a WHILE statement are required can be a
workflow. However, nobody would ever use a workflow for just an IF statement; the
workflow runtime does have a cost that can be amortized as the complexity of the flow grows
beyond a given threshold.
Where's the boundary that delimits the cost effective use of a workflow? The helpdesk
scenario, as implemented in this column, is probably too simple for a workflow and could
have been implemented in an equivalent way by employing a tickets database—even the same
tickets database that the workflow deals with through the helpdesk service.
The real benefit of a workflow-based solution consists of making complex processes simpler
to model and implement, and, more importantly, evolve and extend. Windows Workflow
Foundation provides a managed execution environment for Windows Workflow programs. It
provides durability, robustness, suspension/resumption, and compensation characteristics to
the programs. In a sense, activities are analogous to intermediate language (IL) opcodes or
program statements, but contain domain-specific wisdom. In short, Windows Workflow
Foundation makes your program semantics declarative and explicit, and it lets you model
your applications close to the real-world process. It's the right tool for the job. You wouldn't
write a front-end visual application using IL, but rather you would use a RAD development
tool and a more human-readable language. The Windows Workflow Foundation SDK
provides an extensible programming language designed for modeling complex business
processes, especially when these processes may evolve over time. In this case, the key benefit
is that you add ad hoc activities to address the flow of work and use built-in behavioral
activities to control that flow. You focus on tasks, the runtime does the rest.
Windows Workflow Foundation
Seite 8 von 8
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Garbage Collection in .NET
Quelle: http://www.codeproject.com/kb/dotnet/garbagecollection.aspx
.NET is the much hyped revolutionary technology gifted to the programmer's community by
Microsoft. Many factors make it a must use for most developers. In this article we would like
to discuss one of the primary advantages of .NET framework - the ease in memory and
resource management.
About garbage collection
Every program uses resources of one sort or another-memory buffers, network connections,
database resources, and so on. In fact, in an object-oriented environment, every type identifies
some resource available for a program's use. To use any of these resources, memory must be
allocated to represent the type.
The steps required to access a resource are as follows:
1. Allocate memory for the type that represents the resource.
2. Initialize the memory to set the initial state of the resource and to make the resource
usable.
3. Use the resource by accessing the instance members of the type (repeat as necessary).
4. Tear down the state of the resource to clean up.
5. Free the memory.
The garbage collector (GC) of .NET completely absolves the developer from tracking
memory usage and knowing when to free memory. The Microsoft -- .NET CLR (Common
Language Runtime) requires that all resources be allocated from the managed heap. You
never free objects from the managed heap-objects are automatically freed when they are no
longer needed by the application. Memory is not infinite. The garbage collector must perform
a collection in order to free some memory. The garbage collector's optimizing engine
determines the best time to perform a collection, (the exact criteria is guarded by Microsoft)
based upon the allocations being made. When the garbage collector performs a collection, it
checks for objects in the managed heap that are no longer being used by the application and
performs the necessary operations to reclaim their memory. However for automatic memory
management, the garbage collector has to know the location of the roots i.e. it should know
when an object is no longer in use by the application. This knowledge is made available to the
GC in .NET by the inclusion of a concept know as metadata. Every data type used in .NET
software includes metadata that describes it. With the help of metadata, the CLR knows the
layout of each of the objects in memory, which helps the Garbage Collector in the compaction
phase of Garbage collection. Without this knowledge the Garbage Collector wouldn't know
where one object instance ends and the next begins.
Garbage Collection Algorithm
Application Roots
Every application has a set of roots. Roots identify storage locations, which refer to objects on
the managed heap or to objects that are set to null.
Garbage Collection in .NET
Seite 1 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
For example:
•
•
•
•
•
All the global and static object pointers in an application.
Any local variable/parameter object pointers on a thread's stack.
Any CPU registers containing pointers to objects in the managed heap.
Pointers to the objects from Freachable queue
The list of active roots is maintained by the just-in-time (JIT) compiler and common
language runtime, and is made accessible to the garbage collector's algorithm.
Implementation
Garbage collection in .NET is done using tracing collection and specifically the CLR
implements the Mark/Compact collector.
This method consists of two phases as described below.
Phase I: Mark
Find memory that can be reclaimed. When the garbage collector starts running, it makes the
assumption that all objects in the heap are garbage. In other words, it assumes that none of the
application's roots refer to any objects in the heap.
The following steps are included in Phase I:
1. The GC identifies live object references or application roots.
2. It starts walking the roots and building a graph of all objects reachable from the roots.
3. If the GC attempts to add an object already present in the graph, then it stops walking
down that path. This serves two purposes. First, it helps performance significantly
since it doesn't walk through a set of objects more than once. Second, it prevents
infinite loops should you have any circular linked lists of objects. Thus cycles are
handles properly.
Once all the roots have been checked, the garbage collector's graph contains the set of all
objects that are somehow reachable from the application's roots; any objects that are not in the
graph are not accessible by the application, and are therefore considered garbage.
Phase II: Compact
Move all the live objects to the bottom of the heap, leaving free space at the top. Phase II
includes the following steps:
1. The garbage collector now walks through the heap linearly, looking for contiguous
blocks of garbage objects (now considered free space).
2. The garbage collector then shifts the non-garbage objects down in memory, removing
all of the gaps in the heap.
3. Moving the objects in memory invalidates all pointers to the objects. So the garbage
collector modifies the application's roots so that the pointers point to the objects' new
locations.
4. In addition, if any object contains a pointer to another object, the garbage collector is
responsible for correcting these pointers as well.
Garbage Collection in .NET
Seite 2 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
After all the garbage has been identified, all the non-garbage has been compacted, and all the
non-garbage pointers have been fixed-up, a pointer is positioned just after the last nongarbage object to indicate the position where the next object can be added.
Finalization
.NET Framework's garbage collection implicitly keeps track of the lifetime of the objects that
an application creates, but fails when it comes to the unmanaged resources (i.e. a file, a
window or a network connection) that objects encapsulate.
The unmanaged resources must be explicitly released once the application has finished using
them. .NET Framework provides the Object.Finalize method: a method that the garbage
collector must run on the object to clean up its unmanaged resources, prior to reclaiming the
memory used up by the object. Since Finalize method does nothing, by default, this method
must be overridden if explicit cleanup is required.
It would not be surprising if you will consider Finalize just another name for destructors in
C++. Though, both have been assigned the responsibility of freeing the resources used by the
objects, they have very different semantics. In C++, destructors are executed immediately
when the object goes out of scope whereas a finalize method is called once when Garbage
collection gets around to cleaning up an object.
The potential existence of finalizers complicates the job of garbage collection in .NET by
adding some extra steps before freeing an object.
Whenever a new object, having a Finalize method, is allocated on the heap a pointer to the
object is placed in an internal data structure called Finalization queue. When an object is not
reachable, the garbage collector considers the object garbage. The garbage collector scans the
finalization queue looking for pointers to these objects. When a pointer is found, the pointer is
removed from the finalization queue and appended to another internal data structure called
Freachable queue, making the object no longer a part of the garbage. At this point, the
garbage collector has finished identifying garbage. The garbage collector compacts the
reclaimable memory and the special runtime thread empties the freachable queue, executing
each object's Finalize method.
The next time the garbage collector is invoked, it sees that the finalized objects are truly
garbage and the memory for those objects is then, simply freed.
Thus when an object requires finalization, it dies, then lives (resurrects) and finally dies again.
It is recommended to avoid using Finalize method, unless required. Finalize methods increase
memory pressure by not letting the memory and the resources used by that object to be
released, until two garbage collections. Since you do not have control on the order in which
the finalize methods are executed, it may lead to unpredictable results.
Garbage Collection Performance Optimizations
Weak References
Weak references are a means of performance enhancement, used to reduce the pressure placed
on the managed heap by large objects.
Garbage Collection in .NET
Seite 3 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
When a root points to an abject it's called a strong reference to the object and the object
cannot be collected because the application's code can reach the object.
When an object has a weak reference to it, it basically means that if there is a memory
requirement & the garbage collector runs, the object can be collected and when the
application later attempts to access the object, the access will fail. On the other hand, to access
a weakly referenced object, the application must obtain a strong reference to the object. If the
application obtains this strong reference before the garbage collector collects the object, then
the GC cannot collect the object because a strong reference to the object exists.
The managed heap contains two internal data structures whose sole purpose is to manage
weak references: the short weak reference table and the long weak reference table.
Weak references are of two types:
•
•
A short weak reference doesn't track resurrection.
i.e. the object which has a short weak reference to itself is collected immediately
without running its finalization method.
A long weak reference tracks resurrection.
i.e. the garbage collector collects object pointed to by the long weak reference table
only after determining that the object's storage is reclaimable. If the object has a
Finalize method, the Finalize method has been called and the object was not
resurrected.
These two tables simply contain pointers to objects allocated within the managed heap.
Initially, both tables are empty. When you create a WeakReference object, an object is not
allocated from the managed heap. Instead, an empty slot in one of the weak reference tables is
located; short weak references use the short weak reference table and long weak references
use the long weak reference table.
Consider an example of what happens when the garbage collector runs. The diagrams (Figure
1 & 2) below show the state of all the internal data structures before and after the GC runs.
Now, here's what happens when a garbage collection (GC) runs:
Garbage Collection in .NET
Seite 4 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
1. The garbage collector builds a graph of all the reachable objects. In the above example,
the graph will include objects B, C, E, G.
2. The garbage collector scans the short weak reference table. If a pointer in the table
refers to an object that is not part of the graph, then the pointer identifies an
unreachable object and the slot in the short weak reference table is set to null. In the
above example, slot of object D is set to null since it is not a part of the graph.
3. The garbage collector scans the finalization queue. If a pointer in the queue refers to
an object that is not part of the graph, then the pointer identifies an unreachable object
and the pointer is moved from the finalization queue to the freachable queue. At this
point, the object is added to the graph since the object is now considered reachable. In
the above example, though objects A, D, F are not included in the graph they are
treated as reachable objects because they are part of the finalization queue.
Finalization queue thus gets emptied.
4. The garbage collector scans the long weak reference table. If a pointer in the table
refers to an object that is not part of the graph (which now contains the objects pointed
to by entries in the freachable queue), then the pointer identifies an unreachable object
and the slot is set to null. Since both the objects C and F are a part of the graph (of the
previous step), none of them are set to null in the long reference table.
5. The garbage collector compacts the memory, squeezing out the holes left by the
unreachable objects. In the above example, object H is the only object that gets
removed from the heap and it's memory is reclaimed.
Generations
Since garbage collection cannot complete without stopping the entire program, they can cause
arbitrarily long pauses at arbitrary times during the execution of the program. Garbage
collection pauses can also prevent programs from responding to events quickly enough to
satisfy the requirements of real-time systems.
Garbage Collection in .NET
Seite 5 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
One feature of the garbage collector that exists purely to improve performance is called
generations. A generational garbage collector takes into account two facts that have been
empirically observed in most programs in a variety of languages:
1. Newly created objects tend to have short lives.
2. The older an object is, the longer it will survive.
Generational collectors group objects by age and collect younger objects more often than
older objects. When initialized, the managed heap contains no objects. All new objects added
to the heap can be said to be in generation 0, until the heap gets filled up which invokes
garbage collection. As most objects are short-lived, only a small percentage of young objects
are likely to survive their first collection. Once an object survives the first garbage collection,
it gets promoted to generation 1.Newer objects after GC can then be said to be in generation
0.The garbage collector gets invoked next only when the sub-heap of generation 0 gets filled
up. All objects in generation 1 that survive get compacted and promoted to generation 2. All
survivors in generation 0 also get compacted and promoted to generation 1. Generation 0 then
contains no objects, but all newer objects after GC go into generation 0.
Thus, as objects "mature" (survive multiple garbage collections) in their current generation,
they are moved to the next older generation. Generation 2 is the maximum generation
supported by the runtime's garbage collector. When future collections occur, any surviving
objects currently in generation 2 simply stay in generation 2.
Thus, dividing the heap into generations of objects and collecting and compacting younger
generation objects improves the efficiency of the basic underlying garbage collection
algorithm by reclaiming a significant amount of space from the heap and also being faster
than if the collector had examined the objects in all generations.
A garbage collector that can perform generational collections, each of which is guaranteed (or
at least very likely) to require less than a certain maximum amount of time, can help make
runtime suitable for real-time environment and also prevent pauses that are noticeable to the
user.
Garbage Collection in .NET
Seite 6 von 6
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Wie funktioniert der Java Garbage Collector
(Link zum Artikel: http://www.it-republik.de/jaxenter/artikel/2452)
Garbage Collection und Monitoring von Webanwendungen
Text: Kai Jäger
Eine Eigenschaft von Java ist, dass man sich nicht selbst um das Speichermanagement
kümmern muss. Denn der Garbage Collector der Java Virtual Machine (JVM) erledigt dies
zufriedenstellend. Jedoch kommt es vor, dass Java-Anwendungen im Produktivbetrieb mit
einer OutOfMemoryError terminieren. Im Rahmen des Monitorings ist es nützlich, dies
vorher zu erfassen, um Gegenmaßnahmen einleiten zu können. Dieser Artikel illustriert die
Funktionsweise des Garbage Collectors und geht auf Metriken ein, die beim Monitoring
unterstützen.
Grundlegendes
Im Gegensatz zu C oder C++, die keine Garbage Collection (GC) verwenden, muss in Java
dynamisch zugewiesener Speicher nicht explizit freigegeben werden. Nicht mehr referenzierte
Objekte werden vom Garbage Collector automatisch erkannt und entfernt. Somit werden
einige prominente Fehlerquellen eliminiert, die noch referenziert werden, z. B. das vorzeitige
Freigeben von Objekten oder das Vergessen nicht mehr referenzierter Objekte. Jedoch erzeugt
das automatisierte Einsammeln durch die Laufzeitumgebung einen gewissen Overhead.
Die Organisation des Heap-Speichers in Java, hier am Beispiel der Sun-Implementierung,
lässt sich vereinfacht wie folgt wiedergeben (Abb. 1): Bei der Allokation eines Objekts wird
es zunächst in der Young Generation (Nursery) angelegt. "Überlebt" es einige Zeit, wird es
noch referenziert, also in die Old Generation (Tenured) "befördert". Da ein Großteil der
erzeugten Objekte nicht referenziert ist, kann der Garbage Collector in der Young Generation
mit einem schnellen Algorithmus alle nicht referenzierten Objekte entfernen. In der Old
Generation, die normalerweise den Hauptbestandteil des Heaps ausmacht, wird ein
langsamerer, aber speicherplatzeffizienterer Algorithmus verwendet.
Abb. 1: Interner Aufbau des Heaps
Die Permanent Generation (PermGen) enthält Objekte, die
gemäß Vorgabe der Java Virtual Machine vom Garbage
Collector verwaltet werden sollen. Das sind z. B. Objekte,
die Klassen und Methoden beschreiben, sowie die Klassen
und Methoden selbst. Der PermGen gehört nicht zum
Heap.
Es existieren also zwei unterschiedliche Varianten der
Garbage Collection: Die schnelle GC in der Young
Generation, die sehr häufig ausgeführt wird (Minor
Collection) und die langsamere GC in der Old Generation
(Full Collection). Wichtig ist hierbei, dass sowohl bei den
Minor Collections als auch bei den Full Collections eine
"stop the world"-Pause eingelegt wird, also alles andere
Wie funktioniert der Java Garbage Collector
Seite 1 von 3
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
außer der Garbage Collection angehalten und nach Beendigung der Operation fortgesetzt wird.
Dies dauert je nach Collector-Art unterschiedlich lange.
Garbage-Collection-Algorithmen
Die folgenden Abschnitte geben einen Überblick über die unterschiedlichen GC-Algorithmen
der Young, Old und Permanent Generation. Dabei wird nicht auf alle GC-Algorithmen
eingegangen. Der Speicherbereich der Young Generation ist unterteilt in Eden und zwei
gleich große Survivor Spaces, oft From-Survivor und To-Survivor genannt. Der Eden Space
macht hierbei den Großteil des Speichers aus. Neue Objekte werden im Eden Space generiert.
Sind sie zu groß für diesen, werden sie direkt in der Old Generation erzeugt (Abb. 2).
Abb. 2: Interner Aufbau der Young
Generation
Beginnt der Garbage Collector seine
Arbeit, werden alle noch referenzierten
Objekte im Eden Space und FromSurvivor in den To-Survivor kopiert.
Objekte, die sich schon einige Zeit in
den Survivor Spaces befanden, werden
in die Old Generation verschoben.
Anschließend werden der Eden Space
und der From-Survivor geleert und die
beiden Survivor Spaces "tauschen" die
Funktion: Aus dem From-Survivor
wird der To-Survivor und umgekehrt.
Denn der Großteil aller Objekte im
Eden Space wird nicht oder nur kurz referenziert, z. B. Iterator-Objekte oder sonstige
Schleifenvariablen. Durch das Kopieren in die Survivor Spaces wird sichergestellt, dass
solche Objekte nicht direkt in die Old Generation verschoben werden, nur weil sie zum
Zeitpunkt der Garbage Collection noch eine Referenz trugen.
In einem einfachen Beispiel lässt sich der Vorgang wie folgt darstellen (Abb. 3): Das erste
Bild zeigt symbolisch die Speicherbelegung der Young Generation. Die Objekte werden
durch ihrer Größe entsprechende Blöcke angezeigt. Die grünen Objekte werden momentan
referenziert, die roten sind nicht mehr referenziert. Wird nun ein neues Objekt angelegt, ist
kein Platz mehr im Eden Space vorhanden. Der Garbage Collector nimmt seine Arbeit auf. Er
verschiebt alle Objekte, die noch referenziert sind (hier 1, 3, 5 und 9), in den To-Survivor
Space. In diesem Beispiel wird das Objekt 5 – aufgrund der Zeit, die es in den Survivor
Spaces "überlebt" hat – in die Old Generation verschoben. Sind mehr Objekte referenziert, als
in den To-Survivor Space passen, werden die restlichen direkt in die Old Generation
verschoben. Abschließend werden der Eden Space und der From-Survivor Space geleert, also
die Objekte 2, 4, 6, 7 und 8 endgültig gelöscht. Nach Abschluss einer Garbage Collection in
der Young Generation ist daher der gesamte Speicher bis auf einen der beiden Survivor
Spaces leer.
Wie funktioniert der Java Garbage Collector
Seite 2 von 3
Dr. Ute Blechschmidt-Trapp, .Net Framework und C# SS 2010
Anhang
Abb. 3: Ablauf der Garbage Collection in der
Young Generation
Old Generation/Permanent Generation
Die Bereinigung der Old Generation und der
Permanent Generation erfolgt durch einen marksweep-compact-Algorithmus. In der mark-Phase
werden alle Objekte markiert, die von den
Wurzelzeigern erreichbar sind (z. B. aktive ThreadObjekte, Systemklassen, lokale Variablen, hängige
Exceptions, Referenzen vom Native Stack). Das
Entfernen der nicht markierten Objekte erfolgt in
der sweep-Phase. Abschließend werden die
Markierungen von den Objekten wieder entfernt. In
der compact-Phase erfolgt eine Verschiebung der
Objekte an den Anfang des Speicherbereichs, um
eine möglichst große, zusammenhängende
Speicherregion zu erhalten. Das wird durch
Abbildung 4 verdeutlicht.
Das erste Bild zeigt symbolisch den OldGeneration- oder Permanent-Generation-Speicherbereich. Die einzelnen Blöcke stellen
Objekte dar, die Pfeile visualisieren die Verweise untereinander. Im Beispiel hat das Objekt 1
einen Wurzelzeiger und eine Referenz auf das Objekt 3, Objekt 2 hat eine Referenz auf
Objekt 4 und so weiter.
Die Markierung aller vom Wurzelknoten aus erreichbaren Objekte (grün) erfolgt in der markPhase. Objekt 4 wird zwar vom Objekt 2 referenziert, hat jedoch keine Verbindung zum
Wurzelknoten, ebenso wie Objekt 5. Anschließend werden in der sweep-Phase die nicht
markierten Objekte gelöscht und die Markierungen entfernt. Der Speicher ist nun fragmentiert.
Die abschließende compact-Phase verschiebt alle
Objekte an den Anfang des Speicherbereichs,
sodass sich der frei verfügbare Speicher in einem
großen Block hinter dem letzten Objekt befindet.
Dies erleichtert die Speicherzuteilung für Objekte.
Weiterführende Informationen – insbesondere zu
den verwendeten Algorithmen (copy from to bzw.
mark-sweep-compact) – gibt ein Whitepaper von
Sun zu diesem Thema. Außerdem wurde der
Garbage Collector in der Ausgabe 08/2009 im Java
Magazin thematisiert.
Abb. 4: Ablauf der Garbage Collection in der Old
Generation/Permanent Generation
Wie funktioniert der Java Garbage Collector
Seite 3 von 3

Similar documents