This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Die Reihe Programmer’s Choice Von Profis für Profis Folgende Titel sind bereits erschienen: Bjarne Stroustrup Die C++-Programmiersprache 1072 Seiten, ISBN 3-8273-1660-X Elmar Warken Kylix – Delphi für Linux 1018 Seiten, ISBN 3-8273-1686-3 Don Box, Aaron Skonnard, John Lam Essential XML 320 Seiten, ISBN 3-8273-1769-X Elmar Warken Delphi 6 1334 Seiten, ISBN 3-8273-1773-8 Bruno Schienmann Kontinuierliches Anforderungsmanagement 392 Seiten, ISBN 3-8273-1787-8 Damian Conway Objektorientiertes Programmieren mit Perl 632 Seiten, ISBN 3-8273-1812-2 Ken Arnold, James Gosling, David Holmes Die Programmiersprache Java 628 Seiten, ISBN 3-8273-1821-1 Kent Beck, Martin Fowler Extreme Programming planen 152 Seiten, ISBN 3-8273-1832-7 Jens Hartwig PostgreSQL – professionell und praxisnah 456 Seiten, ISBN 3-8273-1860-2 Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides Entwurfsmuster 480 Seiten, ISBN 3-8273-1862-9 Heinz-Gerd Raymans MySQL im Einsatz 618 Seiten, ISBN 3-8273-1887-4 Dusan Petkovic, Markus Brüderl Java in Datenbanksystemen 424 Seiten, ISBN 3-8273-1889-0 Joshua Bloch Effektiv Java programmieren 250 Seiten, ISBN 3-8273-1933-1
Sandini Bib
Klaus Aschenbrenner
Webapplikationen mit C# Webforms und ASP.NET mit dem Visual Studio
An imprint of Pearson Education München • Boston • San Francisco • Harlow, England Don Mills, Ontario • Sydney • Mexico City Madrid • Amsterdam
Sandini Bib
Die Deutsche Bibliothek – CIP-Einheitsaufnahme Ein Titeldatensatz für diese Publikation ist bei Der Deutschen Bibliothek erhältlich. Die Informationen in diesem Produkt werden ohne Rücksicht auf einen eventuellen Patentschutz veröffentlicht. Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt. Bei der Zusammenstellung von Abbildungen und Texten wurde mit größter Sorgfalt vorgegangen. Trotzdem können Fehler nicht vollständig ausgeschlossen werden. Verlag, Herausgeber und Autoren können für fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag und Herausgeber dankbar. Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und der Speicherung in elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigten Modelle und Arbeiten ist nicht zulässig. Fast alle Hardware- und Softwarebezeichnungen, die in diesem Buch erwähnt werden, sind gleichzeitig eingetragene Warenzeichen oder sollten als solche betrachtet werden. Umwelthinweis: Dieses Produkt wurde auf chlorfrei gebleichtem Papier gedruckt. Die Einschrumpffolie – zum Schutz vor Verschmutzung – ist aus umweltverträglichem und recyclingfähigem PE-Material.
Rückblick Rückblick auf ASP Was ist das Microsoft .NET Framework Die .NET-Programmiersprachen Visual Basic .Net Objektorientierte Features C# Objektorientierte Features Managed C++ JScript.NET Common Language Runtime Assemblies Metadaten Speicherverwaltung Zusammenfassung
Visual Studio .NET-Editionen Startseite Projekte Verwendung von Visual Studio .NET Hello World in ASP.NET Datenbindung Debuggen Debuggen von ASP.NET-Anwendungen Debuggen von Komponenten Visual Database Tools Datenbankdesigner Tabellendesigner Abfrage- und Sichtendesigner
65 72 78 87 87 95 111 111 118 121 122 125 129
Sandini Bib 6
Inhalt
2.7 2.7.1 2.7.2 2.8
Features der Enterprise Architect Edition Klassendesign mit der UML Datenbankdesign mit Visio Zusammenfassung
137 138 146 149
3
Einführung in ASP.NET
3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 3.10
Entwurfsziele von ASP.NET Programmierung von ASP.NET-Seiten Die Page-Klasse und deren Verarbeitung Page-Direktiven Code Behind Server-Controls Validation Controls User Controls Caching von ASP.NET-Seiten Zusammenfassung
4
WebForm-Controls
4.1 4.2 4.3 4.4 4.5 4.6 4.7
Überblick über die ASP.NET WebForm-Controls Standard WebForm-Controls List Controls Rich Controls Datenbindung Stile und Vorlagen Zusammenfassung
5
Internet Explorer WebControls
361
5.1 5.2 5.3 5.4 5.5 5.6
Programmieren mit den WebControls Das MultiPage-WebControl Das TabStrip WebControl Das Toolbar-WebControl Das TreeView-WebControl Zusammenfassung
362 365 372 376 383 391
6
Datenbankzugriff
6.1 6.1.1 6.1.2 6.2 6.2.1 6.2.2 6.2.3 6.3 6.3.1
Einführung in den Datenbankzugriff unter .NET Die grundlegenden Datenbankobjekte Einfache Datenbankzugriffsbeispiele Arbeiten mit relationalen Daten Abfragen von komplexen Daten Gespeicherte Prozeduren (Stored Procedures) Arbeiten mit dem DataTable-Objekt Updaten von relationalen Daten Updaten von Daten mit dem Command-Objekt
151 151 159 167 178 186 188 224 238 240 242
243 243 248 270 279 294 318 359
393 396 397 405 413 413 420 427 450 450
Sandini Bib Inhalt
7
6.3.2 6.3.3 6.4
Verwendung von Transaktionen Updaten von Daten mit dem DataSet-Objekt Zusammenfassung
7
Xml
7.1 7.1.1 7.2 7.2.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3
Überblick über Xml XML-Objekte Arbeiten mit Xml Zugriff auf relationale Daten Überprüfen von XML-Dokumenten Erzeugen und Bearbeiten von XML-Dokumenten Verwenden von XSL-Transformationen Erstellen einer XML-Datei für das TreeView-Control Zusammenfassung
Web-Anwendungen Virtuelle Verzeichnisse Aufbau von Web-Anwendungen global.asax State Management Anwendungsereignisse Konfiguration Überblick über die Konfiguration Allgemeine Konfigurationseinstellungen Zusammenfassung
9
Sicherheit
9.1 9.1.1 9.1.2 9.1.3 9.2
Sicherheit unter dem IIS und Windows 2000 Sicherheitskonzepte Sicherheit in ASP.NET Beispiele Zusammenfassung
Entwickeln einfacher Server-Controls Entwickeln von zusammengesetzten Server-Controls Schreiben eines TextBox-Server-Controls Implementierung des Server-Controls TextBox Implementierung des Postback-Mechanismus Implementierung der Funktion SetFocus Implementieren von Ereignissen Verwendung des ViewState Implementieren von mehreren Ereignissen Stile und Vorlagen Zusammenfassung
Programmieren von WebServices Überblick Programmiermodell Protokolle Datentypen Attribute Entwerfen von WebServices Verwenden von WebServices UDDI und WSDL Erstellen der Proxyklasse für den WebService Verwenden der Proxyklasse Weiterführende Themen Zusammenfassung
Collections und Listen Arrays Stacks Queues Dictionary-Objekte Dateisystemzugriff Zugriff auf Verzeichnisse und Dateien Lesen und Schreiben von Dateien StringBuilder Zugriff auf das Active Directory Das Ereignisprotokoll Message Queues E-Mail-Zugriff Zusammenfassung
Vorwort Microsoft .NET. Diesen Begriff hörte man das erste Mal auf der PDC (Professional Developers Conference) von Microsoft im Sommer 2000 in Orlanda. Aber was verbirgt sich genau hinter diesem Begriff, was sind die Ziele, was sind die Vorteile von .NET? In diesem Buch möchte ich auf die Programmierung von Web Anwendungen unter ASP.NET eingehen. Dabei kann man sich unter ASP.NET viel mehr als nur einen Nachfolger von ASP vorstellen, da das Programmiermodell von Grund auf geändert wurde. Durch diese Maßnahme ist es jetzt möglich, dass man eine Web Anwendung in der gleichen Zeit unf mit dem gleichen Komfort schreiben kann wie eine Windows Anwendung. In den einzelnen Kapiteln wird Schritt für Schritt gezeigt, wie man leistungsfähige Internetanwendungen entwickeln kann. Dabei wird aber nicht nur gezeigt, wie man Anwendungen von Hand entwickelt, sondern es wird auch dargestellt, wie man die nächste Version des Visual Studios, nämlich Visual Studio .NET, sinnvoll einsetzen kann. Egal, ob Sie Anfänger oder Fortgeschrittener sind, mit diesem Buch werden Sie in der Lage sein, innerhalb von kürzester Zeit leistungsfähige Anwendungen planen und umsetzen zu können. Wenn man die Vergangenheit näher betrachtet, wird man sehen, dass sich ASP.NET sicher auf kurz oder lang als Standard bei der Programmierung von Web Anwendungen durchsetzen wird. Daher ist es sinnvoll, wenn man sich mit dieser neuen Technologie so früh wie möglich beschäfigt, da sich daraus enorme Vorteile gegenüber anderen ergeben können. Da ich jetzt lange genug über das Buch und dessen Zielsetzung geredet habe, möchte ich zum Schluss noch an dieser Stelle allen Personen danken, die es mir überhaupt möglich gemacht haben, dieses Buch zu schreiben und fertigzustellen. Der größte Dank gilt sicherlich meinem Lektor Frank Eller, der es mir überhaupt ermöglicht hat, dieses Buch zu schreiben und umzusetzen. Dabei ist er mir in den schweren Zeiten immer hilfreich zur Seite gestanden, und hat mir viele wertvolle Tipps bezüglich des Schreibens gegeben, da das ja mein erstes Buch ist. Jedenfalls freue ich mich schon sehr auf unsere weiteren gemeinsamen Projekte! Ebenfalls großer Dank gilt meinem Chef und Freund Harald Holzknecht, da er es mir erlaubt hat, Teile des Buches innerhalb meiner wertvollen Arbeitszeit zu schreiben.
Sandini Bib 10
Vorwort
Gleich wie mein Lektor hat er mich in schlechten Zeiten immer wieder von neuem motiviert, dieses Buch zu schreiben. Richtig motiviert zum Schreiben hat mich Christian Nagel bei einem Treffen im Januar dieses Jahres. Dabei hat die Harmonie und das Zusammenspiel von uns beiden von Anfang an gepasst. Ich möchte mich bei Christian jedenfalls für die Motivation und für die technische Unterstützung besonders bedanken. Als besonders wertvoll finde ich es, dass er mir die notwendige Unterstützung gibt, die .NET User Group Styria zu gründen. Nicht zu vergessen sind auch meine Arbeitskollegen und Freunde Alex und Wolfgang, die mich auch an meinen schlechten Tagen immer wieder von neuem unterstützt und motiviert haben und somit aus einem schlechten Tag wieder einen guten gemacht haben ;-). Last but not least habe ich mir gedacht, dass ich die wichtigsten Personen zum Schluss erwähnen möchte. Dazu zählen Herbert und Dagmar, meine Eltern, die mir über die Jahre (es sind zur Zeit ca. 8) immer wieder die notwendige Unterstützung gegeben haben, damit ich zu meinem Ziel komme, wo ich heute stehe. Meine ersten Programmiererfahrungen habe ich auf einen DX2-50 mit 16 MB RAM unter MS DOS, den mir mein Vater zur Verfügung gestellt hat, gesammelt. Dabei habe ich mich mit einer Programmiersprache beschäftigt, die sich QBasic nannte. Was sich aus dieser Sprache entwickelt hat, möchte ich lieber an dieser Stelle nicht erörtern, da sonst das Vorwort zu lang werden würde ... Da dies mein erstes und wertvollstes Buch ist, möchte ich es meinen Eltern widmen und damit ein großes Dankeschön ausdrücken – vielen Dank für eure Unterstützung! Klaus Aschenbrenner, 11. März 2002 www.csharp.at [email protected]
Sandini Bib
1 Einführung in Microsoft .NET Microsoft .NET. Diesen Begriff hörte man das erste Mal bei der Professional Developers Conference 2000 in Orlando von Microsoft. Aber was genau ist Microsoft .NET? Was kann man sich darunter vorstellen? Was ist daran so anders? Das sind vielleicht alles Fragen, die Sie als Programmierer zurzeit beschäftigen. Dieses Kapitel soll dabei helfen, diese Fragen zu klären. Am Ende des Kapitels ist man in der Lage, all diese Fragen ausführlich und verständlich beantworten zu können.
1.1 Rückblick Blicken wir in die Vergangenheit der Softwareentwicklung. Hier gab es sehr viele Probleme, die die Programmierer zu lösen hatten. Wenn man ein Programm entwickeln wollte, gab es zuerst immer die Qual der Wahl der Programmiersprache. Welche Programmiersprache soll ich für mein Projekt verwenden? Hat die gewählte Programmiersprache auch den vollen Funktionsumfang, den ich für die Programmierung des Projekts benötige? Unter Microsoft .NET gehört dies der Vergangenheit an. Unter Microsoft .NET sind alle Programmiersprachen mit dem gleichen Funktionsumfang ausgestattet. Das heißt, dass die Wahl der Programmiersprache nach persönlichen Kriterien entschieden werden kann. Hat man sich für die Programmiersprache entschieden, muss man festlegen, unter welchem Betriebssystem die Applikation laufen soll. Hier gibt es viele Plattformen, die man zu berücksichtigen hat: Win9X, WinME, WinNT, Windows 2000, Windows CE, Macintosh, Linux, Unix, Solaris usw. Ein weiteres großes Problem von Windows-Anwendungen war die Stabilität. Haben Sie schon mal etwas von der DLL-Hölle gehört? Wenn nicht, dann seien Sie froh! Unter der DLL-Hölle kann man sich folgendes Beispiel vorstellen: Nehmen wir an, wir installieren Applikation A. Diese verwendet die DLL Z in der Version 1.0. Ein paar Wochen später installieren wir Applikation B, die die DLL Z in der Version 2.0 verwendet. Leider ist aber die Version 2.0 nicht mit der Version 1.0 kompatibel. Das heißt, dass auf unserem Computer jetzt die DLL Z in der Version 2.0 vorliegt. Applikation B funktioniert problemlos. Aber was ist jetzt mit Applikation A? Applikation A wird nicht
Sandini Bib 12
1
Einführung in Microsoft .NET
mehr funktionieren, da sie ja die DLL Z in der Version 1.0 benötigt, diese aber nicht mehr vorhanden ist! Und genau dieses Problem nennt man DLL-Hölle. Die Registrierung von COM-Komponenten war und ist außerdem ein großer Verwaltungsaufwand für Systemadministratoren. Jede COM-Komponente, die in einer Programmiersprache verwendet werden kann, muss in der Registry registriert werden. Das heißt, wenn man solche Komponenten updaten will, muss die Komponente auch neu registriert werden. Für einen Arbeitsplatzrechner stellt dies normalerweise kein Problem dar, aber für einen Webserver ist das ein sehr großes Problem. Außerdem ist noch bei Webservern zu beachten, dass diese heruntergefahren werden müssen, wenn man eine Komponente aktualisieren möchte, da diese ja sonst im Speicher geladen sind, und daher nicht überschrieben werden können. Wie liefert man ein fertig gestelltes Produkt aus? Die traditionelle Auslieferungsmethode in unserer Zeit ist natürlich die CD oder auch noch die altbewährte Diskette. Natürlich kann man Software auch schon über das Internet ausliefern, was sicher ein primärer Verteilungsweg der Zukunft sein wird. Die Auslieferung über Diskette und CD benötigt ein physikalisches Medium, das dem Kunden zugestellt wird. Und hier sieht man auch schon einige Nachteile. Die Auslieferung kostet den Produzenten und den Kunden etwas: das Medium selbst (CD, Diskette), Verpackung, Porto usw. Die Auslieferung über das Internet ist daher kostengünstiger, da es keine Kosten für Verpackung und Porto gibt. Die einzigen Kosten, die entstehen, sind die Onlinezeiten, die man für den Download der Software aufwenden muss. Fassen wir jetzt nochmals die Probleme zusammen, die bei der heutigen Softwareentwicklung entstehen können: 왘 Wahl der Programmiersprache 왘 Wahl des Betriebssystems 왘 Verwaltung von COM-Komponenten 왘 Auslieferung der Software All diese Probleme können durch Microsoft .NET gelöst werden und gehören daher der Vergangenheit an.
1.2 Rückblick auf ASP Da dieses Buch ja von Web-Anwendungen und ASP.NET handelt, werden wir jetzt einmal einen Rückblick auf das traditionelle ASP (Active Server Pages) machen. ASP (Codename Denali: ASP 1.0, IIS 3.0) wurde bei der SiteBuilder Konferenz 1996 in San Jose zum ersten Mal freigegeben. Mit ASP kann man dynamisch Webseiten erstellen, welche am Webserver interpretiert werden. Diese ASP-Seiten geben dann norma-
Sandini Bib Rückblick auf ASP
13
len HTML-Code an den Browser zurück. Serverseitig kann man verschiedene COMKomponenten in den Code integrieren, damit die ASP-Anwendungen leistungsfähiger und umfangreicher werden. (Datenbankzugriff, File-Upload zum Server usw.) Diese Technologie war für die damaligen Verhältnisse einfach umwerfend. Aber im Laufe der Zeit ergaben sich mit ASP einige Probleme, die den Programmierern Kopfzerbrechen bereiteten. Eingangs hatten wir bereits das Problem mit den COM-Komponenten erläutert. Ein weiteres großes Problem habe ich soeben angesprochen: Der ASP-Code wird serverseitig interpretiert. Da der ASP-Code nur interpretiert wird, gibt es seitens des Webservers Performanceprobleme. Außerdem kann man ASP-Code auch nur in Scriptsprachen (VB-Script, JScript) schreiben. In Microsoft .NET sieht es so aus, dass ASP-Code kompiliert wird und daher um einiges schneller abläuft als interpretierender Code. Außerdem ist dieser Script-Code nicht typensicher. Schauen wir uns das anhand eines Beispiels an. Im normalen ASP-Code würde man z.B. mit folgender Anweisung eine Stringvariable deklarieren: Dim strName
In ASP.NET ist eine solche Anweisung unzulässig, da sie nicht typensicher ist. Wenn wir das Beispiel auf ASP.NET umschreiben, würde es wie folgt aussehen: Dim strName As String
Diese Anweisung ist typensicher, da hier der Typ der Variable als String deklariert wurde. Wenn man heute ASP-Anwendungen programmiert, wird man sicherlich clientseitige Scripts verwenden, um bestimmte Funktionen zu implementieren. Was geschieht aber, wenn die Seite mit einem Browser geöffnet wird, der kein clientseitiges Scripting unterstützt? Das ist ein weiteres Problem von traditionellen ASP-Anwendungen. In ASP.NET wird automatisch der richtige Code für den jeweiligen Browser generiert. Unterstützt der Browser kein clientseitiges Scripting, dann wird auch kein ScriptingCode an den Browser zurückgeliefert, und die entsprechenden Funktionen werden serverseitig implementiert. Der Clou an der Sache ist, dass sich der Programmierer dabei um nichts kümmern muss. Dies wird alles automatisch von Microsoft .NET erledigt. ASP.NET ist sicherlich mehr als der Nachfolger von ASP 3.0. Man kann daher ASP.NET nicht ganz einfach als ASP 4.0 bezeichnen. Dies wäre sicher nicht im Sinne von ASP.NET.
Sandini Bib 14
1
Einführung in Microsoft .NET
1.3 Was ist das Microsoft .NET Framework Da wir jetzt die Probleme der Softwareentwicklung und die Probleme von ASPAnwendungen kennen, können wir uns schon ungefähr vorstellen, warum es Microsoft .NET gibt und welche Probleme es löst. Dieser Abschnitt zeigt, was genau das Microsoft .NET Framework ist und wie es die oben genannten Probleme zu lösen versucht. Microsoft .NET ist die Kombination aus folgenden Technologien: 왘 Microsoft .NET Framework 왘 WebServices 왘 Microsoft .NET Enterprise Server Auf den dritten Punkt, die Microsoft .NET Enterprise Server, wird in diesem Buch nicht näher eingegangen. Das Microsoft .NET Framework besteht wiederum aus den folgenden Technologien: 왘 Common Language Runtime 왘 Klassenbibliothek 왘 ASP.NET Das .NET Framework bietet viele Vorteile. Ich möchte hier die wesentlichsten Aspekte aufzählen: 왘 Eine Plattform zum Entwickeln von Internet-basierten Anwendungen, welche auf offenen Standards wie XML, HTTP und SOAP basiert. 왘 Eine Plattform, die zahlreiche Technologien (wie Windows Form) bietet, um klassische GUI-Anwendungen zu programmieren, und Technologien (wie ASP.NET), mit denen man moderne Internet-Anwendungen programmieren kann. 왘 Eine Plattform mit einer umfassenden Klassenbibliothek, die von jeder Programmiersprache ohne Einschränkungen zugänglich ist. Die Klassenbibliothek bietet umfassende Unterstützung für den Datenzugriff (relationale Daten, XML), Verzeichnisdienste, Nachrichtenschlangen und vieles mehr. 왘 Eine Plattform mit einer Basisklassenbibliothek, welche Hunderte von Klassen beinhaltet, wie z.B. Dateisystem-Management, Registrierungszugriff, Sicherheit, Threading, Grafikerstellung, Komponentenprogrammierung usw. 왘 Eine Plattform, die auch die Gegenwart mit einbezieht. Es gibt eine großartige Interoperabilität zwischen dem .NET Framework und den klassischen COM-Komponenten und den Windows-DLL.
Sandini Bib Was ist das Microsoft .NET Framework
15
왘 Eine Plattform mit einer unabhängigen Codegenerierung und mit einer Umgebung, die sich Common Language Runtime (CLR) nennt. Diese stellt sicher, dass der ausführende Code sicher und stabil läuft. Außerdem ist die Common Language Runtime eine abstrakte Schicht über dem Betriebssystem, was bedeutet, dass Microsoft .NET-Anwendungen auch auf anderen Plattformen laufen können, wenn die Common Language Runtime portiert wird. Von der Programmiererseite her kann man sagen, dass das.NET Framework die Windows-Programmier-Plattform verdrängen wird. Das .NET Framework ist komplett neu, von Grund auf objektorientiert gestaltet worden. Dies ist ein großer Vorteil gegenüber der bisherigen Win32-API Programmierung, wo es keine Objekte und dergleichen gibt.
Die .NET-Vision Jahrelang hat Microsoft jetzt schon versucht, in das Internet zu investieren. Ganz am Anfang hat Microsoft dem Internet keine große Zukunft gegeben. Denken wir zurück, als Microsoft ihr eigenes Netzwerk (Microsoft Network) als Konkurrenz zum Internet angeboten hat. Nur leider war die Akzeptanz des Internets riesengroß, weshalb es sich gegen das MSN (Microsoft Network) behauptet hat. Als nun Microsoft sah, dass sich das Internet über kurz oder lang durchsetzen wird, kam der große Browserkrieg zwischen Netscape und Microsoft. Am Anfang hatte Netscape einen sehr großen Marktanteil, aber im Laufe der Jahre schrumpfte er sehr, sehr zum Leidwesen von Netscape. Jetzt mit der Version 6.0 des Microsoft Internet Explorers beträgt der Browseranteil von Microsoft ca. 88 %. Netscape hat dagegen ca. nur mehr 10 % Marktanteil. Die restlichen 2 % teilen sich kleinere Browser, wie Opera, auf. Man kann heute sagen, dass der Browsermarkt fest in der Hand von Microsoft ist. Nur wenn Microsoft das Internet regieren will, müssen sie sich auf offene Standards wie XML, HTTP und SOAP stützen. Aber was genau ist die .NET-Vision von Microsoft? Was kann man sich darunter vorstellen? In einigen Jahren wird das Internet so verbreitet sein, dass sehr viele Haushaltsgeräte über das Internet kommunizieren. Das klingt jetzt sehr weit hergeholt, aber es gibt dafür schon einige Prototypen. Stellen Sie sich einen Kühlschrank vor, der am Internet hängt. Welche Vorteile bringt das? Der Kühlschrank könnte z.B. ganz alleine Bestellungen über das Internet aufgeben, wenn der Vorrat weniger wird. Der Hausherr braucht den Kühlschrank nur einmal dahingehend zu konfigurieren, bei welchem Onlineshop die Lebensmittel zu bestellen sind und wie sie z.B. geliefert werden sollen. Anschließend kann der Kühlschrank von ganz alleine Bestellungen über das Internet durchführen. Und genau hier beginnt die Vision von Microsoft .NET. In ferner Zukunft werden nicht nur mehr Computer am Internet hängen. Es werden auch Handheld-PCs, Mobiltelefone, Fernseher und auch unser Kühlschrank im Internet mitmischen. Und für all diese
Sandini Bib 16
1
Einführung in Microsoft .NET
Geräte muss die entsprechende Software geschrieben werden. Und das .NET Framework bietet die Grundlage für solche Softwareprogramme. Wie schon weiter oben erwähnt, beinhaltet das .NET Framework die Common Language Runtime. Dies ist eine Schicht zwischen dem Betriebssystem und den Programmiersprachen. Wenn man diese Schicht auf andere Betriebssysteme und Geräte portiert, können .NET Programme ohne Quelltextänderung und ohne Neukompilierung auf dieser neuen, zukunftsträchtigen Plattform ausgeführt werden. Ein weiterer Teil der .NET-Vision sind die so genannten WebServices. WebServices sind Programmmodule, die über das Internet aufgerufen werden können. Diese Module sind aber nicht Betriebssystem- oder sprachabhängig. Die Kommunikation mit diesen WebServices läuft über offene Standards wie XML, SOAP und HTTP. Daher kann man WebServices mit jeder beliebigen .NET-Programmiersprache schreiben – und auf jeder beliebigen Plattform aufrufen. Ein Methodenaufruf wird z.B. mithilfe von XML formuliert und in eine SOAP-Nachricht verpackt. Anschließend wird diese Nachricht über HTTP zum WebService transportiert. Der WebService führt den Methodenaufruf aus, formuliert den Rückgabewert der Methode wieder mit XML, verpackt das Ganze in eine SOAP-Nachricht und schickt das Methodenergebnis wieder mit HTTP zum Client zurück. Hier sieht man schon die Tragweite von Microsoft .NET. Microsoft .NET ist nicht nur auf Windows-Plattformen beschränkt. Es können alle möglichen Plattformen damit programmiert werden, auf die das .NET Framework portiert wurde. Microsoft hat außerdem das .NET Framework zur Standardisierung zur ECMA eingereicht. Unser schlauer Kühlschrank könnte dann eine Bestellung mittels eines WebServices aufgeben. Der Lebensmittelhändler könnte dann die Waren durch eine Transportagentur verschicken. Diese Agentur stellt dafür natürlich auch einen WebService zur Verfügung. Und die Bezahlung könnte durch einen WebService einer Kreditkartengesellschaft abgewickelt werden. An diesem Beispiel sieht man schon, wohin das führt: Es führt zum programmierbaren Web! In ferner Zukunft werden viele unserer heutigen Programme als WebServices im Internet laufen, die man gegen eine kleine Gebühr benutzen kann. Das Ganze nennt man Application Service Providing.
Windows DNA wird Microsoft .NET Ende 1996/Anfang 1997 präsentierte Microsoft Windows DNA. Windows DNA war ein Programmiermodell, das Richtlinien festlegte, wie man verteilte, komponentenbasierend, n-Tier-Anwendungen für die Windows-Plattform entwickelt. Windows DNA-Anwendungen mussten nicht das Internet nutzen, aber sie konnten. Über die Laufe der Jahre wuchs Windows DNA immer mehr. Und dann kamen auch schon die Probleme. Eines der Hauptprobleme waren die COM-Komponenten und ihre Registrierung und das Update. Das Component Object Model (COM) ging bis in die Anfänge
Sandini Bib Was ist das Microsoft .NET Framework
17
der 90er Jahre zurück, wo es für die Windows-Programmierung noch nicht viel anderes als das altbewährte, unstrukturierte Win32-API gab. Die Probleme der COM-Komponenten haben wir ja schon ausführlich weiter vorne erläutert. Aber wie löst jetzt das .NET Framework diese beiden Probleme? Vorerst sei nur so viel gesagt: .NET Komponenten sind selbstbeschreibend. Das heißt, dass sie nicht mehr registriert werden müssen. Die .NET Komponenten beinhalten die ganzen Typinformationen selbst in einem so genannten Manifest. Das Manifest von .NET Komponenten wird weiter hinten in diesem Kapitel beschrieben. Dadurch entfällt die Registrierung der Komponenten. Aber wie wird das Problem der Aktualisierung der Komponenten gelöst? Nehmen wir an, eine große Web-Anwendung besteht aus mehreren Komponenten. Wenn Benutzer mit dieser Web-Anwendung arbeiten, sind alle diese Komponenten im Speicher und können nicht aktualisiert werden. Daher muss man den Webserver stoppen, die Komponenten aktualisieren und anschließend wieder den Webserver starten. Das ist aber in 24x7-Anwendungen ein großes Problem, da diese Anwendungen rund um die Uhr laufen müssen (24 Stunden am Tag, 7 Tage die Woche)! .NET-Komponenten kann man während des Betriebs aktualisieren. Sie werden beim Laden durch den Webserver nicht gesperrt. Sie können jederzeit überschrieben werden. Möglich wird das durch ein Feature, das sich Shadow Copy nennt. Shadow Copy ist Teil der Common Language Runtime. Jede Applikation, die man unter Microsoft .NET entwickelt, also Windows-Anwendungen oder ASP.NET-Anwendungen, können von diesem Feature Gebrauch machen. Dadurch werden EXE- und DLL-Dateien nicht mehr durch das Betriebssystem gesperrt. Die Änderungen in den Komponenten werden vom .NET Framework automatisch erkannt und die aktualisierte Komponente wird in den Speicher geladen. Alle folgenden Aufrufe werden an die neue Komponente geleitet. Wenn alle alten Aufrufe abgeschlossen sind, wird die ältere Version der Komponente aus dem Speicher geladen und ist somit verschwunden. Durch diese Features kann man z.B. Web-Anwendung mithilfe eines FTP-Programms von einem Webserver zu einem anderen kopieren. Man braucht dann keine Komponenten mehr auf dem Zielserver zu registrieren. Jetzt fragt man sich vielleicht, wie das mit den Konfigurationseinstellungen aussieht, die in der Metabase des IIS gespeichert werden? Konfigurationseinstellungen von ASP.NET-Anwendungen werden in lesbaren XML-Dateien im Rootverzeichnis der Web-Anwendung gespeichert. Somit braucht man sich auch nicht mehr länger mit der Metabase des IIS herumschlagen. Daher nennt man das Deployment einer WebAnwendung unter .NET auch XCopy-Deployment, da alle Daten, die eine Anwendung benötigt, um korrekt zu funktionieren, mit einem normalen FTP-Programm kopiert werden können. Ein weiteres Feature von Microsoft .NET ist die Side-by-Side-Execution. Man kann z.B. mehrere Versionen einer .NET-Komponente nebeneinander auf einem Rechner laufen
Sandini Bib 18
1
Einführung in Microsoft .NET
lassen, ohne dass sie sich in die Quere kommen. Das wird dann auch der Fall für Microsoft .NET sein. In Zukunft kann man z.B. Microsoft .NET 1.0 neben Microsoft .NET 2.0 laufen lassen, ohne sich um irgendwelche Probleme kümmern zu müssen.
Technologien im .NET Framework Das .NET Framework ist sehr umfassend und besteht aus vielen Technologien, die alle ineinander greifen. Zwei große Technologien haben wir bereits kennen gelernt: ASP.NET und WebServices. Aber das .NET Framework besteht aus viel mehr: 왘 ASP.NET: Mit ASP.NET kann man leistungsfähige Web-Anwendungen entwickeln, ohne sich um die näheren Details zu kümmern. Können Sie sich vorstellen, dass es genauso leicht ist, eine ASP.NET-Anwendug zu entwickeln wie ein Visual BasicProgramm? Sie werden es im Laufe des Buches sehen ... 왘 Web Forms: ASP.NET-Anwendungen werden über Web Forms realisiert. Stellen Sie sich vorerst eine Web Form wie eine Visual Basic Form vor. Man zieht die Controls auf die Form und klickt auf die verschiedenen Controls, um Ereignisprozeduren zu schreiben. 왘 WebServices: WebServices werden das Internet revolutionieren, da sie das programmierbare Internet möglich machen. Ohne WebServices könnte unser Kühlschrank keine Bestellungen aufgeben. 왘 Windows Form: Das Gegenstück zu Web Forms sind die Windows Forms. Windows Forms sind das Gleiche wie die heutigen Visual Basic Forms. Aber diese Forms sind auch von anderen Programmiersprachen wie C# oder C++ aus zugänglich – genauso wie in Visual Basic. 왘 ADO.NET: Ein wichtiger Bestandteil von Microsoft .NET ist der Datenbankzugriff. Dieser wird über ADO.NET realisiert. ADO.NET ist der Nachfolger des heutigen ADO. Aber über ADO.NET kann man z.B. auch auf XML-Daten zugreifen und diese bearbeiten. Jetzt haben wir einen ungefähren Überblick darüber bekommen, welche Probleme Microsoft .NET löst und was genau eigentlich Microsoft .NET ist.
1.4 Die .NET-Programmiersprachen Microsoft .NET beinhaltet folgende Programmiersprachen: 왘 Visual Basic .Net 왘 C# (C Sharp) 왘 Managed C++ 왘 JScript.NET
Sandini Bib Die .NET-Programmiersprachen
19
Alle Beispiele und Probleme werden in diesem Buch mit C# gelöst werden, da C# eine neue, sehr leistungsfähige Programmiersprache von Microsoft ist. Aber warum hat Microsoft eine neue Programmiersprache mit Microsoft .NET eingeführt? Haben die anderen Programmiersprachen nicht gereicht? Microsoft hat auf seine Programmiersprachen, die im Einsatz sind, geschaut, und hat sich folgende Fragen gestellt: 왘 Haben unsere Sprachen all die Möglichkeiten und Fähigkeiten, die Entwickler benötigen? 왘 Wie können wir unsere Programmiersprachen verbessern? 왘 Wie können wir die bestehenden Fähigkeiten der Programmierer ohne viel Aufwand verbessern? 왘 Wie können wir eine bessere Entwicklungsumgebung schaffen? 왘 Ist die Applikationsarchitektur wirklich so gut, wie es scheint? Aus den Antworten auf die Fragen ist hervorgegangen, dass man eine neue Programmiersprache für Microsoft .NET benötigt. Daraus wurde C#. C# ist die erste Wahl bei der Programmierung mit Microsoft .NET. Die Syntax von C# ist an C++ angelehnt und sehr leicht zu lernen. Jetzt fragt man sich vielleicht, warum man dann nicht einfach in C++ entwickelt? C++ hat auch schon einige Jahre auf dem Rücken und ist sehr fehleranfällig. Fragen Sie einen C++-Programmierer, was er an dieser Sprache hasst, und er wird Ihnen sagen, dass ihm der Zeigerzugriff nicht ganz zusagt. C++-Programme sind sehr leistungsfähig und in der Ausführung schnell, aber die Entwicklung von solchen Programmen dauert sehr lange. Die meiste Zeit verbringt ein C++-Programmierer mit dem Debuggen von Anwendungen (Speicherlöcher, Zeigerfehler, usw.). Aus diesem Grund ist C# entstanden. In C# gibt es keine Zeiger mehr. C# ist eine Programmiersprache, mit der man ganz einfach Komponenten für Microsoft .NET entwickeln kann. C# hat aber die Leistungsfähigkeit von C++. In Microsoft .NET gibt es auch keine Speicherlöcher mehr, nach denen der Programmierer zu suchen hat. Das komplette Speichermanagement übernimmt der Garbage Collector (GC). Er kümmert sich um den Speicherhaushalt der .NET-Anwendung. Das heißt, man legt Objekte an, braucht sie aber nicht mehr zu zerstören. Das macht der Garbage Collector – vollautomatisch! Der Garbage Collector wird später in diesem Kapitel näher behandelt. Anbieter von Compilern können ihre Programmiersprachen problemlos in das .NET Framework integrieren. Diese Sprachen können dann auf die komplett gleichen Funktionen zugreifen, wie z.B. C#. Daher gibt es unter .NET z.B. auch Threading-Unterstützung für Visual Basic .Net! Es gibt eine systemweite Konfigurationsdatei für das .NET Framework, die auf XML basiert. Diese heißt machine.config und befindet sich im folgenden Verzeichnis: C:\WinNT\Microsoft .NET\Framework\v0.0.0.0\config 0.0.0.0 steht dabei für die Versionsnummer des .NET Frameworks. In dieser Datei gibt es einen Abschnitt mit dem Namen . Hier sind alle Compiler vermerkt,
Sandini Bib 20
1
Einführung in Microsoft .NET
welche aktuell installiert sind. Bei einer .NET-Framework-SDK-Installation kann der -Abschnitt folgendermaßen aussehen:
Das Attribut language gibt an, wie man die Programmiersprache in einem Quelltext auswählen kann. Beispiele dazu sehen wir in den späteren Kapiteln. Das Attribut extension zeigt die Erweiterung an, welche die Quelltexte dieser Programmiersprache haben. Und das type Attribut zeigt die Microsoft .NET-Klasse an, unter welcher der Compiler zu finden ist, der den Quelltext übersetzt. Wenn ein Drittanbieter eine Programmiersprache für Microsoft .NET schreibt, muss er einen CodeProvider schreiben, der den Compiler der jeweiligen Sprache beinhaltet. Außerdem muss in die Konfigurationsdatei die Programmiersprache aufgenommen werden. Dies ist wiederum ein Beispiel einer XML-basierten Konfiguration anstelle der Windows-Registrierung.
1.4.1 Visual Basic .Net Die neueste Version von Visual Basic ist ein großer Sprung in Richtung Funktionalität und Objektorientiertheit. Visual Basic .Net ist jetzt komplett objektorientiert, das heißt, dass Vererbung, Polymorphismus, Überladungen keine Fremdwörter mehr für Visual Basic Programmierer sind. Außerdem kann auf die kompletten Vorteile der Common Language Specification (CLS) und der Common Language Runtime zugreifen. Die Common Language Specification wird später näher erläutert. Jede neue Generation einer Programmiersprache bringt Änderungen mit sich. Um die Änderungen in Visual Basic .Net zu verstehen, muss man zuerst die Probleme der vorherigen Visual Basic-Versionen kennen gelernt haben:
Sandini Bib Die .NET-Programmiersprachen
21
왘 Runtime-Bibliothek: Jede Programmiersprache braucht ihre Runtime-Bibliothek. Bei Visual Basic besteht diese aus sehr großen DLL-Dateien. Diese Dateien sind für jedes Visual Basic-Programm notwendig. Daher kam seitens der Programmierer auch die Beschwerde, dass diese DLLs oft Kompatibilitätsprobleme verursachen und sehr groß sind. Die Runtime-Bibliothek der verschiedenen .NET-Programmiersprachen wird durch die Common Language Runtime ersetzt. Das heißt, dass für keine .NET-Programmiersprache mehr eine separate Runtime-Bibliothek verwendet werden muss. Die Größe dieser verteilbaren Common Language Runtime beträgt ca. 20 MB und sie unterstützt alle 4 .NET-Sprachen von Microsoft. 왘 Schlechte objektorientierte Features: Visual Basic beherrschte nicht sehr viele objektorientierte Features, und diese nur sehr schlecht. Aber mit Visual Basic .Net stehen jetzt alle objektorientierten Features einer Programmiersprache wie C++ zur Verfügung. Dazu zählt die Vererbung (.NET unterstützt nur die einfache Vererbung, keine Mehrfachvererbung, da diese seitens der Programmierung immer wieder zu Problemen geführt hat), Polymorphismus, Funktionsüberladungen und Operatorüberladungen. Die altbekannten Funktionszeiger von C++ wurden durch ein objektorientiertes Konstrukt von .NET, die so genannten Delegates, ersetzt. 왘 Keine Möglichkeiten für Multithreaded-Anwendungen: Visual Basic hatte keine Möglichkeiten, die Threadingfunktionen des Betriebssystems auszunutzen. Dies war nur C und C++ Programmierern möglich. Multithreaded-Komponenten haben erst Einzug genommen, als Microsoft den MTS (Microsoft Transaction Server) vorgestellt hat. Aber in Microsoft .NET haben auch Visual Basic-Programmierer Zugriff auf Threadingfunktionen. Daher sind Multithreaded-Anwendungen nicht nur mehr C- und C++ Programmierern vorenthalten. All dieses Probleme verschwinden in Visual Basic .Net. Wie gesagt, werden keine Runtime-Bibliotheken der Programmiersprachen mehr gebraucht, da dies die Common Language Runtime übernimmt. Außerdem sind die objektorientierten Features in der Common Language Runtime sehr verbessert worden. Mit der Common Language Runtime sind alle Features für alle .NET Programmiersprachen verfügbar. Werfen wir nun einen genaueren Blick auf die Features von Visual Basic .Net.
1.4.2 Objektorientierte Features Die objektorientierten Features in Visual Basic .Net wurden seit langem von sehr vielen VB Programmierern gefordert. In VB 5.0 waren ja schon grundlegende objektorientierte Features wie Klassen und Interfaces verfügbar. Aber mit Visual Basic .Net wurden jetzt alle objektorientierten Features in VB implementiert.
Sandini Bib 22
1
Einführung in Microsoft .NET
Klassen Schauen wir uns die Klassen in Visual Basic .Net genauer an. Grundsätzlich kann gesagt werden, dass alles in .NET klassenbasiert ist. Ebenso wie in vorhergehenden Versionen von Visual Basic werden Klassen durch das Schlüsselwort Class deklariert. Sehen wir uns die genaue Syntax dieses Schlüsselwortes einmal näher an: [Public | Private | Protected | Friend | Protected Friend] [MustInherit | NotInheritable] Class className End Class Listing 1.1: Klassendeklaration in Visual Basic .Net
Hier sehen Sie alle Schlüsselwörter für die Klassendeklaration in Visual Basic .Net beschrieben: Schlüsselwort
Beschreibung
Public
Auf die Klasse kann öffentlich zugegriffen werden.
Private
Auf die Klasse kann nur innerhalb der Quelltextdatei zugegriffen werden, in welcher sie deklariert wurde.
Protected
Auf die Klasse kann nur von der gleichen und von abgeleiteten Klassen zugegriffen werden.
Friend
Auf die Klasse kann nur innerhalb dieser Assembly zugegriffen werden.
Protected Friend
Auf die Klasse kann nur innerhalb dieser Assembly oder innerhalb von abgeleiteten Klassen zugegriffen werden.
MustInherit
Diese Klasse ist eine abstrakte Klasse, und die Klassenmethoden müssen in der abgeleiteten Klasse implementiert werden.
NotInhertitable
Von dieser Klasse kann nicht abgeleitet werden.
Tabelle 1.1: Schlüsselwörter für die Klassendeklaration in VB.NET
Sehen wir uns ein paar Beispiele an: Public Class Person ' Implementierung folgt hier End Class
oder Protected MustInherit Class Person ' Implementierung folgt hier End Class Listing 1.2: Beispiele für eine Klassendeklaration
Sandini Bib Die .NET-Programmiersprachen
23
Methoden Methoden werden in Visual Basic .Net als Sub oder Function deklariert. Als Sub deklariert, nennt man sie Prozedur und geben dann keinen Wert an den Aufrufer zurück. Als Function deklariert nennt man sie Funktion und sie haben einen Rückgabewert. Hier ist die Syntax für das Deklarieren von Prozeduren in Visual Basic .Net: [Overloads | Overrides | Overridable | NotOverridable | MustOverride | Shadows | Shared] [Private | Public | Protected | Friend | ProtectedFriend] Sub subName [(parameters)] End Sub Listing 1.3: Syntax für die Deklaration einer Prozedur in VB.NET
Für eine Funktion ist die Syntax wie folgt: [Overloads | Overrides | Overridable | NotOverridable | MustOverride | Shadows | Shared] [Private | Public | Protected | Friend | ProtectedFriend] Function functionName [(parameters)] [As type] End Function Listing 1.4: Syntax für die Deklaration einer Funktion in VB.NET
Die verschiedenen Schlüsselwörter sind hier beschrieben: Schlüsselwort
Beschreibung
Overloads
Die Methode ist überladen, hat also mehr als eine Deklaration. Die Deklaration unterscheidet sich in den Parametern.
Overrides
Die Methode überschreibt eine Methode von einer Basisklasse. Die abgeleitete Methode muss die exakt gleiche Methodensignatur haben wie die Basisklassenmethode. Das Überschreiben von Methoden ist sinnvoll, wenn man für Methoden einer Basisklasse seine eigene Implementierung schreiben will bzw. muss.
NotOverridable
Die Methode kann in einer abgeleiteten Klasse nicht überschrieben werden.
Overridable
Die Methode kann in einer abgeleiteten Klasse überschrieben werden.
MustOverride
Die Methode muss in einer abgeleiteten Klasse überschrieben werden.
Shadows
Dies verbirgt die Methode einer Vaterklasse. Das macht es möglich, dass man eine Methode mit einer anderen Methodensignatur überschreiben kann. Daher wird die Methode sozusagen neu deklariert.
Tabelle 1.2: Schlüsselwörter für die Methodendeklaration in VB.NET
Sandini Bib 24
1
Einführung in Microsoft .NET
Schlüsselwort
Beschreibung
Shared
Die Methode wird von allen Klasseninstanzen benutzt und existiert unabhängig von Klasseninstanzen. Das ist das Äquivalent zu einer statischen Methode in C++.
Public
Auf die Methode kann öffentlich zugegriffen werden.
Private
Die Methode ist nur innerhalb der Klasse verfügbar. Dieses Schlüsselwort wird meistens für Hilfsfunktionen gewählt.
Protected
Auf die Methode kann nur von der gleichen und von abgeleiteten Klassen zugegriffen werden.
Friend
Auf die Methode kann nur innerhalb dieser Assembly zugegriffen werden.
Protected Friend
Auf die Methode kann nur innerhalb dieser Assembly oder innerhalb von abgeleiteten Klassen zugegriffen werden.
Tabelle 1.2: Schlüsselwörter für die Methodendeklaration in VB.NET (Forts.)
Zum Beispiel: Public Class Person Public Sub SetName(strName As String, strVorname As String) ' Implementierung folgt hier End Sub End Class Listing 1.5: Beispiel für eine Prozedurdeklaration
Eigenschaften Eigenschaften sind eine Erweiterung zu Variablen; beide sind benannt und haben einen Typ. Die Syntax für das Deklarieren von Eigenschaften und Variablen ist dieselbe. Eigenschaften haben aber so genannte Accessors, über die auf den Inhalt der Eigenschaften zugegriffen werden kann. Beim Deklarieren von Eigenschaften wird zuerst der Typ deklariert und dann die Get- und Set-Accessors. Wenn eine Eigenschaft z.B. nur einen Get-Accessor hat, ist für die Eigenschaften nur ein Lesezugriff erlaubt. Daraus ergeben sich folgende Kombinationen für den Zugriff auf die Eigenschaften: 왘 Schreib- und Lesezugriff 왘 nur Lesezugriff 왘 nur Schreibzugriff Eine Eigenschaft ohne Schreib- und Lesezugriff würde keinen Sinn haben, da in diesem Fall die Eigenschaft im Programm gar nicht angesprochen werden kann.
Sandini Bib Die .NET-Programmiersprachen
25
Zur Veranschaulichung von Eigenschaften deklarieren wir jetzt eine Klasse mit dem Namen Person und deklarieren zwei Eigenschaften mit den Namen Nachname und Vorname. Beide verfügen über Get- und Set-Accessors. Public Class Person Private nachnameValue() As String Private vornameValue() As String Public Property Nachname() As String Get Return nachnameValue End Get Set (ByVal Value As String) nachnameValue = value End Set End Property Public Property Vorname() As String Get Return vornameValue End Get Set (ByVal Value As String) vornameValue = value End Set End Property End Class Listing 1.6: Beispiel für die Deklaration von Eigenschaften
Sehen wir uns den Quelltext Schritt für Schritt durch. Zuerst werden zwei private Stringvariablen deklariert. In diesen wird der Wert der beiden Eigenschaften gespeichert. Dann folgt die Deklaration der ersten Eigenschaft mit dem Namen Nachname vom Typ String. Im Get-Accessor wird einfach der Wert der privaten Stringvariable nachnameValue zurückgegeben. Der Set-Accessor hat einen Übergabeparameter. Dieser heißt immer value und wurde in diesem Fall als Stringvariable deklariert. Dann wird der privaten Variable der Wert dieses Parameters zugewiesen. Jetzt kann man sich vielleicht denken, warum eigentlich Eigenschaften verfügbar sind. Die Lösung ist einfach: Da man Get- und Set-Accessor deklarieren kann, kann man auch in diesem Accessors überprüfen, ob ein korrekter Wert z.B. zugewiesen wird. Oder stellen Sie sich eine Kreisklasse vor. Diese könnte z.B. eine Eigenschaft haben, welches den Radius des Kreises widerspiegelt. Jedes Mal, wenn jetzt z.B. der Radius geändert wird, könnte man automatisch die neue Fläche des Kreises berechnen. Sehen wir uns jetzt an, wie man auf eine solche Eigenschaft zugreifen kann: Dim myPerson As New Person Dim wholeName As String
Sandini Bib 26
1
Einführung in Microsoft .NET
myPerson.Nachname = "Aschenbrenner" ' Aufruf des Set-Accessors myPerson.Vorname = "Klaus" ' Aufruf des Set-Accessor ' Aufruf des Get Accessors wholeName = myPerson.Nachname + " " + myPerson.Vorname Listing 1.7: Beispiel für die Verwendung von Eigenschaften
Konstruktoren Wenn in früheren Visual Basic-Versionen eine Klasse angelegt wurde, wurde das Ereignis Class_Initialize gemeldet. Dieses Event wurde in Visual Basic .Net durch die Funktion New ersetzt. Diese neue Funktion trägt den Namen Konstruktor. Konstruktoren werden nur beim Erzeugen von Klassen aufgerufen und können nicht explizit im Programm aufgerufen werden. Konstruktoren können auch mit mehreren Parameterlisten überladen werden. Sehen wir uns eine einfache Klasse mit zwei Konstruktoren an. Der erste Konstruktor hat keine Übergabeparameter und wird als Standardkonstruktor bezeichnet. Dieser wird immer dann aufgerufen, wenn bei der Erzeugung der Klasse keine Parameter übergeben werden. Public Class Person Private vornameValue As String Private nachnameValue As String Sub New() ' Standardkonstruktur vornameValue = "" nachnameValue = "" End Sub Sub New(strVorname As String, strNachname As String) vornameValue = strVorname nachnameValue = strNachname End Sub End Class Public Class Test Dim Person1 As New Person ' Verwendung des Standardkonstruktors Dim Person2 As New Person("Klaus", "Aschenbrenner") End Class Listing 1.8: Beispiel für die Verwendung von Konstruktoren
Destruktoren In früheren Versionen von Visual Basic gab es genauso wie beim Erzeugen von Klassen auch Events beim Zerstören von Klassen. Dieses Event hieß Class_Terminate. In Visual Basic .Net wurde es durch die Funktion Destruct ersetzt. In Destruktoren können z.B.
Sandini Bib Die .NET-Programmiersprachen
27
Ressourcen freigegeben werden, welche in Konstruktoren angelegt wurden. Sehen wir uns einen Beispieldestruktor an: Public Class Person Sub Destruct ' Implementierung folgt hier End Sub End Class Listing 1.9: Beispiel für die Verwendung von Destruktoren
Vererbung Wie schon früher erwähnt, ist alles in Microsoft .NET ein Objekt. So können wir von sehr vielen Objekten unsere Klassen ableiten. Wenn wir unsere Personenklasse hernehmen, können wir davon eine spezialisiertere Klasse ableiten: Public Class Programmierer Inherits Person Public AnzahlKaffeeProTag As Integer Public Sub New()' Standardkonstruktor MyBase.New() End Sub Public Sub New(strVorname As String, strNachname As String) MyBase.New(strVorname, strNachname) End Sub Public Sub New(strVorname As String, strNachname As String, _ nAnzahlKaffeeProTag As Integer) MyBase.New(strVorname, strNachname) AnzahlKaffeProTag = nAnzahlKaffeeProTag End Sub End Class Listing 1.10: Beispiel für die Vererbung
Analysieren wir den Quelltext Zeile für Zeile. In den ersten beiden Zeilen wird eine neue Klasse mit dem Namen Programmierer deklariert. Diese wird von der Klasse Person abgeleitet. Dann wird eine öffentliche Variable deklariert, welche die Anzahl der Kaffee, die der Programmierer pro Tag trinkt, speichert. Eleganter wäre es gewesen, dafür eine Eigenschaft zu nehmen. Anschließend wird der Standardkonstruktor der Klasse deklariert. In der Implementierung dieses Konstruktors wird einfach der Konstruktor der Basisklasse aufgerufen. Der Zugriff auf die Basisklasse wird durch das Schlüsselwort MyBase möglich. MyBase steht immer für die Basisklasse der Klasse.
Sandini Bib 28
1
Einführung in Microsoft .NET
Anschließend wird noch ein Konstruktor deklariert, der die genaue Parameterliste der Basisklasse hat. Daher wird auch in diesem Konstruktor der Konstruktor der Basisklasse aufgerufen. Schlussendlich wird noch ein Konstruktor deklariert, der den Vornamen, den Nachnamen und die Anzahl der Kaffee des Programmierers entgegennimmt. Als Erstes wird mit dem Vornamen und dem Nachnamen der Konstruktor der Basisklasse aufgerufen. Dann wird die öffentliche Variable AnzahlKaffeeProTag mit der Anzahl der Kaffee pro Tag initialisiert.
Schnittstellen Schnittstellen haben mit COM in unser Programmiererleben Einzug genommen. Aber was genau sind Schnittstellen? Ganz einfach erklärt sind Schnittstellen Klassen ohne Implementierungscode. Eine Schnittstelle ist sozusagen ein Vertrag zwischen der Schnittstelle und dem Programmierer, der eine Schnittstelle implementieren will. Eine Schnittstelle ist eine Beschreibung der Methoden und Eigenschaften, welche eine Klasse zur Verfügung stellen muss, wenn sie diese Schnittstelle implementieren will. Sehen wir uns eine einfache Schnittstellendeklaration an: Public Interface IPerson Property Vorname() As String Property Nachname() As String Function GanzerName() As String End Interface Listing 1.11: Beispiel für die Deklaration einer Schnittstelle
Dieser Quellcode deklariert eine Schnittstelle mit den Eigenschaften Vorname und Nachname. Außerdem wird die Funktion GanzerName deklariert. Was bedeutet das jetzt für eine Klasse, die diese Schnittstelle implementieren will? Als Erstes muss man in der Klasse die beiden Eigenschaften implementieren und anschließend noch die Funktion GanzerName. Hier ein Beispiel: Public Class Person Implements IPerson Private vornameValue As String Private nachnameValue As String Public Propety Vorname() As String Implements IPerson.Vorname ' Implementierung folgt hier End Property Public Property Nachname() As String Implements IPerson.Nachname ' Implementierung folgt hier End Property
Sandini Bib Die .NET-Programmiersprachen
29
Public Function GanzerName() As String Implements _ IPerson.GanzerName ' Implementierung folgt hier End Function End Class Listing 1.12: Beispiel für die Implementierung einer Schnittstelle
1.4.3 C# Was wäre eine neue Technologie ohne neue Programmiersprache? So wurde mit Microsoft .NET die neue Programmiersprache C# (C Sharp) eingeführt. Über C# gibt es, wie über Microsoft .NET, sehr viele Diskussionen. Viele behaupten, dass C# nur eingeführt wurde, um mit Java gleichzuziehen. Andere gehen davon aus, dass C# Java ist, was aber auf gar keinen Fall zutrifft. Warum C# wirklich eingeführt wurde, haben wir ja schon weiter vorne in diesem Kapitel erläutert. Im Laufe der Zeit wird sich C# der gleichen Beliebtheit erfreuen wie C++. C# kann man als großen Bruder von C++ ansehen.
1.4.4 Objektorientierte Features C# hat die komplett gleichen objektorientierten Features wie C++ und hat darüber hinaus auch noch weitere neue Features implementiert.
Klassen Klassen werden in C# durch die folgende Syntax deklariert: [ public | protected | internal | protected internal | private | abstract | sealed ] class className { } Listing 1.13: Syntax für die Klassendeklaration in C#
Hier sehen Sie alle Schlüsselwörter für die Klassendeklaration in C# beschrieben: Schlüsselwort
Beschreibung
public
Auf die Klasse kann öffentlich zugegriffen werden.
protected
Auf die Klasse kann nur von der gleichen und von abgeleiteten Klassen zugegriffen werden.
internal
Auf die Klasse kann nur innerhalb dieser Assembly zugegriffen werden.
protected internal
Auf die Klasse kann nur innerhalb dieser Assembly oder innerhalb von abgeleiteten Klassen zugegriffen werden.
Tabelle 1.3: Schlüsselwörter für die Klassendeklaration in C#
Sandini Bib 30
1
Einführung in Microsoft .NET
Schlüsselwort
Beschreibung
private
Auf die Klasse kann nur innerhalb der Quelltextdatei zugegriffen werden, in welcher sie deklariert wurde.
abstract
Diese Klasse ist eine abstrakte Klasse, und die Klassenmethoden müssen in der abgeleiteten Klasse implementiert werden.
sealed
Von dieser Klasse kann nicht abgeleitet werden.
Tabelle 1.3: Schlüsselwörter für die Klassendeklaration in C# (Forts.)
Sehen wir uns ein paar Beispiele an: public class Person { // Implementierung folgt hier }
oder protected abstract class Person { // Implementierung folgt hier } Listing 1.14: Beispiele für eine Klassendeklaration
Methoden In C# gibt es keinen Unterschied zwischen Funktionen und Prozeduren, wie z.B. in Visual Basic .Net. Beide Typen werden in C# als Funktionen deklariert (mit oder ohne Rückgabewert). Die Syntax ist wie folgt: [ public | protected | internal | protected internal | private | static | virtual | override | abstract | extern ] [ type | void ] memberName([paramters]) { } Listing 1.15: Syntax für die Funktionsdeklaration in C#
Die verschiedenen Schlüsselwörter sind hier beschrieben: Schlüsselwort
Beschreibung
public
Die Funktion ist öffentlich zugänglich.
protected
Die Funktion ist nur von der Klasse und von abgeleiteten Klassen aus zugänglich.
Tabelle 1.4: Schlüsselwörter für die Funktionsdeklaration in C#
Sandini Bib Die .NET-Programmiersprachen
Schlüsselwort
Beschreibung
internal
Die Funktion ist nur von der Assembly aus zugänglich.
protected internal
Die Funktion ist nur von dieser Assembly und von abgeleiteten Klassen aus zugänglich.
31
private
Die Funktion ist nur von dieser Klasse aus zugänglich.
static
Die Funktion wird von allen Klasseninstanzen benutzt und existiert unabhängig von Klasseninstanzen. Dies bezeichnet man als eine statische Funktion.
virtual
Die Funktion kann von einer abgeleiteten Klasse überschrieben werden.
override
Die Funktion überschreibt eine Funktion einer abgeleiteten Klasse mit der gleichen Funktionssignatur. Die Basisklassenfunktion muss als virtual, abstract oder override deklariert werden.
abstract
Die Funktion ist eine abstrakte Funktion und muss in einer abgeleiteten Klasse implementiert werden.
extern
Die Funktion wird in einer anderen Assembly implementiert.
Tabelle 1.4: Schlüsselwörter für die Funktionsdeklaration in C# (Forts.)
Zum Beispiel: public class Person { public void SetName(string strName, string strVorname) { // Implementierung folgt hier } } Listing 1.16: Beispiel für eine Funktionsdeklaration
Eigenschaften Eigenschaften in C# sind den Eigenschaften in Visual Basic .Net sehr ähnlich und können als öffentliche Variablen mit den Get- und Set-Accessors deklariert werden. Sehen wir uns folgendes Beispiel an: public class Person { private string nachnameValue; private string vornameValue; public string Nachname { get { return nachnameValue; }
Sandini Bib 32
1
Einführung in Microsoft .NET
set { nachnameValue = value; } } public string Vorname { get { return vornameValue; } set { vornameValue = value; } } } Listing 1.17: Beispiel für eine Deklaration von Eigenschaften
Der Quelltext ist fast identisch mit der Visual Basic .Net-Version. Der einzige große Unterschied liegt im Set-Accessor. Dieser hat keinen Parameter mit dem Übergabewert. Stattdessen ist dieser Übergabewert im Schlüsselwort value gespeichert. Das heißt, wir brauchen der privaten Variablen nur den Wert von value zuweisen. Sehen wir uns jetzt an, wie man auf die beiden Eigenschaften zugreifen kann: Person myPerson = new Person(); string wholeString; myPerson.Nachname = "Aschenbrenner"; // Aufruf des Set-Accessors myPerson.Vorname = "Klaus" // Aufruf des Set Accessors // Aufruf des Get-Accessors wholeName = myPerson.Nachname + " " + myPerson.Vorname; Listing 1.18: Beispiel für die Verwendung von Eigenschaften
Konstruktoren Ein Konstruktor trägt in C# den gleichen Namen wie die Klasse. Zum Beispiel: public class Person { private string vornameValue; private string nachnameValue; public person(string strVorname, string strNachname) {
Sandini Bib Die .NET-Programmiersprachen
33
vornameValue = strVorname; nachnameValue = strNachname; } } Listing 1.19: Beispiel für die Verwendung eines Konstruktors in C#
Destruktoren Destruktoren tragen in C# den gleichen Namen wie die Klasse und haben eine Tilde (~) vorangestellt. Zum Beispiel: public class Person { ~person() { // Implementierung folgt hier } } Listing 1.20: Beispiel für die Verwendung eines Destruktors in C#
Vererbung Die Syntax für die Vererbung in C# sieht genauso aus wie in C++. Dabei trennt der Doppelpunkt (:) die Klasse und die Basisklasse, von der geerbt wird. Zum Beispiel: public class Programmierer : Person { // Implementierung folgt hier } Listing 1.21: Beispiel für die Vererbung
Schnittstellen Schnittstellen arbeiten genaus so wie in Visual Basic .Net. Zum Erzeugen einer Schnittstelle verwendet man das Interface-Konstrukt. Zum Beispiel: public interface IPerson { string Vorname(get; set;) string Nachname(get; set;) string GanzerName(); } Listing 1.22: Beispiel für die Deklaration einer Schnittstelle
Sandini Bib 34
1
Einführung in Microsoft .NET
Um eine Klasse von einer Schnittstelle abzuleiten, verwendet man die gleiche Vorgehensweise wie bei der Vererbung: public class Person : IPerson { private string vornameValue; private string nachnameValue; public string Vorname() { // Implementierung folgt hier } public string Nachname() { // Implementierung folgt hier } public string GanzerName() { return vornameValue + " " + nachnameValue; } } Listing 1.23: Beispiel für die Implementierung einer Schnittstelle
1.4.5 Managed C++ Auf Managed C++ wird in diesem Buch nicht näher eingegangen, da es für die WebAnwendungs-Programmierung eine nicht so hohe Bedeutung wie C# und Visual Basic .Net hat.
1.4.6 JScript.NET Auf JScript.NET wird in diesem Buch nicht näher eingegangen, da es für die Web Application Programmierung eine nicht so hohe Bedeutung als C# und Visual Basic .Net hat.
1.5 Common Language Runtime Microsoft .NET wurde von Grund auf so konzipiert, dass von allen .NET-Programmiersprachen auf die gesamte Funktionalität des .NET Frameworks zugegriffen werden kann. Das heißt, dass es eigentlich ganz egal ist, mit welcher Programmiersprache man programmiert. Die Wahl der Programmiersprache richtet sich ganz allein nach dem Programmierer, der persönliche Vorlieben für eine Programmiersprache haben kann.
Sandini Bib Common Language Runtime
35
Mit der Sprachunabhängigkeit von Microsoft .NET ist es sogar möglich, eine Visual Basic .Net-Klasse von einer C#-Klasse abzuleiten. Daher sind Projekte mit verschiedenen Programmiersprachen möglich geworden. Möglich gemacht wird das Ganze durch die so genannte Common Language Runtime. (CLR) Die CLR ist das Fundament, auf dem Microsoft .NET basiert. Um Microsoft .NET komplett zu verstehen, ist es notwendig, sich mit den Details der CLR auseinander zu setzen. Daher werden wir uns jetzt auf den folgenden Seiten der CLR von Microsoft .NET widmen.
Sprachunabhängigkeit Wie vorher erwähnt, kann man in Microsoft .NET mit jeder beliebigen .NETProgrammiersprache programmieren. Dabei kann man in einem Projekt sogar die Programmiersprachen mischen. Das heißt, ein C#-Programmierer erstellt Datenbankzugriffsklassen, die dann ein VB.NET-Programmierer in seiner ASP.NET Anwendung problemlos verwenden kann. Es ist sogar möglich, dass man z.B. von einer VB.NET-Klasse eine C#-Klasse ableiten kann. Wie das konkret aussehen könnte, zeigt das folgende Beispiel: Imports System Namespace WebApplications Public Class HelloWorldFromVB Public Overridable Sub SayHelloWorld Console.WriteLine("Hello World from VB.NET!") End Sub End Class End Namespace Listing 1.24: Hello-World-Klasse in Visual Basic .Net
Mit der folgenden Befehlszeile können wir daraus eine Bibliothek erstellen: vbc hello.vb /t:library
Dann erstellt uns der Compiler eine DLL mit dem Namen hello.dll. Jetzt können wir von der erstellten Klasse in C# eine andere Klasse ableiten: using System; using WebApplications; public class HelloWorldFromCSharp : HelloWorldFromVB { public override void SayHelloWorld() {
Sandini Bib 36
1
Einführung in Microsoft .NET
Console.WriteLine("Hello World from C#!"); } } Listing 1.25: Hello-World-Klasse in C#
Mit folgender Befehlszeile können wir daraus wiederum eine Bibliothek erstellen: csc hello.cs /r:hello.dll
Dabei müssen wir eine Referenz auf die Klasse HelloWorldFromVB (/r:hello.dll) angeben, damit der Compiler die entsprechende Klasse finden kann. So einfach sieht die Sprachunabhängigkeit in Microsoft .NET aus! Die Sprachunabhängigkeit geht so weit, dass sogar ein Debugging über mehrere Sprachen hinweg möglich ist. Man kann z.B. eine Web-Anwendung(in VB.NET geschrieben) debuggen, die C#-Komponenten aufruft. All das macht die CLR möglich. Durch die CLR wird die Produktivität in Entwicklungsteams bedeutend gesteigert, da man Programmierer unabhängig von der erlernten Programmiersprache einstellen kann. Und es müssen auch nicht alle Programmierer die gleiche Sprache "sprechen". Es ist wirklich zum ersten Mal möglich, wirklich »gemischtsprachig« zu programmieren. Das »gemischtsprachige« Programmieren unter .NET macht die Common Language Runtime möglich. Aber was genau ist die Common Language Runtime? Sehen wir sie uns im Detail an. Runtimes sind ja für den Programmierer nichts Neues mehr. Jede Programmiersprache (Visual Basic, C++, Java, FoxPro usw.) hat ihre eigene Laufzeitumgebung, welche die grundlegenden Funktionen zur Verfügung stellt. Die CLR ist eine Umgebung, die die Ausführung des Codes überwacht. Darum heißt Code, der durch die CLR ausgeführt wird, auch Managed Code. Außerdem werden durch die CLR Dienste angeboten, durch welche die Programmierung stark vereinfacht wird. Damit die CLR diese Dienste anbieten kann, müssen die Compiler so genannte Metadaten in den Code einbinden. Was genau diese Metadaten sind, werden wir später noch genauer sehen. Vorerst sei nur so viel gesagt, dass die Metadaten den Code sozusagen beschreiben. Sie speichern Informationen über Typen, Funktionen, Ereignisse, Eigenschaften usw. Wenn man ein .NET-Programm kompiliert, wird kein nativer Code erzeugt. Bei der Kompilierung wird der Programmcode in die so genannte Common Intermediate Language (CIL) übersetzt. Die CIL kann man sich als eine Art Assemblersprache vorstellen. Aber welche Vorteile hat eine Zwischensprache wie die CIL? Bei der Ausführung übersetzt dann die CLR den CIL-Code durch die Hilfe von Jitter in Native Code. Jitter sind Just-In-Time-Compiler.
Sandini Bib Common Language Runtime
37
Durch den Einsatz der Jitter und der CIL kann man .NET-Programme plattformunabhängig schreiben und kompilieren. Auf der Zielplattform braucht dann nur noch das .NET Framework verfügbar zu sein, und die Programme können ohne Quelltextänderung und ohne Neukompilierung auf dieser Plattform laufen. Das ist eines der wichtigsten Ziele von Microsoft .NET – Plattformunabhängigkeit! Über die Jitter gibt es noch einiges zu sagen. Man könnte jetzt vermuten, dass durch die Just-In-Time-Kompilierung die Programme langsamer werden. Man braucht sich ja nur die Performance von Java-Programmen anzusehen. Aber in diesem Bereich hat Microsoft Erstaunliches geleistet. Laut Benchmarks von Microsoft sind .NET-Programme von der Performance her mit Programmen zu vergleichen, die vom aktuellen VC++-Compiler übersetzt worden sind. Die Jitter werden auch Teile von .NET sein, die Microsoft im Laufe der Zeit immer mehr verbessern werden wird, damit die Performance der .NET-Programme noch schneller werden kann. Jetzt stellt sich die Frage, wann diese Jitter überhaupt aufgerufen werden? Das hängt ganz von Ihnen ab! Der Programmierer kann festlegen, wann die Just-In-Time-Compiler aufgerufen werden. Dabei gibt es folgende Möglichkeiten: 왘 Bei der Installation des Programms Hier wird bei der Installation des Programms der CIL-Code in den Native Code der Zielplattform kompiliert. 왘 Beim Starten des Programms Hier wird der CIL-Code beim erstmaligen Starten des Programms in den Native Code der Zielplattform kompiliert. 왘 Beim Ausführen einer Methode Hier wird der CIL-Code während der Ausführung einer Methode in den Native Code der Zielplattform kompiliert. Daraus ergibt sich der Vorteil, dass nur jener Code kompiliert wird, der auch benötigt wird. Die beste Einstellung, die sicherlich in der Zukunft am meisten verwendet werden wird, ist die erste Einstellung, nämlich bei der Installation des Programms. Durch die Jitter ergeben sich ungeahnte Möglichkeiten. Stellen Sie sich vor, Sie schreiben heute ein Programm. Dann wird sich Ihnen vielleicht die Frage stellen, für welchen Prozessortyp Sie das Programm optimieren. Dieser Schritt wird bei Microsoft .NET entfallen, da dies die Jitter übernehmen. Die Jitter können den CIL-Code für die aktuelle Plattform optimieren. Sie können z.B. Mehrprozessorsysteme erkennen und deren Möglichkeiten ausnutzen usw. Daher werden auch .NET-Programme nicht langsamer sein als unsere normalen heutigen Windows-Programme.
Sandini Bib 38
1
Einführung in Microsoft .NET
Da wir jetzt über die Jitter und den CIL-Code Bescheid wissen, sehen wir uns die Tiefen der CLR an. Die CLR hat folgende Bestandteile: 왘 Just-In-Time-Compiler (Jitter) 왘 Garbage Collector 왘 Common Type System 왘 Klassenlader 왘 Code-Manager 왘 Security-Engine 왘 Debug-Engine 왘 Typenchecker 왘 Exception-Manager 왘 Thread-Unterstützung 왘 COM-Marshaler 왘 Klassenbibliothek Einige dieser Bestandteile werden weiter hinten noch genauer erläutert. Aber man sieht schon, dass die CLR sehr vieles umfasst und für vieles zuständig ist.
Common Language Specification Um die Mehrsprachigkeit bei der Programmierung zu ermöglichen, bedient sich Microsoft der Common Language Specification (CLS). Dabei werden Sprachfeatures offen gelegt, deren sich alle Programmiersprachen bedienen. Die CLS definiert dabei z.B. Funktionsaufrufe, Ereignisse, Eigenschaften usw.
Common Type System Für die Interoperabilität zwischen den .NET-Programmiersprachen hat Microsoft das Common Type System definiert. Dadurch wird gewährleistet, dass z.B. ein String in C# genauso angesprochen werden kann wie ein String in Visual Basic .Net. Heute sieht es so aus, dass jede Programmiersprache ihre primitiven Datentypen hat. In Visual Basic gibt es z.B. Integer, String und Double. In Visual C++ gibt es wiederum long, ulong und char *. Und diese Typen sind untereinander leider nicht kompatibel. Haben Sie schon einmal probiert, eine COM-Komponente zu erstellen, die in allen Programmiersprachen funktioniert und gleich anzuwenden ist? Es ist eine echte Herausforderung. Oder haben Sie schon mal probiert, einer COM-Komponente, die in C++ geschrieben wurde, einen String von einem Visual Basic-Programm zu übergeben? All diese Probleme löst das Common Type System.
Sandini Bib Common Language Runtime
39
Die folgende Tabelle listet alle Typen auf, die das Common Type System definiert hat: Datentyp
Beschreibung
Bereich/Größe
System.Boolean
Repräsentiert einen BooleanWert.
True oder False. System.Boolean kann man z.B. in keinen Integer konvertieren.
System.Byte
Repräsentiert ein Unsigned Byte. Positive Integer zwischen 0 und 255.
System.Char
Repräsentiert ein UNICODEZeichen.
Jedes mögliche UNICODE-Zeichen
System.DateTime
Repräsentiert Datums- und Zeitwerte.
IEEE 64 Bit (8 Byte) Long Integers mit einer Reichweite vom 1. Januar 1 bis zum 31. Dezember 9999 und von 0:00:00 bis 23:59:59
System.Decimal
Repräsentiert positive und negative Werte mit 28 Ziffern.
79.228.162.514.264.337.593.543.950.335 bis –79.228.162.514.264.337.593.543.950.335
System.Double
Repräsentiert eine 64-Bit-Gleitkommazahl mit doppelter Genauigkeit.
1,79769313486231570E+308 bis –1,79769313486231570E+308
System.Int16
Repräsentiert einen 16-Bit-Integerwert.
32768 bis – 32767
System.Int32
Repräsentiert einen 32-Bit-Integerwert.
2.147.483.648 bis –2.147.486.648
System.Int64
Repräsentiert einen 64-Bit-Integerwert.
9.223.372.036.854.775.808 bis – 9.223.372.036.854.775.808
System.Sbyte
Repräsentiert einen 8-Bit-Integerwert.
128 bis –127
System.Single
Repräsentiert eine 4-Bit-Gleitkommazahl mit einfacher Genauigkeit.
3.402823E+38 bis –3.402823E+38
System.TimeSpan
Repräsentiert eine Zeitspanne, entweder positiv oder negativ.
Das MinValue Feld ist –10675199.02:48:05.4775808. Das MaxValue Feld ist 10675199.02:48:05.4775807
System.String
Repräsentiert einen UNICODEString.
Null oder mehrere UNICODE-Zeichen
System.Array
Repräsentiert ein eindimensionales Array
Reichweite basiert auf der Deklaration. Arrays können andere Arrays beinhalten.
System.Object
Basisobjekt, von welchem alle anderen Typen abgeleitet werden.
Tabelle 1.5: Datentypen in Microsoft .NET
Sandini Bib 40
1
Einführung in Microsoft .NET
Das Common Type System definiert die Richtlinien, wie Typen deklariert, verwendet und von der Runtime verwaltet werden. Außerdem ist das Common Type System ein wichtiger Bestandteil für die Sprachunabhängigkeit bei der Programmierung. Es führt folgende Funktionen durch: 왘 Es baut ein Framework auf, welches die Sprachunabhängigkeit bei der Programmierung, die Typensicherheit und höchste Performance bei der Ausführung von Code gewährleistet. 왘 Es bietet ein objektorientiertes Modell, welches die komplette Implementierung von vielen Programmiersprachen vorsieht. 왘 Es definiert Regeln, an die sich Programmiersprachen halten müssen, damit die Sprachunabhängigkeit bei der Programmierung gegeben sein kann. Das Common Type System unterstützt zwei verschiedene, grundlegende Kategorien von Typen: 왘 Value Types: Value Types werden auf dem Stack angelegt und repräsentieren primitive Typen. 왘 Reference Types: Reference Types werden auf dem Managed CLR Heap angelegt und repräsentieren Objekte. Wenn man von Value Types und Reference Types unter Microsoft .NET spricht, dann fallen in diesem Zusammenhang auch die Wörter Boxing und Unboxing. Unter Boxing versteht man das Konvertieren von einem Value Type in einen Reference Type. Als Unboxing ist das Konvertieren eines Reference Type in einen Value Type definiert. Schauen wir uns folgende Variablendeklaration an: int i = 123;
Mit der folgenden Zeile wird die Variable i in ein Objekt geboxt: object o = i;
Nachdem wir uns das Boxing angeschaut haben, sehen wir uns jetzt das Unboxing an. Dazu folgende Codezeilen: int i = 123; // Value Type object box = i; // Boxing int j = (int)box; // Unboxing
In der ersten Zeile wird die Variable i als Value Type auf dem Stack angelegt. In der nächsten Zeile wird dann ein Objekt auf dem Heap angelegt und die Variable i in dieses Objekt geboxt. In der letzten Zeile wird dann das Objekt in eine neue Integervariable ungeboxt.
Sandini Bib Common Language Runtime
41
Dass man Boxing und Unboxing versteht, wirkt sich sehr auf die Programmierung aus, da diese beiden Operationen sehr viel Rechenleistung benötigen. Immer wenn ein Value Type geboxt wird, wird ein Reference Type erzeugt, und der Value Type wird auf den managed Heap kopiert. Abhängigkeit von der Größe des Value Type und der Zahl der Boxingoperationen benötigt die CLR einige CPU-Zyklen, die sich negativ auf die Performance der Programme auswirken können. Um diese Theorie in der Praxis anzusehen, habe ich ein kleines Beispielprogramm geschrieben, das die Verwendung von Value Types und Reference Types veranschaulicht und zeigt, wann die Boxing- und Unboxing-Mechanismen angewendet werden. using System; using System.Collections; public class TestApplication { public struct Person { string Name; int Alter; } public static void Main() { Hashtable dictionary = new Hashtable(); Person p = new Person(); p.Name = "Aschenbrenner Klaus"; p.Alter = 21; dictionary.Add("Klaus", p); p.Name = "Aschenbrenner Dagmar"; p.Alter = 43; dictionary.Add("Dagmar", p); p = CType(dictionary.Item("Klaus"), Person); Console.WriteLine("Name ist {0} und Alter ist {1}", p.Name, p.Alter); } } Listing 1.26: Beispiel zu Boxing und Unboxing
Schauen wir uns das Beispiel näher an. Zuerst werden die Namespaces System und System.Collections importiert, damit wir auf die Objekte Hashtable und Console Zugriff bekommen. Dann erzeugen wir eine einfache Struktur, die die Daten einer Person speichert. Dabei ist zu beachten, dass eine Struktur unter der CLR ein Value Type ist.
Sandini Bib 42
1
Einführung in Microsoft .NET
Danach deklarieren wir unsere Startfunktion Main. Anschließend werden die Objekte dictionary und p deklariert. In den nächsten Zeilen werden zwei Objekte in der Hashtable abgelegt. Die Funktion Add der Hashtable verlangt als zweiten Parameter ein Objekt. Und genau hier kommt das Boxing zum Vorschein. Hier muss ein Value Type in einen Reference Type konvertiert werden. In den letzten drei Zeilen wird dann aus der Hashtable das gewünschte Objekt anhand des definierten Schlüsselbegriffes ausgelesen. Hier kommt das Unboxing zum Tragen. Hier sei noch gesagt, dass die Funktion CType ein Objekt in ein anderes Objekt konvertiert. Schlussendlich werden dann noch der Name und das Alter der betreffenden Person auf der Konsole ausgegeben. An diesem Beispiel sieht man, dass man während der Programmierung sehr oft mit den Boxing- und Unboxing-Mechanismen des .NET Frameworks zu tun hat. Wie gesagt, muss man hier aufpassen, dass sich keine größeren Performanceprobleme ergeben.
1.6 Assemblies Wenn man sich mit der traditionellen Windows-Programmierung beschäftigt, wird man sich früher oder später mit den so genannten Dynamic Link Libraries (DLL) herumschlagen. In diesen DLLs werden Funktionen gespeichert, auf die alle WindowsProgramme zugreifen können. Der komplette Windows-Kern, auch Kernel genannt, basiert auf solchen Bibliotheken. Auf die Probleme, die sich daraus ergeben können, wurde bereits am Anfang dieses Kapitels eingegangen (DLL-Hölle). Wenn man mit Microsoft .NET programmiert, wird man im Laufe der Zeit auch Code schreiben, der von mehreren Programmen benutzt werden kann. Hierbei werden dann auch DLLs erzeugt. Von der Struktur her sehen sie aus wie herkömmliche DLLs, aber verwendet werden sie ganz anders. Microsoft nennt sie auch offiziell nicht mehr Dynamic Link Libraries, sondern Assemblies, was zu deutsch so viel wie Baugruppe heißt. Sehen wir uns an, welche Aufgaben solche Assemblies erledigen: 왘 Eine Assembly beinhaltet CIL-Code, den die CLR ausführt. Dieser Code liegt im Portable Executive Format (PE) vor. 왘 Jede Assembly formt eine Sicherheitsgrenze. Eine Assembly ist eine Einheit, welche Zugriffserlaubnisse erbittet und bewilligt. 왘 Jede Assembly formt eine Typengrenze. Jeder Typ (Klasse) ist durch die Assembly, in der er liegt, und durch seinen Namen eindeutig identifizierbar. Der Typ mit dem Namen MyType in der Assembly A ist ein anderer Typ als der Typ MyType in der Assembly B. Im Manifest der Assembly wird genau festgelegt, welche Typen in dieser Assembly definiert sind.
Sandini Bib Assemblies
43
왘 Jede Assembly formt eine Reference-Grenze. Im Manifest der Assembly ist genau festgelegt, auf welche Typen anderer Assemblies sich diese Assembly bezieht. Die Assembly spezifiziert die Typen, die in anderen Assemblies deklariert sind. Außerdem listet das Manifest alle anderen Assemblies auf, auf die sich die Assembly bezieht. 왘 Jede Assembly formt eine Versionierungsgrenze. Eine Assembly ist die kleinste Einheit in der CLR, die versioniert werden kann. Alle Typen und Ressourcen innerhalb der Assembly werden als eine Einheit versioniert. Das Manifest beschreibt außerdem die Versionsabhängigkeit zwischen den abhängigen Assemblies. 왘 Jede Assembly formt eine Deployment-Einheit. Wenn eine Applikation gestartet wird, müssen nur die Assemblies vorhanden sein, die die Assembly während des Ladevorgangs aufruft. Andere Assemblies, wie z.B. Lokalisierungsressourcen oder Utilityklassen, können auf Nachfrage z.B. über das Internet nachgeladen werden. Dies ermöglicht Anwendungen, deren Download sehr klein und schnell ist. 왘 Eine Assembly ist eine Einheit, in welcher die Side-By-Side-Execution möglich ist. Unter Side-By-Side-Execution versteht man, dass mehrere Versionen einer Assembly nebeneinander unabhängig voneinander laufen können. Die Side-By-Side-Execution wird weiter hinten in diesem Buch näher erläutert. In den vorhergehenden Punkten wurde öfter vom so genannten Manifest geredet. Das Manifest wird später in diesem Kapitel erklärt. Assemblies können nicht nur Code enthalten, sondern auch Ressourcen (wie z.B. Bitmaps, JPEG-Dateien usw.). Assemblies, die direkt auf der Festplatte gespeichert werden, bezeichnet man als statische Assemblies. Wenn aber Assemblies sozusagen "on the fly" während des Programmablaufs im Speicher erzeugt werden, nennt man sie dynamische Assemblies. Wenn man jetzt der Ansicht ist, dass eine Assembly einfach eine DLL ist, dann stimmt das nicht ganz. Eine Assembly kann auch mehrere DLLs und andere Dateien, wie z.B. Ressourcendateien, umfassen. Aber eine dieser DLLs muss das Manifest beinhalten. In diesem wird genau gespeichert, welche Dateien zu dieser Assembly gehören. Alle anderen Dateien, die nicht das Manifest enthalten, werden Module genannt. Im Allgemeinen beinhaltet eine statische Assembly folgende Elemente: 왘 Das Manifest, das die Metadaten beinhaltet 왘 CIL-Code, der die Typen der Assembly implementiert 왘 Ressourcen Von diesen drei Punkten wird eigentlich nur das Manifest benötigt, aber Code oder Ressourcen sind dazu notwendig, um der Assembly eine Funktionalität zu verleihen.
Sandini Bib 44
1
Einführung in Microsoft .NET
Single-File Assemblies Wenn man all diese drei Punkte in einer physischen Assembly gruppiert, spricht man von einer Single-File-Assembly. Single-File-Assemblies kann man entweder mit den Kommadozeilentools oder mit dem Visual Studio .NET erzeugen. In den nächsten Zeilen möchte ich zeigen, wie man mit den Kommandozeilentools Single-File-Assemblies erzeugen kann. Das Erzeugen einer Assembly mit einer .EXE-Erweiterung geschieht mit folgendem Kommando: csc myAssemblyCode.cs
csc steht dabei für den C#-Compiler. Durch diesen Befehl erzeugt der Compiler eine Assembly mit dem Namen myAssemblyCode.exe. Mit dem Parameter /out kann man den Namen der Assembly angeben (z.B. csc /out myFirstAssembly.exe myAssemblyCode.cs). Mit den letzten Befehlen haben wir Single-File-Assemblies erzeugt, welche immer einen Eintrittspunkt, wie z.B. Main oder WinMain, enthalten müssen. Wenn in einer Single-File-Assembly kein Eintrittspunkt vorhanden ist, meldet der Compiler einen Fehler und bricht die Kompilierung ab. Aber was ist, wenn man keinen Eintrittspunkt benötigt? Für diesen Fall gibt es die so genannten Bibliotheks-Assemblies. Eine Bibliotheks-Assembly ist wie eine Klassenbibliothek. Sie beinhaltet Klassen und Typen, welche von anderen Assemblies referenziert werden, hat aber keinen Eintrittspunkt, wo die Ausführung beginnt. Um eine Bibliotheks-Assembly zu erzeugen, kann man folgenden Befehl ausführen: csc /out:myAssembly.dll /t:library myCode.cs
Über den Schalter /t (steht für target) kann man festlegen, dass eine Bibliothek erzeugt wird. Diese Bibliothek beinhaltet dann die Klassen und Typen und hat keinen Eintrittspunkt.
Multi-File-Assemblies Alternativ kann man diese drei Punkte auf verschiedene Dateien verteilen. Diese Dateien können Module mit kompiliertem CIL-Code sein, Ressourcendateien (.bmp, .jpg) oder andere Dateien, die von der Applikation benötigt werden. Diese werden dann Multi-File-Assemblies genannt. Sie sind sehr vorteilhaft, wenn man z.B. Utilityklassen hat, die nicht oft benötigt werden. Diese können dann z.B. als zusätzlicher Download zur Verfügung gestellt werden.
Sandini Bib Assemblies
45
In diesem Beispiel gehören alle drei Dateien zu einer Assembly, so wie es im Manifest der Assembly beschrieben ist. Bei der Programmierung werden alle drei Dateien als eine Assembly angesprochen, aber im Kontext des Dateisystems besteht die Assembly aus drei separaten Dateien. Util.netmodule wurde als Modul kompiliert, damit es kein Manifest enthält. Als die Assembly erzeugt wurde, wurde das Manifest in die Datei MyAssembly.dll aufgenommen. Das Manifest selbst hat wiederum Referenzen auf die Dateien Util.netmodule und Graphic.bmp aufgenommen. Multi-File-Assemblies kann man nur von der Kommandozeile aus erzeugen. Visual Studio .NET beinhaltet zur Zeit noch keine Funktionen, um Multi-File-Assemblies direkt aus der Entwicklungsumgebung heraus zu erzeugen. Wie schon weiter vorne erwähnt, muss eine Datei in der Assembly das Manifest enthalten, das die Assembly näher beschreibt. Eine Assembly, die ein Programm ist, muss außerdem einen Eintrittspunkt, wie z.B. Main oder WinMain, enthalten. Nebenbei sei noch bemerkt, dass eine Multi-File-Assembly nur einen Eintrittpunkt haben kann. Warum gibt es überhaupt Multi-File-Assemblies? Multi-File-Assemblies wurden unter anderem aus folgenden Gründe eingeführt: 왘 Kombinieren von Modulen, die in verschiedenen Sprachen geschrieben wurden. Das ist der häufigste Grund für die Verwendung von Multi-File-Assemblies. 왘 Optimierung des Downloads. Klassen und Typen, die selten verwendet werden, können in eine eigene Assembly gepackt werden, die nur bei Bedarf heruntergeladen wird. 왘 Kombinieren von Modulen, die von verschiedenen Programmierern geschrieben wurden. Jeder Programmierer kann seine Codemodule kompilieren und anschließend werden diese dann in einer Multi-File-Assembly zusammengeführt. Wenn man dann die Multi-File-Assembly erzeugt hat, kann man die Datei, die das Manifest enthält, mit einer digitalen Signatur signieren und anschließend in den Global Assembly Cache (GAC) kopieren. Wie das genau vor sich geht, sehen wir weiter hinten unter dem Punkt Shared Assemblies.
Private Assemblies Wenn man mit dem .NET Framework Assemblies erstellt, erzeugt man wahrscheinlich so genannte Private Assemblies. Private Assemblies sind Assemblies, die nur von einer Applikation verwendet werden. Wenn eine Assembly von mehreren Anwendungen verwendet wird, spricht man von so genannten Shared Assemblies. Diese werden später näher erläutert. Private Assemblies werden im Bin-Verzeichnis der Applikation gespeichert. Wenn eine Assembly nicht im Bin-Verzeichnis gespeichert ist, sucht die CLR die Assembly im Glo-
Sandini Bib 46
1
Einführung in Microsoft .NET
bal Assembly Cache (GAC). Der GAC wird im nächsten Abschnitt erläutert. Da also Assemblies entweder im Bin-Verzeichnis einer Anwendung oder im GAC gespeichert werden, müssen sie beim System nicht mehr registriert werden. Natürlich kann man das Laufzeitverhalten einer Anwendung so abändern, dass man Assemblies auch in einem anderen Verzeichnis als im Bin-Verzeichnis speichern kann. Dafür muss man eine XML-Konfigurationsdatei anlegen, welche sich im Anwendungsverzeichnis befinden muss. Außerdem muss sie den Namen der Hauptassembly mit der Endung .config tragen. Nehmen wir an, unsere Hauptassembly heißt TestApplication, dann heißt die Konfigurationsdatei TestApplication.config. Außerdem nehmen wir noch an, dass wir unsere privaten Assemblies im Unterverzeichnis MyAssemblies speichern wollen. Der Eintrag in der Konfigurationsdatei würde dann wie folgt aussehen: Listing 1.27: TestApplication.config
Anschließend sucht die CLR nach den privaten Assemblies nicht mehr im Bin-Verzeichnis, sondern im Verzeichnis MyAssemblies. In der Eigenschaft PrivatePath kann man auch mehrere Verzeichnisse angeben, welche durch einen Strichpunkt getrennt werden müssen.
Shared Assemblies Wenn Assemblies von mehreren Anwendungen verwendet werden können, nennt man sie Shared Assemblies. Shared Assemblies werden sehr oft von Firmen entwickelt und von anderen Firmen verwendet. Die Basisklassenbibliothek des .NET Frameworks ist hierbei ein sehr gutes Beispiel für eine Shared Assembly, denn alle Anwendungen, die auf diesem Framework basieren, werden Teile davon verwenden. Der wichtigste Punkt ist, dass Private und Shared Assemblies die gleiche Struktur haben. Es macht also fast keinen Unterschied, für welchen Typ man sich bei der Programmierung entscheidet. Sie benutzen das gleiche PE-Format, die gleichen Metadaten und der CIL-Code ist vom Aufbau her auch komplett identisch. Als Entwickler benutzt man auch die gleichen Tools zum Erstellen von Privaten und Shared Assemblies. Die wichtigsten Unterschiede ergeben sich in der Namensgebung, der Versionierung und der Unterbringung im Dateisystem. Private Assemblies werden am Namen der PE-Datei identifiziert (ohne Erweiterung), in der das Manifest liegt. Aber für Shared Assemblies reicht diese einfache Methode nicht mehr aus. Shared Assemblies werden
Sandini Bib Assemblies
47
von verschiedenen Firmen programmiert, und daher kann es bei der Namensgebung sehr leicht Überschneidungen geben. Daher bekommen Shared Assemblies Strong Names. Damit die CLR Shared Assemblies finden kann, müssen sie in einem speziellen Verzeichnis gespeichert werden, dem GAC. Dieser befindet sich im Verzeichnis C:\Winnt\Assembly. Aber es reicht nicht aus, einfach nur die Assembly in dieses Verzeichnis zu kopieren, da Shared Assemblies beim System angemeldet werden müssen. Diesen Vorgang erledigt das Utility Al.exe des .NET Framework SDK. Die Syntax für eine Installation einer Shared Assembly schaut folgendermaßen aus: al /I:Dateiname
Dabei steht der Schalter I für Install. Wenn wir z.B. unsere Assembly MyAssembly.dll in den GAC installieren wollen, müssen wir folgende Befehlszeile ausführen: al /I:MyAssembly.dll
Wenn man sich das GAC-Verzeichnis im Windows Explorer ansieht, wird auch ein spezieller Viewer geladen, der dieses Verzeichnis in einer speziellen Form anzeigt. Dabei sieht man Informationen, wie z.B. den Global Assembly Name, die Versionsnummer der Shared Assembly, die Kultur der Assembly und andere diverse Infos. Diese Installation der Assemblies im GAC verletzt eigentlich das Ziel, die Installation, die Sicherung, Kopierung und die Entfernung der Applikation so einfach wie möglich zu machen. Daher schlägt Microsoft auch vor, für den normalen Gebrauch auch nur Private Assemblies zu verwenden, da hier keine Registrierung beim System notwendig ist. Der GAC führt eine Datenbank, in welcher alle Shared Assemblies gespeichert werden, und leicht zu finden sind. Eingetragen wird dabei der Strong Name (starker Name) der Assembly. Daraus stellt sich jetzt die Frage, was ein so genannter Strong Name eigentlich ist? Jede Firma, die eine Shared Assembly veröffentlichen will, braucht ein Verfahren, das gewährleistet, dass ihre Assembly eindeutig identifizierbar ist. Und genau das leisten die Strong Names. Microsoft hat dazu ein Verfahren gewählt, das sich der Public Key Encryption bedient, die man vom Emailverkehr her kennt.
Strong Names Um einer Shared Assembly einen Strong Name geben zu können, braucht man zuerst ein Schlüsselpaar mit einem öffentlichen und einen privatem Schlüssel. Dieses Schlüsselpaar dient zur Kennzeichnung der Shared Assembly. Der Strong Name besteht aus dem Namen, der Versionsnummer und der Kulturinformationen der Assembly und außerdem aus dem öffentlichen Schlüssel und einer digitalen Signatur.
Sandini Bib 48
1
Einführung in Microsoft .NET
Die digitale Signatur wird so berechnet, das sie nach menschlichem Ermessen eindeutig ist. Jeffrey Richter hat dies im System Journal 04/2001 auf Seite 19/20 wie folgt beschrieben: Wenn Sie einer Assembly einen starken Namen geben, erhält die FileDef-Tabelle im Manifest eine Liste aller Dateien, aus denen sich die Assembly zusammensetzt. Beim Eintrag der Dateinamen ins Manifest wird eine Prüfsumme über jede eingetragene Datei errechnet und zusammen mit dem Dateinamen in die FileDef-Tabelle eingetragen. Den Prüfsummenalgorithmus können Sie übrigens auf zwei Wegen überschreiben: einmal mit dem Schalter /algid von Al.exe oder auf Assemblysebene mit dem anwenderdefinierten Attribut System.Reflection.AssemblyAlgIDAttribute. Sobald die PE-Datei fertig ist, in der das Manifest liegt, wird eine Prüfsumme über die gesamte Datei gebildet. Als Prüfsummenalgorithmus wird immer SHA-1 eingesetzt. Der Algorithmus lässt sich auch nicht überschreiben. Die resultierende Prüfsumme, die um die 100-200 Bytes groß ist, wird mit dem privaten Schlüssel des Herausgebers signiert und die resultierende digitale RSA-Signatur wird in einem reservierten Abschnitt der PE-Datei untergebracht, der natürlich nicht in die Prüfsummenberechnung einbezogen wird. Dann wird der CLR-Kopf der PE-Datei aktualisiert, damit er die richtige Stelle angibt, an der die digitale Signatur in der Datei zu finden ist. Auch der öffentliche Schlüssel des Herausgebers wird in dieser PE-Datei untergebracht, und zwar in der Metadaten-Tabelle AssemblyDef des Manifests. Durch die Kombination von Dateinamen und öffentlichem Schlüssel erhält dieses Assembly einen Strong Name, der nach allem menschlichen Ermessen unverwechselbar ist. Solange alles mit rechten Dingen zugeht und die Firmen nicht dasselbe Schlüsselpaar benutzen, können zwei verschiedene Firmen einfach keine Assembly mit demselben öffentlichen Schlüssel versehen. Um eine Shared Assembly mit einer digitalen Signatur zu signieren, braucht man einen privaten und einen öffentlichen Schlüssel. Dieses Schlüsselpaar wird mit dem Kommandozeilentool sn.exe erstellt. Um z.B. ein Schlüsselpaar zu erstellen, verwendet man folgende Befehlszeile: sn –k myKeyFile.snk
Dieser Befehl erstellt die Datei myKeyFile.snk, die den öffentlichen und den privaten Schlüssel enthält. Um jetzt eine Assembly mit diesem öffentlichen Schlüssel zu signieren, verwendet man das Kommandozeilentool al.exe. Eine typischer Befehl würde wie folgt aussehen: al /out:MyAssembly.dll MyModule.netmodule /keyfile:myKeyFile.snk
Dieser Befehl würde die Assembly mit dem vorher erstellten öffentlichen Schlüssel signieren.
Sandini Bib Assemblies
49
Installation von Assemblies Assemblies, die man installieren will, brauchen keine bestimmte Verpackung mehr. Am einfachsten ist es, wenn man die Assemblies direkt in das gewünschte Verzeichnis kopiert. Diesen Vorgang der Installation nennt daher auch XCopy Deployment. Da die Assemblies selbst alle Informationen im Manifest speichern, brauchen sie nicht mehr in der Registrierung oder im Active Directory registriert werden. Der Benutzer braucht nur noch die Anwendung zu starten und die Laufzeitschicht findet dann die installierten Assemblies von alleine. Wenn man dann die Anwendung entfernen will, braucht man nur noch das Anwendungsverzeichnis inklusive der Assemblies zu löschen, da ja keine Informationen mehr in der Registrierung gespeichert werden. Die Dateien einer Assembly kann man natürlich auch z.B. mithilfe von .cab-Dateien über das Internet verteilen. Durch die Verwendung von .cab-Dateien reduziert sich die Größe der Assemblies, da .cab-Dateien ja komprimiert sind. Daher ergeben sich durch die Verwendung von .cab-Dateien kürzere Downloadzeiten.
Versionsnummern Stellen Sie sich vor, Sie schreiben Anwendungen, die mehrere gemeinsame Assemblies verwenden. Nach ein paar Monaten entscheidet sich die Firma, ein Update der gemeinsamen Assemblies herauszubringen, die Bugs behebt und neue Features bringt. Nun könnten Sie diese neuen Assemblies einfach den Kunden schicken und sie auffordern, die alten Assemblies einfach mit den neuen Assemblies zu überschreiben. Wenn nun der Anwender die Programme startet, sollten die Bugs behoben, und die neuen Features zugänglich sein, oder? Normalerweise müssten die neuen Assemblies korrekt laufen, aber es könnte ja sein, dass sich wieder neue Programmierfehler eingeschlichen haben. In der heutigen Zeit kann man nur dringendst empfehlen, keine Assemblies einzusetzen, die von mehreren Programmen verwendet werden. Dies erhöht zwar den Bedarf an Festplattenspeicher, aber dieser ist zurzeit billiger als je zuvor. Wenn man Assemblies mit verschiedenen Versionen einsetzen will, bekommt man Hilfe von der Common Language Runtime. Diese bietet eine integrierte Versionierung von Shared Assemblies. Eine Versionierung von Private Assemblies ist zurzeit von Microsoft nicht vorgesehen. Jede Assembly hat eine bestimmte Versionsnummer, die nach einem bestimmten Schema aufgebaut ist. Diese Nummer setzt sich aus drei logischen Teilen zusammen, die insgesamt in vier Zifferngruppen geschrieben werden. Ein Bespiel einer Versionsnummer ist z.B. 1.2.1860.2. Die ersten beiden Nummern bilden die logische Version der Assembly. Die erste Nummer ist die Hauptversionummer (Major number) und die zweite Nummer ist die Unterversionsnummer (Minor number). Die dritte Nummer
Sandini Bib 50
1
Einführung in Microsoft .NET
gibt die Build-Nummer der Assembly an. Wenn eine Firma z.B. eine Assembly jeden Tag neu kompiliert, sollte auch die Build-Nummer jeden Tag um eins erhöht werden. Die letzte Nummer gibt schlussendlich noch die Revisionsnummer dieser Build-Version an. Wenn z.B. eine Firma eine Assembly aus irgendeinem Grund zweimal kompilieren muss, um z.B. einen bestimmten Fehler zu beheben, der sonst den gesamten Betrieb aufhalten würde, sollte man die Revisionsnummer um eins erhöhen. Die CLR betrachtet Assemblies mit verschiedenen Versionsnummer als verschiedene Assemblies. Wenn z.B. eine Anwendung eine Assembly in der Version 2.0.0.0 benötigt, wird die CLR die Anwendung immer an die Assembly mit der Version 2.0.0.0 binden. Wenn dann die Assembly mit der Version 2.5.0.0 installiert wird, kann die CLR die Anwendung nicht mehr an die gewünschte Assembly binden. Dies ist das Standardverhalten der CLR, das aber der Administrator beeinflussen kann. Wenn aber die CLR zwei Assemblies mit der gleichen Haupt- und Unterversionsnummer findet, verwendet die CLR die Assembly mit der höchsten Build- und Revisionsnummer. Dies ist wiederum das Standardverhalten der CLR, das der Administrator bei Bedarf wieder beeinflussen kann. Die Versionsnummer einer Assembly kann man entweder mit dem Schalter /version des Kommandozeilentools al.exe angeben oder mithilfe des Attributes System.Reflection.AssemblyVersionAttribute. Wenn man keine Versionsnummer angibt, wird die Standardversionsnummer 0.0.0.0 verwendet. Beide Methoden nehmen die Versionsnummer in das Manifest der Assembly auf. Schauen wir uns ein Beispiel an, wie man die Versionsnummer mithilfe des Versionsattributes angeben kann: [assembly:AssemblyVersion("2.5.1860.4")]
Diese Zeile würde die Versionsnummer der Assembly auf 2.5.1860.4 festlegen.
1.7 Metadaten Auf den vorherigen Seiten ist schon oft das Wort Metadaten gefallen. In diesem Abschnitt schauen wir uns jetzt genauer an, was die Metadaten sind und warum sie von Microsoft eingeführt wurden. Wie schon erwähnt wurde, müssen in Microsoft .NET keine Komponenten mehr beim System registriert werden. Dieser Schritt kann deshalb entfallen, da alle .NET-Komponenten die Informationen selbst im Manifest speichern. Diese Informationen, die Metadaten, werden dabei in verschiedenen Tabellen innerhalb der Assembly gespeichert. Die Common Language Runtime braucht dann nicht mehr die Registrierung nach diesen Informationen zu durchsuchen, sondern sucht sie in den Assemblies selbst.
Sandini Bib Metadaten
51
Welche Vorteile ergeben sich durch diese Vorgehensweise? Da die Assemblies nicht mehr registriert werden müssen, kann man sie einfach durch Kopieren auf einem Zielsystem installieren. Dadurch wird echtes XCopy Deployment möglich gemacht. Die Deinstallation von Software wird auch sehr vereinfacht, da nur der entsprechende Applikationsordner gelöscht werden muss und auch keine Informationen mehr in der Registrierung zurückbleiben. Wenn jetzt ein Programm unter Microsoft .NET kompiliert wird, werden die Metadaten in die Metadatentabellen in der Assembly eingetragen, und der ausführbare Code wird zur CIL kompiliert. Alle Klassen und Typen, die in einer Assembly referenziert oder verwendet werden, werden innerhalb der Metadaten genau beschrieben. Wenn dann der Code ausgeführt wird, werden die Metadaten von der CLR in den Speicher geladen und die entsprechenden Informationen über die Klassen, Typen, Vererbungen usw. aus den Metadaten gelesen. Danach können weitere Assemblies geladen werden, welche in der ursprünglichen Assembly referenziert wurden. Die Metadaten speichern die folgenden Informationen über die Assembly: 왘 Beschreibung der Assembly –
Mitglieder (Methoden, Felder, Eigenschaften, Events, eingebettete Typen)
왘 Attribute –
Zusätzliche, beschreibende Elemente, die Typen und Klassen näher beschreiben
Vorteile der Metadaten Die Metadaten sind der Schlüssel zu einer einfacheren Programmierung, beseitigen die Notwendigkeit der Verwendung von Dateien der Interface Definition Language (IDL) Dateien und Header Dateien. Metadaten erlauben den .NET-Programmiersprachen, dass sie sich selbst in einer einheitlichen Form beschreiben können, unbemerkt vom Programmierer und dem Anwender.
Sandini Bib 52
1
Einführung in Microsoft .NET
Zusätzlich sind die Metadaten durch die Verwendung von Attributen erweiterbar. Metadaten bieten die folgenden großen Vorteile: 왘 Selbstbeschreibende Dateien CLR-Module und Assemblies sind selbstbeschreibend. Die Metadaten eines Moduls oder einer Assembly beinhalten alles, um mit einem anderen Modul oder einer anderen Assembly zu interagieren. Metadaten bieten die gleiche Funktionalität wie die IDL in COM, die erlaubt, dass man die Definition und die Implementierung in einer Datei unterbringen kann. Module und Assemblies müssen beim Betriebssystem nicht registriert werden. 왘 Interoperabilität der Programmiersprachen und einfacheres komponentenbasiertes Design Die Metadaten beinhalten alle Informationen über den kompilierten Code, um eine Klasse von einer PE-Datei in einer anderen Programmiersprache abzuleiten. Man kann eine Klasse von jeder Klasse ableiten, die von einer .NET-Programmiersprache geschrieben wurde, ohne sich Sorgen um das Marshaling und andere Dinge zu machen. 왘 Attribute Das .NET Framework erlaubt einem, spezifische Arten von Metadaten, die so genannten Attribute, zu erzeugen. Attribute findet man überall im .NET Framework. Sie werden verwendet, um das Laufzeitverhalten von Anwendungen unter .NET zu beeinflussen. Außerdem kann man benutzerdefinierte Typen einführen. Die Metadaten sind in einem Teil der PE-Datei gespeichert und der ausführbare CILCode ist in einem anderen Teil der PE-Datei gespeichert. Die Metadaten beinhalten verschiedene Tabellen und eine große Anzahl von Strukturen. Der CIL-Codeteil beinhaltet CIL-Code, der sich wiederum auf die Metadatentabellen und Strukturen bezieht. Jede Metadatentabelle beinhaltet Informationen über die Elemente des Programms. Zum Beispiel beschreibt eine Metadatentabelle alle Klassen in dem Code, eine andere Tabelle alle Felder, und eine andere wiederum z.B. alle Eigenschaften innerhalb des Codes. Wenn man dann z.B. zehn Klassen programmiert hat, hat diese Tabelle zehn Reihen, die die Klassen näher beschreiben. Außerdem verweisen die Metadatentabellen auf andere Tabellen und Strukturen innerhalb der Metadaten. Zum Beispiel verweist die Metadatentabelle der Klassen auf die Metadatentabelle der Klassenmethoden. Die Metadaten speichern in vier großen Strukturen Informationen: String, Blob (binary large object), User String und GUID. Beispielsweise wird der Name einer Klasse nicht direkt in der Metadatentabelle gespeichert, sondern verweist auf den Namen in der Stringstruktur.
Sandini Bib Metadaten
53
Metadaten innerhalb der PE-Datei Wenn ein Programm für die Common Language Runtime kompiliert wird, wird eine PE-Datei erstellt, die aus drei Teilen besteht. Die folgende Tabelle beschreibt diese drei Teile. PE Teil
Inhalt des PE-Teils
PE Header
Speichert die Adresse des Eintrittspunkts. Die Runtime verwendet diese Informationen, um die Datei als eine PE-Datei zu identifizieren und um festzustellen, wo die Ausführung bei einem Start beginnen soll.
CIL-Code
Der eigentliche Code, der dann bei der Ausführung in Native Code übersetzt wird.
Metadaten
Tabellen und Strukturen. Die Runtime verwendet diesen Teil, um Informationen über alle Typen innerhalb der Datei genau zu beschreiben. Dieser Teil beinhaltet auch Attribute und benutzerdefinierte Attribute. Außerdem werden hier auch die Zugriffserlaubnisse gespeichert.
Tabelle 1.6: Aufbau einer PE-Datei unter Microsoft .NET
Wie schon weiter vorne erwähnt, werden die Metadaten in den Metadatentabellen gespeichert. Es gibt z.B. eine Tabelle, in der alle Klassen gespeichert werden. Für einen solchen Eintrag gibt es dann wiederum eine Referenz zu einer Tabelle, wo dann die Methoden für diese Klasse zu finden sind. Die Metadatentabellen beschreiben Dinge, die im Modul implementiert werden. In der folgenden Tabelle werden die wichtigsten Metadatentabellen näher beschrieben: Metadatentabelle
Beschreibung
ModuleDef
Enthält immer einen Eintrag, der das Modul identifiziert. Der Eintrag umfasst den Dateinamen des Moduls mit Namensendung (ohne Pfad) sowie eine Modulversions-ID (in Form einer GUID, die der Compiler generiert hat). Daher bleibt der ursprüngliche Dateiname bekannt, auch wenn die Datei umbenannt wird.
TypeDef
Enthält für jeden Typ, der im Modul definiert wird, einen Eintrag. Jeder Eintrag nennt den Namen des Typs, den Basistyp und die Flags (public, private usw.). Außerdem verweist er auf die dazugehörigen Methoden, die in der MethodDef-Tabelle geführt werden, und auf die Felder (Datenelemente) des Typs, die in der Tabelle FieldDef verzeichnet werden.
Tabelle 1.7: Metadatentabellen
Sandini Bib 54
1
Einführung in Microsoft .NET
Metadatentabelle
Beschreibung
MethodDef
Enthält für jede im Modul definierte Methode einen Eintrag. Jeder Eintrag verzeichnet den Namen der Methode, Flags (private, public, virtual, abstract, static, final usw.), die Signatur und den Offset für das Modul, in dem der entsprechende CIL-Code zu finden ist. Außerdem kann jeder Eintrag auf die ParamDef-Tabelle verweisen, in der weitere Informationen über die Parameter der Methode zu finden sind.
FieldDef
Enthält für jedes im Modul definierte Feld einen Eintrag. Solch ein Eintrag umfasst einen Namen, Flags (private, public usw.) und außerdem eine Typangabe.
ParamDef
Enthält für jeden im Modul definierten Parameter einen Eintrag. Zu solch einem Eintrag gehören ein Name und Flags (in, out, retval usw.).
PropertyDef
Enthält einen Eintrag für jede Eigenschft, das im Modul definiert wird. Der Eintrag enthält einen Namen, Flags, eine Typangabe und das zugeordnete Feld (das übrigens auch null sein kann).
EventDef
Enthält für jedes im Modul definierte Ereignis einen Eintrag mit Namen und Flags
Tabelle 1.7: Metadatentabellen (Forts.)
Es gibt auch noch Metadaten-Referenztabellen, in denen die Dinge verzeichnet sind, die zwar in diesem Modul benutzt werden, aber in anderen Assemblies liegen. Die folgende Tabelle zeigt einige der gebräuchlicheren Referenztabellen. Metadaten-Referenztabelle
Beschreibung
AssemblyRef
Enthält für jede Baugruppe, die vom Modul benutzt wird, einen Eintrag. Ein Eintrag enthält die Informationen, die zur Einbindung der Baugruppe erforderlich sind: den Namen der Baugruppe (ohne Pfad und Endung), die Versionsnummer, den Kulturkreis und ein öffentliches Schlüsselzeichen (normalerweise ein kurzer Hash-Wert, der den öffentlichen Schlüssel der betreffenden Assembly identifiziert). Außerdem enthält ein Eintrag noch einige Flags und einen Hash-Wert.
ModuleRef
Enthält einen Eintrag für jedes PE-Modul, das Typen implementiert, die von diesem Modul benutzt werden. Ein Eintrag besteht aus dem Dateinamen des Moduls samt Namensendung (ohne Pfad). Diese Tabelle dient zur Einbindung von Typen, die in anderen Modulen aus der aufrufenden Assembly implementiert werden.
Tabelle 1.8: Metadaten-Referenztabellen
Sandini Bib Metadaten
55
Metadaten-Referenztabelle
Beschreibung
TypeRef
Enthält für jeden Typ, der vom Modul verwendet wird, einen Eintrag. Der Eintrag umfasst den Namen des Typs und eine Referenz auf den Ort, an dem der Typ zu finden ist. Wird der Typ in einer anderen Assembly implementiert. so verweist der Eintrag auf einen AssemblyRef-Eintrag. Wird der Typ dagegen in einem Modul aus der aufrufenden Assembly implementiert, so verweist der Eintrag auf einen ModuleRef-Eintrag.
MemberRef
Enthält für jedes Feld, jede Methode, jede Eigenschaft und jede Ereignismethode, die im Modul verwendet werden, einen Eintrag. Ein Eintrag umfasst den Namen und die Signatur und verweist auf den TypeRef-Eintrag des Typs, in dem das betreffende Feld oder die Methode implementiert wird.
Tabelle 1.8: Metadaten-Referenztabellen (Forts.)
Wie bereits angesprochen, enthält eine Assembly immer ein Manifest, in dem verschiedene Dinge über die betreffende Assembly gespeichert werden. Diese Informationen werden auch wieder in Tabellen gespeichert. Die Tabellen nennt man Manifest-Tabellen. Die folgende Tabelle beschreibt sie näher: Manifest-Tabelle
Beschreibung
AssemblyDef
Enthält einen einzelnen Eintrag, sofern dieses Modul eine Assembly beschreibt. Der Eintrag umfasst den Namen der Assembly (ohne Pfad und Endung), den Kulturkreis, die Version (major, minor, build und revision), Flags, den Hash-Algorithmus und außerdem den öffentlichen Schlüssel des Herausgebers
FileDef
Enthält Einträge für jede PE- und Quelltextdatei, die zur Assembly gehört. Der Eintrag umfasst den Namen der Datei mit Namensendung (ohne Pfad), einen Hash-Wert und Flags. Falls die Assembly nur aus ihrer eigenen Datei besteht, hat diese Tabelle keine Einträge.
ManifestResourceDef
Enhält einen Eintrag für jede Ressource, die zur Assembly gehört. Der Eintrag umfasst den Namen der Ressource, Flags (public, private) und einen Index für die FileDef-Tabelle. In der FileDef-Tabelle wird die Datei beschrieben, in welcher die Ressourcen-Datei oder der RessourcenStream liegt. Sofern es sich bei der Ressource nicht um eine eigenständige Datei handelt (wie JPEG oder GIF), liegt die Ressource als Stream in einer PE-Datei vor. Bei einer eingebetteten Ressource nennt der Eintrag auch den Offset, bei dem der Ressourcen-Datenstrom in der PE-Datei beginnt.
Tabelle 1.9: Manifest-Tabellen
Sandini Bib 56
1
Einführung in Microsoft .NET
Manifest-Tabelle
Beschreibung
ExportedTypesDef
Enthält für jeden öffentlichen Typ, der von den PE-Modulen der Baugruppe exportiert wird, jeweils einen Eintrag. Darin wird der Name des Typs angegeben, ein Index für die FileDef-Tabelle (er zeigt die Datei, in welcher der Typ implementiert wird) und ein Index für die TypeDef-Tabelle. Zur Platzersparnis werden die Typen, die von der Manifestdatei exportiert werden, nicht in dieser Tabelle wiederholt, da die Typinformationen bereits in der TypeDef-Tabelle verfügbar sind.
AssemblyProcessorDef
Enthält für jede CPU einen Eintrag, die von den Modulen der Assembly unterstützt wird. Solch ein Eintrag besteht aus einer Prozessorkennung wie PROCESSOR_INTEL_PENTIUM, PROCESSOR_INTEL_IA64 usw. (wie in WinNT.h definiert). Normalerweise ist diese Tabelle leer. Ihre Angaben werden von der Laufzeitschicht ignoriert. Allerdings können Daten interessant werden, weil manche Module Binärcode enthalten können.
AssembyOSDef
Enthält einen Eintrag, der das Betriebssystem nennt, für das die Module aus der Assembly vorgesehen sind. In jedem Eintrag gibt es eine Plattform-ID (wie VER_PLATFORM_WIN32, VER_PLATFORM_WIN32_NT oder VER_PLATFORM_WIN32_CE) und die Haupt- und Nebennummern der Betriebssystemversion. Normalerweise ist diese Tabelle leer. Ihre Angaben werden von der Laufzeitschicht ignoriert. Allerdings könnten ihre Informationen interessant werden, wenn manche Typen oder Methoden nur auf bestimmten Betriebssystemen verfügbar sind.
Tabelle 1.9: Manifest-Tabellen (Forts.)
Durch die Existenz eines Manifests dieser Art beschreiben sich die Assemblies praktisch von selbst. Durch das Manifest weiß die Datei, welche anderen Dateien noch zur Assembly gehören. Diese Dateien wiederum wissen aber nicht, dass sie zu einer Assembly gehören.
ILDASM Jetzt stellt sich vielleicht die Frage, ob ein normaler Anwender jemals diese Metadaten zu Gesicht bekommt. Microsoft stellt einen Disassembler zur Verfügung, der dies möglich macht. Dieses Tool heißt IlDasm. Wenn man das Programm startet, kann man über das Menü File den Befehl Open auswählen, durch den man eine .NET-Assembly öffnen kann. Anschließend wird die Assembly geladen, und die Informationen der Assembly werden hierarchisch dargestellt. Nachfolgend ist eine Ausgabe von IlDasm zu sehen.
Sandini Bib Metadaten
57
Abbildung 1.1: Beispielausgabe von IlDasm.exe
Das Bild zeigt die Ausgabe von IlDasm.exe für die Anwendung Wordcount.exe. Dabei sind folgende Typen zu sehen: 왘 Manifest 왘 Application 왘 ArgParser 왘 WordCountArgParser 왘 WordCounter Wenn man auf einen Typ doppelklickt, werden nähere Informationen über diesen angezeigt. Wenn man z.B. auf eine Klasse klickt, werden die Funktionen, Eigenschaften, Ereignisse usw. angezeigt. Außerdem wird vor jedem Eintrag ein Symbol angezeigt, das dessen Typ näher beschreibt (siehe Abbildung 1.2). Über das Menü View kann man ganz genau festlegen, welche Informationen der Disassembler über die Assembly anzeigen soll. Wenn man IlDasm.exe mit dem Schalter /adv startet, werden weitere Menüeinträge sichtbar, mit denen man sich die geöffnete Assembly noch genauer ansehen kann. Ein interessanter Menüpunkt ist z.B. View/Statistics. Mit diesem Befehl kann man sich eine Statistik über die Assembly ansehen. Wenn man sich die Assembly mscorlib.dll ansieht, die einen gewissen Teil der Basisfunktionalität des .NET Frameworks implementiert, sieht man folgende Ausgabe: File size : 1945600 PE header size : 4096 (496 used) PE additional info : 1723 Num.of PE sections : 3 CLR header size : 72 CLR meta-data size : 1006132
( 0.21%) ( 0.09%) ( 0.00%) (51.71%)
Sandini Bib 58
1
Einführung in Microsoft .NET
Abbildung 1.2: Anzeige der Typen einer Assembly CLR additional info : 186654 CLR method headers : 66337 Managed code : 629196 Data : 8192 Unaccounted : 43198
Sehr interessant ist der erste Teil der Ausgabe, der nähere Informationen über den Aufbau der Assembly liefert. In der ersten Zeile sieht man die Gesamtgröße der aktuellen Datei und anschließend die Größe des PE-Headers. Die Größe des CLR-Headers und die Größe der Metadaten sind ebenfalls im ersten Abschnitt vermerkt. Zu erwähnen ist noch, dass auch angegeben wird, wie viel Prozent der entsprechende Teil in Bezug auf die komplette Datei ausmacht. Jedenfalls ist dieser Disassembler ein Tool, mit dem sich jeder ernsthafte .NET Programmierer näher beschäftigen sollte, wenn er mehr über das .NET Framework und dessen interne Vorgänge wissen möchte.
Sandini Bib 60
1
Einführung in Microsoft .NET
1.8 Speicherverwaltung Haben Sie schon einmal einen C++-Programmierer gefragt, mit welchen Problemen er am meisten bei der Programmierung zu kämpfen hat? Als Antwort werden Sie sicher die beiden Punkte Zeigerverwaltung und die Speicherverwaltung zu hören bekommen. Unter dem .NET Framework braucht man sich um diese beiden Punkte bei der Programmierung nicht mehr zu kümmern – wenn man will. Wenn man in Visual Basic .Net und in C# programmiert, gibt es keine Zeiger mehr. Wenn man in Managed C++ programmiert, kann man Zeiger verwenden, wenn man will; aber man braucht nicht mehr. Und die Speicherverwaltung hat sich im .NET Framework auch komplett geändert. Am besten ist es, wenn Sie alles über die Speicherverwaltung vergessen, was Sie bis jetzt gehört haben. Denn unter .NET sieht alles ganz anders aus. Unter dem .NET Framework gibt es den so genannten Garbage Collector (GC), der sich um den kompletten Speicherhaushalt einer Anwendung kümmert. In der Praxis heißt das, dass der Programmierer nur Objekte anlegen und nicht mehr freigeben muss, denn dies übernimmt der Garbage Collector für den Programmierer. Den alten Programmierhasen ist der Garbage Collector vielleicht noch von MS QBasic ein Begriff, wo er auch im Einsatz ist. Aber diese beiden Garbage Collector kann man nicht miteinander vergleichen, da hier sehr viele Entwicklungsjahre dazwischen liegen. Jedes Mal wenn man den new-Operator verwendet, alloziert die Runtime Speicher für das gewünschte Objekt auf dem Managed Heap. Solange Speicher auf dem managed Heap vorhanden ist, wird von der Runtime Speicher für neue Objekte alloziert. Der Speicher ist jedoch nicht unendlich. Schlussendlich muss dann der Garbage Collector eine Sammlung durchführen, wodurch wieder Speicher für neue Objekte freigegeben wird. Wann aber diese Sammlung durchgeführt wird, unterliegt ganz dem Garbage Collector. Jedoch kann auch der Programmierer diese Sammlung manuell im Programmcode auslösen. Wenn der Garbage Collector eine Sammlung auf dem Managed Heap ausführt, prüft er, welche Objekte nicht mehr von einer Anwendung verwendet werden, und zerstört dann diese, um Speicherplatz wieder freizugegeben. Der Garbage Collector ist ein Teilsystem des .NET Frameworks, das sich sehr auf die Leistung der Anwendungen auswirkt. Daher wird er von Microsoft in Zukunft immer mehr und mehr hinsichtlich der Leistung verbessert werden. Eines der Merkmale des Garbage Collectors existiert überhaupt nur aus diesem Grund, nämlich der Verbesserung der Leistung: Generationen. Und so gelten in einem generationsbezogenen Garbage Collector folgende Annahmen: 왘 Je jünger ein Objekt ist, desto kürzer wird auch seine Lebensdauer sein. 왘 Je älter ein Objekt bereits ist, desto länger wird es noch leben.
Sandini Bib Zusammenfassung
61
왘 Neue Objekte neigen dazu, untereinander starke Beziehungen aufzubauen. Meistens erfolgt der Zugriff auf diese Objekte daher ungefähr zur selben Zeit. 왘 Die Verdichtung eines Teils der Speicherhalde erfolgt schneller als die Verdichtung der gesamten Halde. Unmittelbar nach ihrer Initialisierung enthält der Managed Heap noch keine Objekte. Die Objekte, die auf dem Managed Heap landen, gehören zur Generation 0. Mit der Generation 0 sind junge Objekte gemeint, die noch nie vom Garbage Collector untersucht wurden. Nun werden immer mehr und mehr Objekte auf dem Managed Heap angelegt. Der managed Heap bevölkert sich immer weiter und irgendwann wird eine Speicherbereinigung erforderlich. Während der Garbage Collector den Managed Heap untersucht, stellt er einen Graphen mit den Müllobjekten auf und einen mit den anderen Objekten. Alle Objekte, die nun die Speicherbereinigung überstanden haben, werden in den unteren Teil des Managed Heap verschoben. Diese Objekte stellen nun die Generation 1 des Managed Heap dar. Die neuen Objekte, die anschließend auf dem Managed Heap landen, gehören alle zur Generation 0. Ist der verfügbare Platz durch die neue Generation 0 belegt, wird wieder eine Speichersammlung durchgeführt. Diesmal werden die überlebenden Objekte aus der Generation 1 verdichtet. Sie stellen anschließend die Generation 2 dar. Auch die überlebenden Objekte aus der Generation 0 werden verdichtet und stellen anschließend die Generation 1 dar. Generation 0 enthält nun keine Objekte mehr. Alle Objekte, die anschließend neu auf dem Managed Heap angelegt werden, gehören zur Generation 0. Derzeit ist die Generation 2 die älteste Generation, die vom Garbage Collector erstellt wird. Sofern weitere Speicherbereinigungen notwendig werden, bleiben die überlebenden Objekte in der Generation 2.
1.9 Zusammenfassung Dieses Kapitel hat gezeigt, was eigentlich unter den Begriffen Microsoft .NET und .NET Framework zu verstehen ist. Das .NET Framework wurde eingeführt, um diverse Probleme bei der Softwareentwicklung zu vermeiden oder zu verbessern: 왘 Wahl der Programmiersprache 왘 Wahl des Betriebssystems 왘 Verwaltung von COM-Komponenten 왘 Auslieferung der Software
Sandini Bib 62
1
Einführung in Microsoft .NET
Das .NET Framework ist eine Kombination aus den folgenden Technologien: 왘 Microsoft .NET Framework 왘 WebServices 왘 Microsoft .NET Enterprise Server Im .NET Framework sind verschiedenen Technologien, wie z.B. ASP.NET, Web Forms, WebServices, Windows Forms oder ADO.NET, vereint. Das .NET Framework wird von Microsoft mit den folgenden vier Programmiersprachen ausgeliefert: 왘 Visual Basic .Net 왘 C# 왘 Managed C++ 왘 JScript.NET Außerdem wurden die grundlegenden Programmierkonstrukte von Visual Basic .Net und C# näher beschrieben, da dies die Programmiersprachen sind, die bei der ASP.NET-Programmierung vorwiegend eingesetzt werden. Das Herzstück des .NET Frameworks ist die Common Language Runtime. Sie ist eine Zwischenschicht zwischen dem Betriebssystem und den .NET-Programmiersprachen. Wenn die Common Language Runtime auf eine andere Zielplattform portiert wird, können .NET-Programme ohne Quelltextänderung und ohne Neukompilierung plattformunabhängig ausgeführt werden. Außerdem wird beim Kompilieren kein Native Code erzeugt, sondern Code in einer Zwischensprache, die sich Common Intermediate Language nennt. Diese Common Intermediate Language wird dann auf der Zielplattform durch so genannten Just-InTime-Compiler in den gewünschten Native Code umgewandelt. Durch die Common Language Runtime ist beim Programmieren auch eine Unabhängigkeit der Programmiersprache gegeben. Das heißt, der Programmierer kann frei entscheiden, welche Programmiersprache er für seine Applikation verwenden will. Denn jede Programmiersprache hat auf die gleichen Features des .NET-Frameworks Zugriff. Außerdem ist es z.B. möglich, eine Klasse in VB.NET zu schreiben und davon eine Klasse in C# abzuleiten. Dadurch sind Programmierteams möglich, die in verschiedenen Sprachen programmieren. Ein weiteres neues Feature des .NET Frameworks sind die Assemblies. In den Assemblies können Code und Ressourcen zusammengefasst werden. Es gibt Single-File- und Multi-File-Assemblies. Außerdem unterscheidet man nach Private und Shared Assemblies.
Sandini Bib Zusammenfassung
63
Da Komponenten unter.NET nicht mehr beim System registriert werden müssen, muss es dafür eine andere Möglichkeit geben. Daher wurden von Microsoft die Metadaten eingeführt. Die Metadaten beschreiben alle Typen, die in einer Assembly verwendet werden. Außerdem werden auch die Abhängigkeiten zwischen Assemblies mit den Metadaten beschrieben. Durch die Metadaten wird es möglich, Assemblies durch einfaches Kopieren zu installieren. Ein weiterer großer Sprung wurde in Richtung Speicherverwaltung gemacht. Unter .NET gibt es den Garbage Collector, der für den kompletten Speicherhaushalt von .NET Anwendungen zuständig ist. Objekte, die vom Programmierer erzeugt wurden, brauchen nicht mehr freigegeben zu werden. Dies erledigt jetzt der Garbage Collector.
Sandini Bib
Sandini Bib
2 Einführung in Visual Studio .NET Mit der Einführung von Microsoft .NET gibt es von Microsoft auch eine neue Version des Visual Studios. Diese Version wird nicht wie angenommen Visual Studio 7.0 heißen, sondern Visual Studio .NET. Aus der Produktbezeichnung geht schon hervor, dass diese Version des Visual Studios auf die Entwicklung von .NET Anwendungen ausgerichtet ist. Visual Studio .NET umfasst folgende neue Features: 왘 Eine Startseite, die den Einstieg in die IDE (Integrated Development Environment) vereinfacht 왘 Sehr viele Projektassistenten, die das Grundgerüst der gewünschten Anwendung erzeugen 왘 Alle Programmiersprachen (C#, Visual Basic .Net, Managed C++, JScript.NET) sind innerhalb einer einzigen IDE verfügbar. 왘 Designer, die von allen Programmiersprachen auf dieselbe Weise zugänglich sind 왘 Integrierte, dynamische Hilfe 왘 Leistungsfähiger Debugger, der sprachübergreifend debuggen kann 왘 Makroeditor, mit dem wiederkehrende Aufgaben vereinfacht werden können 왘 Umfangreiche Integration des SQL Servers
2.1 Visual Studio .NET-Editionen Insgesamt gibt es von Visual Studio .NET die folgenden drei Editionen, die ich nachfolgend näher beschreiben möchte: 왘 Visual Studio .NET Enterprise Architect Edition 왘 Visual Studio .NET Enterprise Developer Edition 왘 Visual Studio .NET Professional Edition 왘 Visual Studio .NET Standard Edition
Sandini Bib 66
2
Einführung in Visual Studio .NET
Visual Studio .NET Enterprise Architect Edition Diese Version von Visual Studio .NET ist die ausgereifteste Version der Entwicklungsumgebung. Hier findet der Programmierer alle Tools, die man braucht, um leistungsfähige, skalierbare Unternehmensanwendungen zu planen, zu designen und zu programmieren. Ein Zusatz-AddOn der Architect Edition ist Microsoft Visio, mit dem man z.B. UMLDiagramme der Software erstellen, und diese anschließend in C#-Code umwandeln kann. Weiter hinten in diesem Kapitel möchte ich anhand eines einfachen Beispiels zeigen, wie man ein UML-Modell einer Klassenbibliothek entwickeln, und anschließend daraus den entsprechenden C#-Quellcode generieren kann. Microsoft Visio bietet aber noch viele andere Vorteile, auf die ich an dieser Stelle nicht näher eingehen möchte.
Visual Studio .NET Enterprise Developer Edition Die Enterprise Developer Edition unterscheidet sich von der Architect Edition insoweit, dass in dieser Edition nicht das Produkt Microsoft Visio enthalten ist. Sonst bietet einem die Enterprise Developer Edition die gleichen Möglichkeiten wie die Visual Studio Enterprise Edition 6.0.
Visual Studio .NET Professional Edition Die Visual Studio .NET Professional Edition richtet sich an alle Programmierer, die "normale" Anwendungen auf der Basis von Microsoft .NET entwickeln wollen. Ein großer Unterschied zur Enterprise Developer Edition ist, dass im Lieferumfang dieser Edition nicht die .NET Enterprise Server standardmäßig enthalten sind. In der folgenden Tabelle möchte ich die Unterschiede und die Features der verschiedenen Editionen von Visual Studio .NET zeigen, damit man feststellen kann, welche Edition für einen die beste ist. Feature
Enterprise Architect
Developer Edition
Professional Edition
Ja XML-WebServices: Mithilfe von XML-WebServices kann man jede beliebige Komponente über das Internet anbieten.
Ja
Ja
Windows-Forms: Die Windows-Forms ersetzen die die Formularprogrammierung in Visual Basic und C++. Außerdem gibt es hier jetzt die Möglichkeit, dass man Formulare voneinander ableiten kann.
Ja
Ja
Ja
Tabelle 2.1: Unterschiede der verschiedenen Editionen von Visual Studio .NET
Sandini Bib Visual Studio .NET-Editionen
67
Feature
Enterprise Architect
Developer Edition
Professional Edition
WebForms: Mit den WebForms kann man InternetAnwendungen ebenso wie Visual Basic-Anwendungen entwickeln. Klickt man z.B. auf einen Button, gelangt man direkt in die Ereignisbehandlungsfunktion des entsprechenden Buttons. Der Code, der jetzt hier eingegeben wird, wird dann serverseitig durch ASP.NET kompiliert und ausgeführt. Der Code kann in jeder beliebigen Programmiersprache wie C# oder Visual Basic programmiert werden.
Ja
Ja
Ja
Mobile Web-Forms: Mit den Mobile Web-Forms ist es möglich, Internet-Anwendungen für mobile Geräte, wie z.B. PDAs oder Handies, zu entwickeln. Dabei wird wieder in der gleichen Entwicklungsumgebung wie bei den WebForms oder Windows-Forms entwickelt.
Ja
Ja
Ja
Pocket-PC- und Windows-CE.NET-basierte Anwendungen: Mit dem .NET Compact Framework wird es sogar möglich, dass man .NET Anwendungen für Pocket-PC- und Windows CE.NET-basierte Anwendungen entwickeln kann. Der Clou dabei ist wieder, dass man die gewohnte Entwicklungsumgebung nicht zu verlassen braucht, da alle notwendigen Tools in Visual Studio .NET integriert sind.
Ja
Ja
Ja
Ja Das .NET Framework und die Common Language Runtime: Mithilfe des .NET Frameworks und der Common Language Runtime wird es möglich, dass man ein Projekt z.B. in mehreren Programmiersprachen entwickeln kann. Die integrierten Compiler erzeugen auch keinen x86-Code mehr, sondern einen Zwischencode, der sich CIL (Common Intermediate Language) nennt. Dieser Code wird dann bei der Programmausführung just in time in Native Code übersetzt. Dadurch ergeben sich enorme Vorteile, die ich in den nächsten Kapiteln näher erörtern werde.
Ja
Ja
Visual Basic .Net Upgrade Wizard: Mit dem Upgrade Wizard von Visual Basic .Net wird es möglich, dass man vorhandene Visual Basic 6.0-Anwendungen fast ohne Quellcodeänderungen auf die neue Plattform übernehmen kann.
Ja
Ja
Ja
Visual Basic .Net: Visual Basic .Net unterstützt jetzt alle objektorientierten Technologien, wie z.B. Vererbung, strukturierte Ausnahmebehandlung und Threading, um nur einige Punkte nennen zu wollen.
Ja
Ja
Ja
Tabelle 2.1: Unterschiede der verschiedenen Editionen von Visual Studio .NET (Forts.)
Sandini Bib 68
2
Einführung in Visual Studio .NET
Feature
Enterprise Architect
Developer Edition
Professional Edition
Visual C#.NET: Visual C#.NET ist eine neue Programmiersprache, die die Vorteile von C, C++ und Visual Basic in einer mächtigen Sprache vereint. Dabei stehen dem Programmierer alle RAD-Tools (Rapid Application Development) von Visual Basic .Net zur Verfügung.
Ja
Ja
Ja
Visual C++.NET: Visual C++.NET ist in Visual Studio .NET die einzige Programmiersprache, in der es noch möglich ist, nativen x86-Code zu entwickeln. Jedoch kann man mit den Managed-Extensions Anwendungen auf der Basis von .NET entwickeln, die alle Vorteile der Common Language Runtime nutzen können. Natürlich sind noch die beliebten Bibliotheken wie die MFC (Microsoft Foundation Classes) oder die ATL (Active Template Library) vorhanden.
Ja
Ja
Ja
Visual J#.NET: Microsoft Visual J#.NET ist eine Programmiersprache, die sich an der Syntax von Java anlehnt. Mithilfe dieser Sprache ist es möglich, dass man vorhandene Java-Anwendungen auf Microsoft .NET portieren kann. Ob sich diese Programmiersprache jedoch in der Zukunft durchsetzen wird, ist ungewiss.
Ja
Ja
Ja
Support für zusätzliche Programmiersprachen: Da alle Programmiersprachen von Microsoft .NET auf der CLR (Common Language Runtime) basieren, können auch Programmiersprachen von Drittanbietern problemlos in die IDE integriert werden. Microsoft hat schon Beispiele gezeigt, in denen Cobol- oder Modulo-Programme unter dem .NET Framework kompiliert und ausgeführt wurden.
Ja
Ja
Ja
Integrierte Entwicklungsumgebung (IDE): Das Schöne an Visual Studio .NET ist, dass alle Programmiersprachen und Tools in einer Entwicklungsumgebung vereint sind. Man braucht daher nicht mehr das Programm zu wechseln, wenn man z.B. eine gespeicherte Prozedur im SQL Server entwickeln möchte.
Ja
Ja
Ja
RAD-Entwicklung für den Server: Wenn man ServerAnwendungen entwickeln möchte, kann man diese auch direkt mithilfe von RAD-Tools innerhalb der IDE entwickeln und testen. Hierzu zählen Tools wie z.B. der Server-Explorer, mit dem man alle Objekte eines Server verwalten und programmieren kann.
Ja
Ja
Ja
Tabelle 2.1: Unterschiede der verschiedenen Editionen von Visual Studio .NET (Forts.)
Sandini Bib Visual Studio .NET-Editionen
69
Feature
Enterprise Architect
Developer Edition
Professional Edition
Visual Studio .NET Debugger: Der Debugger von Visual Studio .NET ist in vielen Bereichen um einiges leistungsfähiger geworden. Jetzt ist es z.B. möglich, dass man von einer Internet-Anwendung direkt in eine Komponente debuggen kann. Wird dann vielleicht noch innerhalb der Komponente eine gespeicherte Prozedur aufgerufen, ist es auch möglich, dass man diese noch debuggt.
Ja
Ja
Ja
Dynamische Hilfe: Ein leistungsfähiges Feature von Visual Ja Studio .NET ist die dynamische Hilfe. Sie zeigt die entsprechenden Hilfethemen an, die interessant für die Aufgabe sind, die der Programmierer gerade durchführt. Dadurch erspart man sich längere Suchzeiten in der MSDN-Library und kommt schneller zum gewünschten Ziel.
Ja
Ja
Ja
Ja
Ja
HTML Designer: Viele Entwickler haben bei der Program- Ja mierung von Web-Anwendungen einen Designer wie in Visual Basic vermisst. Dieser Einschränkung haben die Programmierer von Visual Studio .NET Rechnung getragen. Jetzt ist es komplett egal, ob man eine Windowsoder eine Web-Anwendung entwickelt – überall ist ein Designer verfügbar, mit dem man die notwendigen Steuerelemente direkt auf die Windows-Form oder WebForm ziehen kann. Über ein Eigenschaftenfenster kann man anschließend noch die verschiedenen Eigenschaften des Steuerelements festlegen.
Ja
Ja
Microsoft SQL Server: Der SQL Server von Microsoft kann als Datenbankserver in kritischen Unternehmensanwendungen verwendet werden, die ununterbrochen laufen und online sein müssen. Außerdem ist es möglich, skalierbare E-Commerce-und Datawarehouse-Anwendungen auf der Basis des SQL Servers zu entwickeln.
Ja
Nein
Aufgabenliste: Ein weiteres nettes Feature von Visual Studio .NET ist die integrierte Aufgabenliste. Hier werden alle Aufgaben angezeigt, die noch im Quellcode zu erledigen sind. Im Code kann man eine Aufgabe hinzufügen, indem man einen Kommentar schreibt, der am Anfang das Wort TODO enthält. Dadurch wird der Komentar direkt in die Aufgabenliste übernommen und dem Programmierer angezeigt.
Ja
Tabelle 2.1: Unterschiede der verschiedenen Editionen von Visual Studio .NET (Forts.)
Sandini Bib 70
2
Einführung in Visual Studio .NET
Feature
Enterprise Architect
Developer Edition
Professional Edition
Microsoft SQL Server Desktop Engine (MSDE): Mithilfe der MSDE ist es möglich, dass man Datenbankanwendungen entwickelt, die eine abgespekte Version des SQL Servers, nämlich die MSDE, verwenden. Solche Anwendungen können in Zukunft ohne Probleme auf den SQL Server skaliert werden.
Ja
Ja
Ja
Visual Database Tools: Mit den Visual Database Tools ist es möglich, Datenbankanwendungen innerhalb der IDE von Visual Studio .NET zu entwickeln. Dabei kann man Tabellen, Sichten und gespeicherte Prozeduren wie z.B. in Access mithilfe von visuellen Hilfsmitteln entwickeln und testen.
Ja
Ja
Ja
Microsoft Visio-basierte Datenbankmodellierung: Mit Microsoft Visio ist es möglich, dass man eine Datenbank mithilfe des integrierten Designers modelliert und anschließend daraus die Datenbank erzeugt. Werden Änderungen an der Datenbank oder am Visio-Modell durchgeführt, werden die Änderungen automatisch in der jeweiligen anderen Quelle übernommen.
Ja
Nein
Nein
XML-Designer: Mithilfe des XML-Designers von Visual Studio .NET ist es möglich, dass man XML-Dokumente, XSD-Dateien oder typisierte DataSets ohne eine einzige Zeile Code entwickeln kann.
Ja
Ja
Ja
Ja Microsoft Visual SourceSafe 6.0c: Visual Source bietet ein skalierbares Quellcodeverwaltungssystem, mit dem es möglich ist, teambasierte Entwicklungen in einem Unternehmen zu skalieren und zu optimieren.
Ja
Nein
Ja Application Center Test: Mithilfe des Application Center Tests kann man WebServices und Anwendungen einem Test unterziehen, in dem festgestellt wird, wie sie reagieren, wenn mehrere tausend Anfragen pro Minute durchgeführt werden. Dadurch kann festgestellt werden, wie skalierbar eine entwickelte Anwendung ist.
Ja
Nein
Visio-basierte UML-Modellierung: Mithilfe von Visio ist es möglich, dass man UML-Modelle der zu entwickelnten Software erstellt. Handelt es sich dabei um ein Klassensystem, ist es weiters möglich, aus dem UML-Modell direkt den entsprechenden Code zu generieren.
Ja
Nein
Nein
Enterprise Templates: Mit den Enterprise Templates wird die Entwicklung von Unternehmensanwendungen um vieles vereinfacht.
Ja
Nein
Nein
Tabelle 2.1: Unterschiede der verschiedenen Editionen von Visual Studio .NET (Forts.)
Sandini Bib Visual Studio .NET-Editionen
71
Feature
Enterprise Architect
Developer Edition
Professional Edition
Microsoft .NET-basierte Referenzanwendungen: Einige der Beispielprogramme zeigen, wie man E-Commerce- und Unternehmensanwendungen auf der Basis von Microsoft .NET entwickelt. Dabei ist in diesen Anwendungen nicht nur der Code enthalten, sondern auch zusätzliche Dokumente, wie z.B. Visio-Modelle.
Ja
Ja
Nein
Visual Studio Analyzer: Mithilfe des Visual Studio Analyzers kann man Engpässe in einer Anwendung ermitteln, diese grafisch darstellen lassen und anschließend beheben.
Ja
Ja
Nein
Windows 2000 Advanced Server, SQL Server 2000, Commerce Server, Host Integration Server, Exchange Server
Ja
Ja
Nein
Microsoft BizTalk-Server
Ja
Nein
Nein
Tabelle 2.1: Unterschiede der verschiedenen Editionen von Visual Studio .NET (Forts.)
An dieser Tabelle sieht man schon, dass das Visual Studio .NET viel mehr als eine einfache, nette Entwicklungsumgebung ist. Wenn man will, ist es fast nicht möglich, dass man in einem Projekt alle Features von Visual Studio .NET ausreizen kann. Wie man gesehen hat, kann man hier seiner Phantasie freien Lauf lassen, ohne dass man auf Probleme stoßen wird.
Abbildung 2.1: Visual Studio .NET
Sandini Bib 72
2
Einführung in Visual Studio .NET
Nachdem das Visual Studio .NET installiert wurde, befindet es sich im Startmenü unter dem Menüeintrag Microsoft Visual Studio .NET. Hier kann man die Entwicklungsumgebung durch einen Klick auf das Symbol Microsoft Visual Studio .NET starten. Wenn die Anwendung schließlich geladen wurde, präsentiert sie sich uns wie in Abbildung 2.1. Die Seite, die standardmäßig beim Starten von Visual Studio .NET angezeigt wird, ist die Startseite, die ich auf den folgenden Seiten näher beschreiben möchte.
2.2 Startseite Beim Starten von Visual Studio .NET wird als Erstes die Startseite angezeigt. Diese Startseite wurde von Microsoft eingeführt, damit der Einstieg in die IDE erleichtert wird. Außerdem hat man über die Startseite Zugriff auf die neuesten Informationen des Microsoft Developer Networks (MSDN), das unter der Adresse http://msdn.microsoft.com zu finden ist. Auf der linken Seite der Startseite gibt es folgende Menüpunkte: 왘 Get Started 왘 What’s New 왘 Online Community 왘 Headlines 왘 Search Online 왘 Downloads 왘 Xml WebServices 왘 Web Hosting 왘 My Profile
Get Started Über diesen Menüpunkt hat man Zugriff auf die Projekte, die zuletzt bearbeitet wurden. Durch einen Klick auf das gewünschte Projekt wird dieses in die IDE geladen. Außerdem kann man weitere Projekte über den Button Open Project öffnen. Ein neues Projekt wird über die Schaltfläche New Project angelegt. Der Menüpunkt Get Started wird beim Start des Visual Studio .NET standardmäßig angezeigt.
Sandini Bib Startseite
73
What’s New Auf dieser Seite werden die neuesten Informationen rund um das Visual Studio .NET angezeigt. Außerdem hat man Zugriff auf Partner-Ressourcen von Microsoft und auf neueste Produktinformationen rund um Microsoft .NET. Dabei werden die neuesten Informationen direkt aus dem Internet ermittelt und dem Programmierer angezeigt. Außerdem gibt es hier die Möglichkeit, dass man überprüfen kann, ob aktuelle Service Packs für das Visual Studio .NET vorhanden sind. Die folgende Abbildung zeigt diese Seite:
Abbildung 2.2: Die neuesten Entwicklerinformationen im Visual Studio .NET
Online Community Hier werden von Microsoft alle Newsgroups über Microsoft .NET veröffentlicht. Durch einen Klick auf die entsprechende Newsgroup wird diese im Newsreader eingerichtet. Über diesen Menüpunkt hat man auch die Möglichkeit, dass man direkt auf Codebeispiele aus dem Internet zugreifen kann (siehe Abbildung 2.3).
Headlines Wenn man über die neuesten Features, News, technischen Artikel und über die Knowledge Base von Microsoft Bescheid wissen will, ist dieser Menüpunkt eine Pflicht. Denn hier findet man die brandaktuellen Informationen des Microsoft Developer Networks (siehe Abbildung 2.4).
Sandini Bib 74
Abbildung 2.3: Online Communities im Visual Studio .NET
Abbildung 2.4: Neuigkeiten für Entwickler von Microsoft
2
Einführung in Visual Studio .NET
Sandini Bib Startseite
75
Search Online Das Durchsuchen der MSDN Online Library ist durch diesen Menüpunkt möglich.
Downloads Wer auf der Suche nach den neuesten Entwicklerdownloads ist, findet sie auf dieser Seite bestimmt. Außerdem hat man Zugriff auf die MSDN Subscriber Downloads.
Abbildung 2.5: Kostenlose Downloads und Downloads für MSDN-Subscriber
XML WebServices Über diese Seite kann man WebServices, die man selbst entwickelt hat, beim UDDIServer vom Microsoft, der auch auf andere Server von Partnern des UDDI-Netzwerkes gespiegelt wird, registrieren lassen. Außerdem ist es auch möglich, dass man auf dem UDDI-Server nach bestimmten WebServices suchen kann. Weitere Informationen rund um UDDI und WebServices findet man unter http://msdn.microsoft.com/uddi und http://msdn.microsoft.com/webservices.
Sandini Bib 76
2
Einführung in Visual Studio .NET
Abbildung 2.6: Suche nach WebServices im UDDI-Katalog von Microsoft
Web Hosting Man schreibt eine Anwendung mit ASP.NET und dann wird sicherlich die Frage kommen, wo man zurzeit eine solche Webanwendung hosten soll. Denn zurzeit gibt es noch nicht viele Provider, die das .NET Framework unterstützen. Wenn Sie trotzdem einen Provider finden wollen, werden sie auf dieser Seite fündig (siehe Abbildung 2.7).
My Profile Jeder Programmierer hat seine eigenen Vorlieben, wie die IDE aussehen soll. Auf dieser Seite können Sie die Entwicklungsumgebung an Ihre Bedürfnisse anpassen. Hierzu zählen das Tastaturlayout, das Fensterlayout und die integrierte Hilfe (siehe Abbildung 2.8).
Sandini Bib Startseite
Abbildung 2.7: Suche nach WebHosting-Providern, die ASP.NET unterstützen
Abbildung 2.8: Benutzerdefinierte Einstellungen für das Visual Studio .NET
77
Sandini Bib 78
2
Einführung in Visual Studio .NET
2.3 Projekte Wenn man ein Projekt zu programmieren beginnt, wird man es sicherlich nicht von Grund auf erstellen. Visual Studio .NET bietet sehr viele Projektassistenten, die das Grundgerüst des gewünschten Projekts für Sie liefern. Dadurch erspart man sich sehr viel an Entwicklungszeit, die man viel produktiver einsetzen kann. Die folgende Abbildung zeigt, welche Projektarten bei Visual Studio .NET Enterprise Architect vorhanden sind, wenn man ein ehemaliger eingefleischter C++-Programmierer ist und Visual C# und Visual C++ installiert sind:
Abbildung 2.9: Projektarten bei Visual Studio .NET Enterprise Architect Edition
In diesem Abschnitt möchte ich jetzt die wichtigsten Projektarten von Visual Studio .NET näher beschreiben und zeigen, welcher Code durch den Assistenten automatisch generiert wird. Außerdem möchte ich noch zeigen, wie mit dem generierten Code Anwendungen und Komponenten entwickelt werden.
Windows-Anwendung Möchte man eine Windows-Anwendung auf der Basis von Microsoft .NET entwickeln, sollte man diese Projektart verwenden.
Sandini Bib Projekte
79
Abbildung 2.10: Erstellen einer Windows-Anwendung mit dem Wizard
Wenn der Anwendungsassistent das Grundgerüst der Windows-Anwendung erstellt, werden die folgenden Dateien zur Projektmappe hinzugefügt: 왘 App.ico: Das Anwendungssymbol 왘 AssemblyInfo.cs: Hier können nähere Informationen wie die Versionsnummer für die Assembly eingetragen werden. 왘 Form1.cs: Diese Datei repräsentiert das Startformular der Anwendung.
Klassenbibliothek Möchte man eine Klassenbibliothek oder eine Komponente erstellen, sollte man sich den Wizard für die Komponentenerstellung näher ansehen. Dieser legt eine Klassendatei an und stellt die Eigenschaften des Projekts so ein, dass aus dem Quellcode eine Assembly generiert wird. Das erstellte Projekt enthält dabei die folgenden Dateien: 왘 AssemblyInfo.cs: Hier können nähere Informationen wie die Versionsnummer für die Assembly eingetragen werden. 왘 Class1.cs: Das ist die Klassendatei, die durch den Assistenten automatisch angelegt wird. Sie kann direkt für die Entwicklung der eigenen Komponente oder Klasse verwendet werden.
Sandini Bib 80
2
Einführung in Visual Studio .NET
Abbildung 2.11: Assistent zur Erstellung einer Klassenbibliothek oder einer Komponente
Windows-Steuerelementebibliothek Möchte man für Windows-Anwendungen Steuerelemente erstellen, sollte man diesen Assistenten verwenden. Dieser stellt ebenfalls die Projekteigenschaften so ein, dass als Ausgabe eine Assembly mit dem programmierten Steuerelement erstellt wird.
Abbildung 2.12: Erstellen einer Windows-Steuerelementebibliothek
Sandini Bib Projekte
81
Der Anwendungsassistent erstellt dabei die folgenden Quelltextdateien, die zum neu angelegten Projekt hinzugefügt werden: 왘 AssembylInfo.cs: Hier können nähere Informationen wie die Versionsnummer für die Assembly eingetragen werden. 왘 UserControl1.cs: Das ist die Quelltextdatei, die das Verhalten des Steuerelements definiert. Hier kann man dann seinen Code einfügen, der das Steuerelement mit Leben füllt, und kann es somit an seine Bedürfnisse anpassen.
ASP.NET-Anwendung Wenn man diesen Assistenten verwendet, wird eine Internet-Anwendung auf der Basis von ASP.NET durch den Assistenten erstellt.
Abbildung 2.13: ASP.NET-Anwendungsassistent
Führt man diesen Assistenten aus, werden die folgenden Dateien zur Projektmappe hinzugefügt: 왘 AssemblyInfo.cs: Hier können nähere Informationen wie die Versionsnummer für die Assembly eingetragen werden. 왘 Global.asax: Diese Datei ist das Gegenstück zur Datei global.asa in ASP. Hier kann man z.B. Komponenten vom Server-Explorer auf die Datei ziehen, mit denen man anschließend programmieren möchte. 왘 Global.asax.cs: In dieser Datei kann man Ereignisbehandlungsfunktionen für globale Ereignisse wie z.B. Session_OnStart oder Session_OnEnd definieren.
Sandini Bib 82
2
Einführung in Visual Studio .NET
왘 Web.config: In dieser XML-Konfigurationsdatei kann man globale Konfigurationseinstellungen für die Web-Anwendung festlegen. Nähere Informationen darüber findet man im Kapitel 8. 왘 WebApplication1.vsdisco: Diese Datei wird bei der Verwendung von WebServices benutzt. Nähere Informationen darüber findet man in der MSDN-Library. 왘 WebForm1.aspx: Diese Datei stellt die Html-Repräsentation der ASP.NET-Seite dar. 왘 WebForm1.aspx.cs: In dieser Datei kann man auf Ereignisse reagieren, die in der ASP.NET-Seite ausgelöst werden. Der Inhalt dieser Datei wird zu einer Assembly kompiliert, wodurch der Quelltext für Dritte nicht mehr einsehbar ist. Zusätzlich wird im Internet Information Manager noch ein virtuelles Verzeichnis angelegt, unter dem die Web-Anwendung auf dem lokalen Computer erreichbar ist.
ASP.NET-WebService Möchte man einen WebService auf der Basis von ASP.NET erstellen, ist diese Projektart genau das Richtige.
Abbildung 2.14: Anwendungsassistent für WebServices auf der Basis von ASP.NET
Wenn man diesen Assistenten ausführt, werden die folgenden Dateien zur Projektmappe hinzugefügt: 왘 AssemblyInfo.cs: Hier können nähere Informationen wie die Versionsnummer für die Assembly eingetragen werden.
Sandini Bib Projekte
83
왘 Global.asax: Diese Datei ist das Gegenstück zur Datei global.asa in ASP. Hier kann man z.B. Komponenten vom Server-Explorer auf die Datei ziehen, mit denen man anschließend programmieren möchte. 왘 Global.asax.cs: In dieser Datei kann man Ereignisbehandlungsfunktionen für globale Ereignisse wie z.B. Session_OnStart oder Session_OnEnd definieren. 왘 Service1.asmx: Dies ist die eigentliche Datei, die den WebService implementiert. Hier kann man z.B. Komponenten vom Server-Explorer auf die Datei ziehen, mit denen man anschließend programmieren möchte. 왘 Service1.asmx.cs: In dieser Datei kann man die eigentlichen WebService-Methoden definieren und programmieren, indem man zur entsprechenden Funktion das Attribut [WebMethod] hinzufügt. Dadurch wird für die Funktion eine WSDLBeschreibung generiert, aus der die Client-Anwendung eine Proxyklasse für den Zugriff erstellen kann. 왘 Web.config: In dieser XML-Konfigurationsdatei kann man globale Konfigurationseinstellungen für die Web-Anwendung festlegen. Nähere Informationen darüber findet man im Kapitel 8. 왘 WebService1.vsdisco: Diese Datei wird für die Verwendung von WebServices benutzt. Nähere Informationen darüber findet man in der MSDN-Library. Ebenso wie bei einer ASP.NET-Anwendung wird auch hier im Internet Information Manager ein virtuelles Verzeichnis erstellt, unter dem der WebService auf dem lokalen Computer aufgerufen werden kann. Nähere Informationen darüber findet man im Kapitel 11.
Web-Steuerelementebibliothek Möchte man ein Web-Steuerelement programmieren, das auf verschiedenen ASP.NETSeiten wiederverwendet werden kann, ist diese Projektart für die Aufgabenstellung bestens geeignet. Dieser Assistent legt die folgenden Dateien in der Projektmappe an: 왘 AssemblyInfo.cs: Hier können nähere Informationen wie die Versionsnummer für die Assembly eingetragen werden. 왘 WebCustomControl1.cs: In dieser Quelltextdatei kann man das Verhalten des Web Steuerelements gezielt festlegen. Dabei ist es sogar möglich, dass man von einem bestehenden Steuerelement eine Klasse ableitet und somit dieses Steuerelement um eigene Funktionen erweitert.
Sandini Bib 84
2
Einführung in Visual Studio .NET
Abbildung 2.15: Verwendung des Assistenten für ein Web-Steuerelement
Konsolenanwendung Möchte man mit dem Visual Studio .NET nur eine normale Konsolenanwendung entwickeln, kann man mit diesem Anwendungsassistenten das Grundgerüst dafür erstellen. Hier werden die Projekteigenschaften so eingestellt, dass automatisch eine Konsolenanwendung erstellt wird, wenn die Projektmappe übersetzt wird.
Abbildung 2.16: Anwendungsassistent für eine Konsolenanwendung
Sandini Bib Projekte
85
Erstellt man eine Konsolenanwendung mit diesem Assistenten, werden die folgenden Dateien angelegt: 왘 App.ico: Das Anwendungssymbol 왘 AssemblyInfo.cs: Hier können nähere Informationen wie die Versionsnummer für die Assembly eingetragen werden. 왘 Class1.cs: Dies ist die Hauptklasse der Konsolenanwendung. In dieser Klasse ist die statische Funktion Main definiert, die aufgerufen wird, wenn das Programm gestartet wird.
Windows Service Ein komplett neuer Projekttyp, den sich vielleicht viele Windows-Programmierer schon lange gewünscht haben, ist dieser. Mit diesem Projekttyp kann man WindowsDienste erstellen, die anschließend auf einem beliebigen Computer im Hintergrund laufen können. Bekannte Dienste sind z.B. der SQL Server oder der Exchange Server von Microsoft.
Abbildung 2.17: Erstellen eines Windows Dienstes
Sandini Bib 86
2
Einführung in Visual Studio .NET
Wurde das neue Windows-Services-Projekt durch den Assistenten erstellt, werden die folgenden Dateien zur Projektmappe hinzugefügt: 왘 AssemblyInfo.cs: Hier können nähere Informationen wie die Versionsnummer für die Assembly eingetragen werden. 왘 Service1.cs: Dies ist die eigentliche Datei, die den Windows Service implementiert. Hier kann man z.B. Komponenten vom Server-Explorer auf die Datei ziehen, mit denen man anschließend programmieren möchte.
Datenbankprojekt
Abbildung 2.18: Verwenden des Datenbankprojekt-Assistenten von Visual Studio .NET
Ebenfalls eine neue Projektart ist das Datenbankprojekt. Mit dieser Projektart kann mit den integrierten Visual Database Tools von Visual Studio .NET eine Datenbank entwerfen und anschließend entsprechend programmieren. Wird der Assistent ausgeführt, muss zuerst eine Referenz zu einer Datenbank hinzugefügt werden. Dies geschieht über das auf der folgenden Seite gezeigte Dialogfenster. Wurde das Projekt erfolgreich erstellt, besteht dann eine Referenz zur vorher ausgewählten Datenbank. Danach können Tabellen, Sichten, gespeicherte Prozeduren usw. für diese Datenbank modelliert und programmiert werden.
Sandini Bib Verwendung von Visual Studio .NET
87
Abbildung 2.19: Hinzufügen einer Datenbankreferenz
2.4 Verwendung von Visual Studio .NET In diesem Abschnitt möchte ich jetzt zeigen, wie man das Visual Studio .NET sinnvoll für die Anwendungsentwicklung unter Microsoft .NET einsetzen kann. Dabei werden wir sehen, wie man den WebForms-Designer für das Erstellen von leistungsfähigen Web-Oberflächen verwenden kann. Außerdem wird gezeigt, wie man die integrierten Steuerelemente für die Datenbindung verwenden kann, ohne dass man eine Zeile Code dafür schreiben muss.
2.4.1 Hello World in ASP.NET Fängt man an, eine Programmiersprache neu zu lernen, wird man immer wieder auf das gleiche Beispiel stoßen: nämlich auf die berühmte Hello World Anwendung. In diesem Abschnitt möchte ich jetzt eine Schritt-für-Schritt-Anleitung beschreiben, die zeigt, wie man diese Anwendung mit dem Visual Studio .NET programmiert, um eine Web-Anwendung zu erstellen. Nach dieser Anleitung wird man in der Lage sein, selbstständig einfache Web-Anwendungen mit dem Visual Studio .NET zu programmieren. Sehen wir uns im ersten Schritt in Abbildung 2.20 an, wie die fertige Web-Anwendung im Internet Explorer aussehen soll. Gibt man in der TextBox seinen Namen ein, und klickt anschließend auf den Button Say Hello World, werden im unteren Textfeld die Zeichenfolge Hello World und der eingegebene Name ausgegeben.
Sandini Bib 88
2
Einführung in Visual Studio .NET
Abbildung 2.20: Unsere fertige ASP.NET-Anwendung
Um das Webprojekt anzulegen, wählen wir den Menüpunkt FILE/NEW/PROJECT aus. Im folgenden Dialogfenster braucht man dann nur noch unter Projekttypen Visual C# Projects zu markieren. Dadurch erhält man auf der rechten Seite des Dialogfensters alle Projekte, die man mit Visual C# erstellen kann. Hier wählen wir dann anschließend das Projekt ASP.NET Web Application aus. In der Textbox unten kann man dann noch das Zielverzeichnis der Web-Anwendung angeben. Die Standardeinstellung ist hier http:// localhost/WebApplication1. Dadurch wird die Web-Anwendung auf dem lokalen Webserver angelegt. Für dieses Beispiel geben wir in diese Textbox http://localhost/HelloWorld ein. Anschließend klicken wir noch auf den Button OK. Dadurch wird jetzt auf dem lokalen Webserver ein neues virtuelles Verzeichnis mit dem Namen HelloWorld angelegt, das auf den Pfad c:\inetpub\wwwroot\HelloWorld verweist. Unter diesem Pfad werden auch die Projektdateien und alle anderen Dateien, die von der Web-Anwendung verwendet werden, abgelegt. Nachdem der Anwendungsassistent ausgeführt wurde, wird die WebForm WebForm1.aspx im Designer geöffnet und kann somit bearbeitet werden. Auf der linken Seite von Visual Studio .NET finden wir die Werkzeugleiste, die die gleiche Funktionalität wie unter Visual Basic hat. Das heißt, man kann ein Steuerelement direkt auf die WebForm ziehen und entsprechend positionieren. Hat man das Steuerelement positioniert, kann man dann noch die Eigenschaften an die entsprechenden Gegebenheiten anpassen. Die folgende Abbildung zeigt alle Steuerelemente, die durch die Werkzeugleiste verfügbar sind:
Sandini Bib Verwendung von Visual Studio .NET
89
Abbildung 2.21: Die Werkzeugleiste von Visual Studio .NET
Sehen wir uns die wichtigsten Steuerelemente an, die uns die Werkzeugleiste bietet: 왘 Label: Mit diesem Steuerelement kann man einen Text auf der WebForm platzieren, der durch den serverseitigen Code geändert werden kann. 왘 TextBox: Dieses Steuerelement zeigt eine TextBox auf der WebForm an, die durch den serverseitigen Code programmiert werden kann. 왘 Button: Durch dieses Steuerelement wird ein Button erstellt, auf dessen Klick-Ereignis man im serverseitigen Code entsprechend reagieren kann. 왘 LinkButton: Dieses Steuerelement erstellt einen Button. Wird dieser Button durch den Benutzer geklickt, wird zu einem entsprechend festgelegten Url gewechselt.
Sandini Bib 90
2
Einführung in Visual Studio .NET
왘 ImageButton: Dieses Steuerelement verhält sich ebenso wie ein LinkButton. Der einzige Unterschied ist, dass man ein Bild angeben kann, das auf der Oberfläche angezeigt wird. 왘 Hyperlink: Durch dieses Steuerelement wird ein Hyperlink erstellt, der auch im serverseitigen Code angesprochen und dadurch programmiert werden kann. 왘 DropDownList: Dieses Steuerelement stellt uns eine DropDownList zur Verfügung, die wir mit Werten füllen können. 왘 ListBox: Das ListBox-Steuerelement verhält sich ebenso wie das DropDownList-Steuerelement, nur dass die Werte in einer ListBox angezeigt werden. 왘 DataGrid: Das DataGrid-Element ist das leistungsfähigste Steuerelement bei den WebForms-Controls. Mit diesem Steuerelement kann man fast alle Daten anzeigen und bearbeiten. Auf die Verwendung dieses Steuerelements wird in den nachfolgenden Kapiteln noch öfter näher eingegangen. 왘 DataList: Das DataList-Steuerelement gleicht dem DataGrid-Steuerelement, nur dass die Daten in Listenform angezeigt werden. 왘 CheckBox: Dieses Steuerelement erzeugt eine CheckBox, die durch den serverseitigen Code programmiert werden kann. 왘 RadioButton: Dieses Steuerelement erzeugt einen RadioButton, der durch den serverseitigen Code programmiert werden kann. 왘 Image: Mithilfe dieses Steuerelements kann eine Grafik auf der WebForm angezeigt werden. 왘 PlaceHolder: Dieses Steuerelement erzeugt einen Platzhalter. In einen solchen Platzhalter können z.B. andere Steuerelemente zur Laufzeit hinzugefügt werden. Platzhalter werden sehr oft für die Verwendung von UserControls verwendet. Mehr über UserControls findet man in den nachfolgenden Kapiteln. 왘 Calendar: Ein ebenfalls sehr komplexes Steuerelement ist das Calendar-Control. Mit diesem Steuerelement kann man einen Kalender auf der WebForm anzeigen, durch den der Benutzer ein beliebiges Datum oder eine beliebige Woche auswählen kann. Nachdem jetzt die wichtigsten Steuerelemente näher beschrieben wurden, sehen wir uns an, welche Elemente wir für unsere Web-Anwendung benötigen: 왘 zwei Label-Steuerelemente 왘 ein TextBox-Steuerelement 왘 einen Button
Sandini Bib Verwendung von Visual Studio .NET
91
Man braucht ein Steuerelement nur in der Werkzeugleiste zu markieren und anschließend auf die WebForm zu ziehen. Ich habe die Steuerelemente wie in der folgenden Abbildung angeordnet:
Abbildung 2.22: Erstellen der Benutzeroberfläche für die Web-Anwendung
Über das Eigenschaftenfenster, das sich auf der rechten Seite von Visual Studio .NET befindet, kann man die einzelnen Eigenschaften der Steuerelemente näher festlegen. Die folgende Abbildung zeigt z.B. das Eigenschaftenfenster für das Button-Steuerelement:
Abbildung 2.23: Das Eigenschaftenfenster von Visual Studio .NET
Sandini Bib 92
2
Einführung in Visual Studio .NET
Die Eigenschaften des ersten Label-Steuerelements habe ich wie folgt festgelegt: 왘 ID: lblMessage 왘 Text: Bitte geben Sie Ihren Namen ein: Hier sind die Eigenschaften des zweiten Label-Steuerelements: 왘 ID: lblOutput: 왘 Text: "" Beim TextBox-Steuerelement habe ich die folgenden Eigenschaften verändert: 왘 ID: txtName Beim letzten Steuerelement, der TextBox, sind die folgenden Eigenschaften geändert worden: 왘 ID: cmdHelloWorld 왘 Text: Say Hello World Ich habe jedem Steuerelement eine ID vergeben, durch die man dieses Element im serverseitigen Code ansprechen kann. Durch die Vergabe der IDs wurde durch den Designer der folgende Code generiert: protected protected protected protected
Diese Variablen werden innerhalb der Klassendeklaration definiert und sind somit in der ganzen Klasse verfügbar. Um jetzt auf das Klick-Ereignis des Buttons reagieren zu können, muss eine Ereignisbehandlungsfunktion erstellt werden. Diese Aufgabe wird uns aber durch den WebForms-Designer komplett abgenommen: Das Einzige, was wir machen müssen, ist, dass wir auf den Button im Designer einen Doppelklick machen. Dadurch wird die Ereignisbehandlungsfunktion automatisch angelegt und die Funktion im Codefenster wie in Abbildung 2.24 angezeigt. In diese Ereignisbehandlungsfunktion können wir jetzt den folgenden Code einfügen: private void cmdHelloWorld_Click(object sender, System.EventArgs e) { lblOutput.Text = "Hello World " + txtName.Text; }
Sandini Bib Verwendung von Visual Studio .NET
93
Abbildung 2.24: Die durch den Designer erstellte Ereignisbehandlungsfunktion
Dadurch werden im unteren Label-Steuerelement die Zeichenfolge Hello World und der eingebene Name angezeigt. Dabei wird über die Eigenschaft Text auf den eingegebenen Namen zugegriffen und dieser wiederum der Eigenschaft Text des Label-Steuerelements zugewiesen. Jetzt werden sich vielleicht einige noch wundern, wie jetzt eigentlich diese Ereignisbehandlungsfuntion aufgerufen wird. Der notwendige Code dafür wird durch den Designer angelegt und sollte durch den Programmierer nicht verändert werden. Er befindet sich im Abschnitt Web Form Designer generated code, der in der vorherigen Abbildung zu sehen ist. Wenn wir diesen Abschnitt im Code-Editor öffnen, sehen wir die folgenden beiden Funktionen: override protected void OnInit(EventArgs e) { // // CODEGEN: This call is required by the ASP.NET Web Form Designer. // InitializeComponent(); base.OnInit(e); }
Sandini Bib 94
2
Einführung in Visual Studio .NET
private void InitializeComponent() { this.cmdHelloWorld.Click += new System.EventHandler(this.cmdHelloWorld_Click); this.Load += new System.EventHandler(this.Page_Load); }
In der Funktion InitializeComponent wird dem Klick-Ereignis des Button-Steuerelements ein neuer Delegate zugewiesen, der die Funktion cmdHelloWord_Click aufruft. Dadurch wird das Klick-Ereignis mit der Ereignisbehandlungsfunktion verknüpft und damit aufgerufen, wenn der Benutzer auf den Button klickt. Im letzten Schritt müssen wir jetzt nur die Web-Anwendung übersetzen und ausführen. Um die Web-Anwendung zu übersetzen, brauchen wir nur vom Menü BUILD den Befehl BUILD HELLO WORLD auswählen. Dadurch wird unsere Web-Anwendung übersetzt und kann anschließend im Browser angezeigt werden. Um die Web-Anwendung im Browser anzeigen zu lassen, brauchen wir nur (F5) zu drücken. Danach bekommen wir die folgende Abbildung zu sehen:
Abbildung 2.25: Unsere erste Web-Anwendung
Wenn wir jetzt in der TextBox einen Namen eingeben und anschließend auf den Button klicken, bekommen wir das in Abbildung 2.26 gezeigte Ergebnis zu sehen. In dieser Schritt-für-Schritt-Anleitung haben wir jetzt gesehen, wie man eine WebAnwendung mit dem Visual Studio .NET programmieren kann und haben uns dabei mit einem neuen Programmiermodell vertraut gemacht. In den nächsten Kapiteln werden wir dann sehen, wie man mit ASP.NET leistungsfähige Anwendungen schreiben kann.
Sandini Bib Verwendung von Visual Studio .NET
95
Abbildung 2.26: Hello World mit ASP.NET und Visual Studio .NET
2.4.2 Datenbindung Die Datenbindung ist ein Thema unter ASP.NET, das sehr groß geschrieben wird. Mit der Datenbindung ist es sehr einfach, Daten aus einer Datenbank auf einer WebForm anzuzeigen und sogar zu bearbeiten. Außerdem ist es mithilfe der Datenbindung möglich, bestimmte Eigenschaften von Steuerelementen an eine Spalte innerhalb einer Datenbank zu binden. Daraus ergeben sich ungeahnte Szenarien, die man mit WebAnwendungen erstellen. Eine Möglichkeit wäre z.B., dass man das Aussehen und die Stile der einzelnen Steuerelemente in einer Datenbank speichert und anschließend die entsprechenden Eigenschaften an die Spalten innerhalb einer entsprechenden Tabelle bindet. Das DataGrid-Steuerelement arbeitet ebenfalls sehr gut mit der Datenbindung zusammen. Eigentlich sind nur ein paar Schritte notwendig, um Daten aus einer Datenbank mit dem DataGrid-Steuerelement anzuzeigen. Hier gibt es dann sogar die Möglichkeit, dass die Daten mit diesem Steuerelement bearbeitet und anschließend aktualisiert werden können. In diesem Abschnitt möchte ich ein Beispiel präsentieren, dass zeigt, wie man nur mit ein paar Zeilen Code Daten aus einer Datenbank mit dem DataGrid-Steuerelement anzeigen und bearbeiten kann. Dabei wird für die Erstellung dieser Anwendung nur auf die Assistenten von Visual Studio .NET zugegriffen, die dem Programmierer die Arbeit ziemlich abnehmen können. Genug der langen Rede, gehen wir an die Arbeit! Im ersten Schritt müssen wir wieder eine ASP.NET-Anwendung innerhalb von Visual Studio .NET anlegen. Wie das funktioniert, haben wir ja bereits im vorherigen Beispiel gesehen. Ich habe meiner Anwendung den Namen WebAppDataBinding gegeben. Nachdem die Anwendung durch den Assistenten erstellt wurde, präsentiert sich uns wieder eine leere WebForm. Auf dieser
Sandini Bib 96
2
Einführung in Visual Studio .NET
WebForm möchte ich jetzt mithilfe eines DataGrid-Steuerelements ein paar Kunden der Tabelle Customers der Datenbank Northwind anzeigen. Daher müssen wir jetzt zuerst eine Verbindung zur Datenbank Northwind herstellen und das entsprechende SQL-Statement definieren, das uns die gewünschten Daten zurückliefert. Dafür fügen wir von der Werkzeugleiste unter der Registerkarte Data einen SqlDataAdapter auf unsere WebForm ein. Wenn wir diese Komponente auf unsere WebForm gezogen haben, wird automatisch der Konfigurationsassistent für den DataAdapter gestartet:
Abbildung 2.27: Der DataAdapter-Konfigurationsassistent
Im zweiten Schritt des Assistenten muss man die Datenbankquelle angeben, die man für diesen DataAdapter verwenden möchte. Dabei kann man eine vorhandene Verbindung auswählen, die schon einmal erstellt wurde, oder eine neue einrichten. Da wir noch keine Verbindung zur Datenbank Northwind eingerichtet haben, erstellen wir eine neue. Dafür klicken wir auf den Button mit der Aufschrift NEW CONNECTION ... Im folgenden Dialogfenster geben wir dann die folgenden Informationen ein, um eine Verbindung zur Datenbank Northwind herzustellen:
Sandini Bib Verwendung von Visual Studio .NET
97
Abbildung 2.28: Herstellen einer Verbindung zur Datenbank Northwind
Anschließend klicken wir auf den Button OK. Dadurch wird die soeben erstellte Datenbankverbindung übernommen und das ursprüngliche Dialogfenster angezeigt. In diesem Dialogfenster können wir auf NEXT klicken. Im folgenden Dialogfenster müssen wir jetzt den Abfragetyp festlegen, der spezifiziert, wie wir die Daten von der Datenbank abfragen. Hier gibt es die folgenden Möglichkeiten: 왘 Schreiben eines SQL-Statements 왘 Erstellen einer neuen gespeicherten Prozedur 왘 Verwendung einer bereits vorhandenen gespeicherten Prozedur Für dieses Beispiel entscheiden wir uns für den ersten Punkt, nämlich für die Erstellung eines eigenen SQL-Statements. Daher können wir in diesem Dialogfenster einfach auf den Button NEXT klicken. Im nachfolgenden Dialogfenster können wir jetzt unser SQL-Statement manuell eingeben oder durch einen Abfrageassistenten erstellen lassen. Da wir in diesem Beispiel so wenig wie möglich schreiben möchten, entscheiden wir uns für den Abfrageassistenten. Im Abfrageassistenten können wir jetzt die verschiedenen Tabellen, die wir in der Abfrage verwenden möchten, hinzufügen. Wir fügen die beiden Tabellen Customers und Orders durch einen Doppelklick zur Abfrage hinzu.
Sandini Bib 98
2
Einführung in Visual Studio .NET
Abbildung 2.29: Festlegen des Abfragetyps für das SQL-Statement
Der Abfrageassistent erkennt automatisch die Beziehung zwischen diesen beiden Tabellen. Daher wird sie auch grafisch angezeigt. Ich habe die folgenden Spalten der Tabelle Customers zur Abfrage hinzugefügt: 왘 CustomerID 왘 CompanyName 왘 City Von der Tabelle Orders wurden die folgenden Spalten hinzugefügt: 왘 OrderDate 왘 ShipName 왘 ShipAddress Nach dem Hinzufügen der verschiedenen Spalten präsentiert sich der Abfrageassistent wie in der folgenden Abbildung.
Sandini Bib Verwendung von Visual Studio .NET
99
Abbildung 2.30: Der Abfrageassistent nach dem Hinzufügen der verschiedenen Spalten
Der Abfrageassistent hat das folgende SQL-Statement für uns automatisch erstellt: SELECT Customers.CustomerID, Customers.CompanyName, Customers.City, Orders.OrderDate, Orders.ShipName, Orders.ShipAddress FROM Customers INNER JOIN Orders ON Customers.CustomerID = Orders.CustomerID
Um jetzt nur die ersten 10 Datensätze zurückzubekommen, ändern wir das SQL-Statement wie folgt um: SELECT TOP 10 Customers.CustomerID, Customers.CompanyName, Customers.City, Orders.OrderDate, Orders.ShipName, Orders.ShipAddress FROM Customers INNER JOIN Orders ON Customers.CustomerID = Orders.CustomerID
Anschließend können wir auf den Button NEXT klicken und im letzten Dialogfenster auf FINISH.
Sandini Bib 100
2
Einführung in Visual Studio .NET
Abbildung 2.31: Der DataAdapter-Assistent nach seiner Arbeit
Nach dem Beenden des DataAdapter-Assistenten werden im unteren Bereich unserer WebForm zwei neue Objekte hinzugefügt: Ein SqlDataAdapter-Objekt und ein SqlConnection-Objekt. Diese beiden Objekte habe ich wie folgt umbenannt: 왘 sqlDataAdapter1 in myDataAdapter 왘 sqlConnection1 in cnnConnection Wenn wir den DataAdapter markieren und das Eigenschaftenfenster öffnen, haben wir im unteren Bereich dieses Fensters die Möglichkeit, den DataAdapter zu konfigurieren, ein DataSet zu erstellen und uns die zurückgelieferten Daten anzusehen. Wenn wir auf den Link Create DataSet klicken, bekommen wir das in Abbildung 2.32 gezeigte Dialogfenster zu sehen. Hier haben wir die Möglichkeit, den Namen des zu erzeugenden DataSets anzugeben und welche Tabellen zum DataSet gehören sollen. Ich habe das neue DataSet dsCustomers genannt. Wenn wir auf den Button OK klicken, wird das DataSet erzeugt und zur Projektmappe hinzugefügt. Außerdem erscheint das DataSet im unteren Bereich unserer WebForm bei den anderen beiden Objekten myDataAdapter und cnnConnection.
Sandini Bib Verwendung von Visual Studio .NET
101
Abbildung 2.32: DataSet erzeugen
Nach diesen Schritten haben wir jetzt alle Objekte erzeugt, die benötigt werden, um Daten in einem DataGrid-Steuerelement anzuzeigen. Daher können wir jetzt im nächsten Schritt ein DataGrid-Steuerelement auf unsere WebForm ziehen. Diese befindet sich in der Werkzeugleiste unter der Registerkarte Web Forms. Nachdem das DataGrid auf der WebForm angezeigt wird, müssen wir jetzt unser DataGrid-Steuerelement mit dem soeben erzeugten DataSet verknüpfen. Dazu müssen wir die folgenden Schritte durchführen: 왘 Setzen der DataSource-Eigenschaft des DataGrid-Steuerelements auf das DataSet dsCustomers. 왘 Setzen der DataMember-Eigenschaft des DataGrid-Steuerelements auf die DataTable Customers. 왘 Setzen der DataKeyField-Eigenschaft des DataGrid-Steuerelements auf die Spalte CustomerID. Nachdem wir diese Schritte durchgeführt haben, sehen wir beim DataGrid-Steuerelement in der Entwurfsansicht bereits die einzelnen Spalten unserer SQL-Abfrage. Damit jetzt noch die Daten im DataGrid-Steuerelement angezeigt werden, müssen in der Funktion Page_Load unserer WebForm die folgenden beiden Zeilen Code eingefügt werden:
Durch die erste Zeile wird unser DataSet mit Daten gefüllt. Dabei wird als erster Parameter unser DataSet übergeben und als zweiter Parameter der Name unserer Tabelle bzw. DataTable. In der zweiten Zeile wird dann das DataGrid-Steuerelement an die Daten gebunden. Dadurch werden die Daten angezeigt. Wenn wir unsere Web-Anwendung übersetzen und im Internet Explorer ansehen, sehen wir die folgende Abbildung:
Abbildung 2.33: Unser DataGrid-Steuerelement
Da wir jetzt mit der Abfrage der Daten fertig sind, kümmern wir uns im nächsten Schritt ein wenig um das Design der Oberfläche des DataGrid-Steuerelements. Das DataGrid kann man mit einem vorgefertigten Design versehen, so wie man das z.B. von Microsoft Excel gewohnt ist. Dazu markieren wir das DataGrid und klicken die rechte Maustaste. Im erscheinenden Popup-Menü wählen wir den Punkt Auto Format aus. Im nachfolgenden Dialogfenster habe ich mich für das Format Colorful 1 entschieden. Wenn wir auf OK klicken, wird das DataGrid sofort im neuen Design dargestellt. Ein leistungsfähiges Werkzeug des DataGrids ist der Property-Builder, über den man sehr viele Eigenschaften und das Verhalten des Steuerelements festlegen kann. Den Property-Builder können wir anzeigen, indem wir das DataGrid markieren und wieder die rechte Maustaste klicken. Danach erscheint ein Menüpunkt, über den der PropertyBuilder aufgerufen werden kann:
Sandini Bib Verwendung von Visual Studio .NET
103
Abbildung 2.34: Der Property-Builder des DataGrid-Steuerelements
Wenn wir bei den Objekten den Eintrag Items auswählen, können wir das Aussehen der einzelnen Einträge des DataGrids festlegen. Dabei habe ich bei den Normal Items die folgenden Eigenschaften festgelegt: 왘 Schriftart: Tahoma 왘 Schriftgröße: X-Small 왘 Horizontale Ausrichtung: Zentriert Bei den Alternating Items wurden die folgenden Eigenschaften eingestellt: 왘 Schriftart: Tahoma 왘 Schriftgröße: X-Small 왘 Hintergrundfarbe: NavajoWhite 왘 Horizontale Ausrichtung: Zentriert
Sandini Bib 104
2
Einführung in Visual Studio .NET
Bei der Überschrift habe ich mich für die folgenden Einstellungen entschieden: 왘 Schriftart: Tahoma 왘 Schriftgröße: X-Small 왘 Vordergrundfarbe: #FFFFCC 왘 Hintergrundfarbe: #990000 왘 Horizontale Ausrichtung: Zentriert Wenn wir uns jetzt anschließend unser DataGrid wieder im Browser ansehen, bekommen wir die folgende, schönere Ausgabe:
Abbildung 2.35: Formatierte Ausgabe des DataGrid-Steuerelements
Im nächsten Schritt möchte ich jetzt das DataGrid so anpassen, dass ein paar Spalten ausgeblendet werden, und dass es die Möglichkeit gibt, dass die vorhandenen Daten durch den Benutzer bearbeitet werden können. Da die Spalte CustomerID der Primärdatenschlüssel unserer Tabelle ist, ist es auch logisch, dass dieser durch den Benutzer nicht geändert werden kann. Dazu wechseln wir in den Property-Builder, wählen die Spalte CustomerID aus und markieren die Checkbox SCHREIBGESCHÜTZT. Dadurch kann jetzt diese Spalte im Editiermodus nicht durch den Benutzer geändert werden. Um die Spalte OrderID ausblenden zu können, müssen wir wiederum in den PropertyBuilder wechseln. Dabei habe ich die CheckBox CREATE COLUMNS AUTOMATICALLY AT RUN TIME deaktiviert. Danach wurden die folgenden Spalten zur rechten ListBox hinzugefügt: 왘 CustomerID
Sandini Bib Verwendung von Visual Studio .NET
105
왘 CompanyName 왘 OrderDate 왘 ShipName 왘 ShipAddress Wenn wir bei der linken ListBox ganz nach unten scrollen, kommen wir zum Eintrag Button Columns. Wenn wir diesen öffnen, gibt es die Möglichkeit, dass Buttons zum DataGrid hinzugefügt werden, mit denen es anschließend möglich ist, die vorhandenen Daten zu bearbeiten. Dazu habe ich den Eintrag Edit, Update, Cancel hinzugefügt und die Eigenschaften dafür wie folgt festgelegt: 왘 Edit Text: Bearbeiten 왘 Update Text: Aktualisieren 왘 Cancel Text: Abbrechen Nachdem wir jetzt die einzelnen Buttons und Spalten definiert haben, die angezeigt werden sollen, möchte ich zeigen, wie man Spalten mithilfe des Property-Builders formatieren kann. Dafür habe ich in der rechten oberen ListBox die Spalte OrderDate ausgewählt. Diese Spalte möchte ich so formatieren, dass nur das Datum und nicht die Zeit angezeigt wird. Dazu habe ich in das Textfeld Data Formatting Expression den folgenden Ausdruck eingegeben: {0:d}
Jetzt gibt es beim DataGrid-Steuerelement die Möglichkeit, dass man das Verhalten der einzelnen Spalten genau festlegen kann. Hier kann z.B. angegeben werden, welches Steuerelement angezeigt werden soll, wenn ein Datensatz in den Editiermodus versetzt wird. Standardmäßig wird für jede Spalte eine TextBox angezeigt. Um ein anderes Steuerelement anzeigen zu können, müssen wir die betreffende Spalte in eine so genannte TemplateColumn umwandeln. Dies geschieht durch den Link Convert this column into a Template Column, der sich im unteren Bereich des Property-Builders befindet. Für dieses Beispiel habe ich die folgenden Spalten in eine TemplateColumn konvertiert: 왘 OrderDate 왘 ShipAddress Die OrderDate-Spalte möchte ich so anpassen, dass beim Editieren des Datensatzes das Kalender-Steuerelement angezeigt wird. Bei der Spalte ShipAddress möchte ich dagegen eine mehrzeilige TextBox anzeigen, damit man längere Adressen eingeben kann.
Sandini Bib 106
2
Einführung in Visual Studio .NET
Sehen wir uns jetzt an, wie wir die einzelnen Spalten an unsere Bedürfnisse anpassen können. Nachdem die Spalten konvertiert wurden, markieren wir das DataGrid und klicken wieder mit der rechten Maustaste. Danach wählen wir vom Menü den Punkt EditTemplate und danach OrderDate aus. Durch diese Schritte ist es uns jetzt möglich, das EditItemTemplate dieser Spalte an unsere Wünsche anzupassen. Der Designer präsentiert sich uns wie folgt:
Abbildung 2.36: Festlegen der ItemTemplates der Spalte OrderDate
Hier löschen wir jetzt unter EditItemTemplate die TextBox und fügen von der Werkzeugleiste ein Kalender-Steuerelement ein. Wenn wir das Kalender-Steuerelement markieren und anschließend die rechte Maustaste drücken, haben wir die Möglichkeit wieder, ein Auto Format für das Steuerelement festzulegen. Hier habe ich mich für den Eintrag Colorful 1 entschieden. Dadurch schaut jetzt die Spalte OrderDate wie folgt aus: Das Einzige, was wir jetzt noch machen müssen, ist die Datenbindung für diese Spalte. In diesem Beispiel möchten wir, dass der Kalender das Datum der Spalte OrderDate anzeigt. Dazu wählen wir wieder das Kalender-Steuerelement aus, und im Eigenschaftenfenster klicken wir auf den ersten Eintrag (DataBindings). Dadurch wird das Dialogfenster DataBindings geöffnet, wo wir jetzt die Datenbindung vornehmen können. In diesem Dialogfenster werden die beiden Eigenschaften SelectedDate und VisibleDate an die Spalte OrderDate gebunden. Dazu wählen wir die gewünschte Eigenschaft aus, und in der ListBox wählen wir den Eintrag Container/DataItem/OrderDate aus: Wenn wir danach auf OK klicken, wird die Datenbindung übernommen. Das Gleiche müssen wir dann noch für die Eigenschaft VisibleDate durchführen. Nachdem wir die Spalte OrderDate an unsere Vorstellungen angepasst haben, überarbeiten wir das EditItemTemplate der Spalte ShipAddress. Hier markieren wir die TextBox des EditItemTemplate und setzen ihre Eigenschaften wie folgt: 왘 Columns: 20
Sandini Bib Verwendung von Visual Studio .NET
Abbildung 2.37: Anzeigen eines Kalender-Steuerelements für das EditItemTemplate
Abbildung 2.38: Festlegen der Datenbindung für die Eigenschaft SelectedDate
왘 Rows: 3
107
Sandini Bib 108
2
Einführung in Visual Studio .NET
왘 TextMode: MultiLine Da wir kein anderes Steuerelement eingefügt haben, brauchen wir auch die Datenbindung für dieses Steuerelement nicht erneut durchzuführen. Die Templates für diese TextBox sehen anschließend wie folgt aus:
Abbildung 2.39: Die Templates der Spalte ShipAddress
Nachdem wir jetzt die einzelnen Templates der Spalten angepasst haben, müssen wir noch ein paar Zeilen Code schreiben, damit wir auf die einzelnen Ereignisse reagieren können, die ausgelöst werden, wenn ein Datensatz bearbeitet werden soll. Insgesamt gibt es die folgenden drei Ereignisse, die behandelt werden müssen: 왘 EditCommand: Die zugeordnete Ereignisbehandlungsfunktion wird aufgerufen, wenn wir auf den LinkButton Bearbeiten klicken. 왘 UpdateCommand: Die zugeordnete Ereignisbehandlungsfunktion wird aufgerufen, wenn wir auf den LinkButton Aktualisieren klicken. 왘 CancelCommand: Die zugeordnete Ereignisbehandlungsfunktion wird aufgerufen, wenn wir auf den LinkButton Abbrechen klicken. Um die entsprechenden Ereignisbehandlungsfunktionen hinzuzufügen, markieren wir das DataGrid und wechseln im Eigenschaftenfenster auf die Rubrik EREIGNISSE. Dazu klicken wir auf den gelben Blitz im oberen Bereich des Fensters. Danach führen wir einen Doppelklick auf das Ereignis EditCommand aus. Dadurch fügt der Designer die Ereignisbehandlungsfunktion hinzu und wechselt in die Codeansicht, wo wir jetzt den folgenden Code in die Ereignisbehandlungsfunktion einfügen können: private void DataGrid1_EditCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e) {
In der ersten Codezeile wird der Index des Eintrags ermittelt, der in den Editiermodus versetzt werden soll. Dieser Index wird der Eigenschaft EditItemIndex des DataGrids zugewiesen. Anschließend braucht das DataGrid nur erneut gebunden werden. Dadurch wird in den Editiermodus gewechselt. Der Code für die Ereignisbehandlungsfunktion UpdateCommand sieht wie folgt aus: private void DataGrid1_UpdateCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e) { DataGrid1.EditItemIndex = -1; DataGrid1.DataBind(); }
Hier weisen wir der Eigenschaft EditItemIndex einfach –1 zu. Dadurch wird das DataGrid wieder in den normalen Modus zurückgesetzt. In dieser Funktion könnten wir jetzt die Daten ermitteln, die der Benutzer eingegeben hat, und anschließend die Datenbank damit aktualisieren. Der Code für die Ereignisbehandlungsfunktion UpdateCommand sieht wie folgt aus: private void DataGrid1_CancelCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e) { DataGrid1.EditItemIndex = -1; DataGrid1.DataBind(); }
Nach diesen Schritten haben wir unsere Web-Anwendung so weit fertiggestellt, dass wir sie übersetzen und im Browser anzeigen können (siehe Abbildung 2.40). Wenn wir jetzt auf den LinkButton Bearbeiten klicken, wird der entsprechende Datensatz in den Editiermodus versetzt. Der ausgewählte Datensatz wird dann mit dem festgelegten EditItemTemplate angezeigt (siehe Abbildung 2.41). Wie wir in der Abbildung sehen, wird im Kalender-Steuerelement das richtige Datum ausgewählt und die TextBox für die Spalte ShipAddress erstreckt sich über mehrere Zeilen.
Sandini Bib 110
2
Einführung in Visual Studio .NET
Abbildung 2.40: Unser DataGrid-Steuerelement im endgültigen Zustand
Abbildung 2.41: Der ausgewählte Datensatz im Editiermodus
An diesem komplexen Beispiel haben wir gesehen, wie man mit dem Visual Studio .NET und den integrierten Assistenten Web-Anwendungen erstellen kann, fast ohne dass man eine einzige Zeile Code schreiben muss. Hier wird der Programmierer durch die Entwicklungsumgebung wirklich an allen möglichen Punkten leistungsfähig unterstützt. Dadurch wird es möglich, dass man Anwendungen schneller als je zuvor entwickeln kann.
Sandini Bib Debuggen
111
2.5 Debuggen In diesem Abschnitt möchte ich den Debugger von Visual Studio .NET näher beschreiben. Der integrierte Debugger der IDE hat im Leistungsumfang sehr stark zugenommen und kann wirklich ernsthaft eingesetzt werden. Der größte Funktionszuwachs ist beim Debuggen von Visual C++.NET-Anwendungen wahrzunehmen. Aber auch die Features für das Debuggen von ASP.NET-Anwendungen und Komponenten sind gegenüber der vorherigen Version enorm erweitert worden. Im Folgenden möchte ich beschreiben, welche Features der integrierte Debugger beinhaltet: 왘 Sprachübergreifendes Debugging: Es ist jetzt möglich, Programme zu debuggen, die in verschiedenen Sprachen (Visual C++.NET, Visual C#, Visual Basic .Net, SQL) programmiert wurden. 왘 Debuggen von Anwendungen, die unter dem .NET Framework programmiert wurden. Außerdem ist es natürlich auch noch möglich, Win32-Anwendungen zu debuggen. 왘 Anhängen eines Prozesses, der auf einem anderen Server läuft. Durch dieses Feature kann man z.B. direkt eine ASP.NET-Anwendung debuggen, die auf einem Web Server läuft. 왘 Debuggen von mehreren Programmen gleichzeitig. 왘 Debuggen von Xml WebServices.
2.5.1 Debuggen von ASP.NET-Anwendungen In diesem Abschnitt möchte ich Schritt für Schritt zeigen, welche Vorkehrungen getroffen werden müssen, um eine ASP.NET-Anwendung zu debuggen. Außerdem werden wir anhand dieses Beispiels sehen, welche Möglichkeiten uns der Debugger von Visual Studio .NET bietet, um komplizierte Programmierfehler zu entdecken und beheben zu können. Dieses Beispiel zeigt wieder unsere beliebte Meldung Hello World mit dem Namen, den der Benutzer eingegeben hat, an. Dazu erstellen wir wieder eine ASP.NET-Anwendung. Ich habe der Anwendung den Namen HelloWorldDebugging gegeben. Dadurch wird im Internet Information Server wieder ein virtuelles Verzeichnis mit diesem Namen angelegt, das auf das physische Verzeichnis auf der Festplatte zeigt. Danach habe ich die notwendigen Steuerelemente auf der WebForm platziert, wie in der folgenden Abbildung gezeigt:
Sandini Bib 112
2
Einführung in Visual Studio .NET
Abbildung 2.42: Die Benutzeroberfläche unserer ASP.NET-Anwendung
Wenn wir auf den Button SAY HELLO WORLD! doppelklicken, kommen wir wieder automatisch in die Ereignisbehandlungsfunktion für das Click-Ereignis des Buttons, wo wir jetzt den folgenden Code einfügen können: private void cmdHelloWorld_Click(object sender, System.EventArgs e) { lblOutput.Text = "Hello World " + txtName.Text; }
Nachdem wir den Code eingefügt haben, können wir die Anwendung über den Menüpunkt BUILD/BUILD HELLOWORLDDEBUGGING erstellen lassen. Wenn eine Anwendung mit dem Assistenten angelegt wird, wird standardmäßig immer eine Debug-Version erstellt. Bei einer Debug-Version werden zusätzliche Informationen in die Assemblies mit aufgenommen, mit denen es anschließend möglich ist, die Anwendung zu debuggen. Um zwischen einer Debug- und einer Release-Version umzuschalten, brauchen wir nur wie in der folgenden Abbildung die entsprechende Version aus der Combobox auszuwählen:
Abbildung 2.43: Auswählen der Release- oder Debug-Version
Außerdem gibt es auch noch die Möglichkeit, dass man benutzerdefinierte Konfigurationen erstellen kann. Dies geschieht mit dem Configuration Manager, der ebenfalls aus dieser Combobox ausgewählt werden kann (siehe Abbildung 2.44). Mehr Informationen über den Configuration Manager findet man in der Onlinehilfe von Visual Studio .NET.
Sandini Bib Debuggen
113
Abbildung 2.44: Der Configuration Manager von Visual Studio .NET
Wenn man eine Anwendung debuggen möchte, muss immer sichergestellt sein, dass eine Debug-Version erstellt wurde, da ansonsten die kompletten Debuginformationen nicht verfügbar sind. Daher ist es auch nicht möglich, dass der Debugger an einem Haltepunkt automatisch stehen bleibt. Möchten wir jetzt anschließend unsere ASP.NET-Anwendung debuggen, brauchen wir nur die Taste (F5) zu drücken. Dadurch werden die Anwendung und der Debugger gestartet. Um jetzt z.B. die Ereignisbehandlungsfunktion für das Click-Ereignis zu debuggen, müssen wir einen Haltepunkt an die entsprechende Position setzen. Dazu setzten wir den Cursor in die entsprechende Zeile und drücken die Taste (F9). Dadurch wird die Zeile dunkelrot markiert:
Abbildung 2.45: Setzen eines Haltepunktes in Visual Studio .NET
Wenn wir jetzt nochmals die Anwendung starten, unseren Namen in die TextBox eingeben und anschließend auf den Button klicken, bleibt der Debugger anschließend in der betreffenden Zeile stehen (siehe Abbildung 2.46). Wenn der Debugger an der betreffenden Zeile stehen bleibt, wird diese anschließend gelb hervorgehoben.
Sandini Bib 114
2
Einführung in Visual Studio .NET
Abbildung 2.46: Der Debugger ist am Haltepunkt angekommen
Nachdem wir jetzt an unserem Haltepunkt stehen geblieben sind, stellt uns der Debugger verschiedene Fenster und Befehle zur Verfügung, mit denen wir Fehler suchen und beheben können. Die wichtigsten Fenster sind: 왘 Lokale Variablen 왘 Überwachung 왘 Call Stack 왘 Haltepunkte 왘 Geladene Module 왘 Speicher 왘 Register 왘 Disassembly Wenn diese Fenster standardmäßig nicht angezeigt werden, können sie über das Menü DEBUG/WINDOWS geöffnet werden. Nachfolgend möchte ich diese Fenster näher beschreiben.
Lokale Variablen Über dieses Fenster kann man sich die lokalen Variablen der aktuellen Funktion ansehen.
Abbildung 2.47: Die lokalen Variablen unserer Funktion, die gerade debuggt wird
Sandini Bib Debuggen
115
Überwachung Über dieses Fenster kann man Ausdrücke eingeben, die während des Debuggens überwacht werden sollen. Hier könnten wir z.B. den Ausdruck lblOutput.Text eingeben, um den Inhalt des Label-Steuerelements überwachen zu können. Würden wir die Funktion debuggen, würde man anschließend genau sehen können, wie sich der Inhalt verändern würde.
Abbildung 2.48: Das Überwachungsfenster für benutzerdefinierte Ausdrücke
Call Stack Über dieses Fenster kann man sich ganz genau ansehen, in welcher Reihenfolge welche Funktionen ausgeführt wurden, bis der aktuelle Haltepunkt erreicht wurde. Wenn man sich näher mit den internen Vorgängen der CLR (Common Language Runtime) beschäftigen möchte, kann man anhand dieses Fensters ganz genau feststellen, wann welche Funktion aufgerufen wird.
Abbildung 2.49: Der Call Stack unserer ASP.NET-Anwendung
Haltepunkte Über dieses Fenster kann man sich alle Haltepunkte ansehen, die innerhalb der Anwendung durch den Programmierer definiert wurden. Außerdem gibt es in diesem Fenster auch noch die Möglichkeit, dass man weitere Haltepunkte hinzufügt. Dabei kann man den Haltepunkt sehr fein definieren, da man sogar Bedingungen festlegen kann, die eintreffen müssen, damit der Haltepunkt gesetzt wird. Nähere Informationen darüber findet man wieder in der Onlinehilfe.
Sandini Bib 116
2
Einführung in Visual Studio .NET
Abbildung 2.50: Die definierten Haltepunkte in unserer ASP.NET-Anwendung
Geladene Module Um genau feststellen zu können, welche Module geladen wurden, kann man dieses Fenster verwenden. In diesem Fenster werden der Name, die Adresse, der Pfad, die Version und weitere Informationen über das geladene Modul angezeigt.
Abbildung 2.51: Anzeige der Module, die durch die ASP.NET-Anwendung automatisch geladen wurden
Speicher In Visual Studio .NET gibt es jetzt sogar die Möglichkeit, dass man sich den Speicherbereich ansehen kann, in den eine ASP.NET-Anwendung geladen wurde. Dadurch kann man sich genau ansehen, wie sich der entsprechende Speicherbereich zusammensetzt.
Abbildung 2.52: Anzeigen des Speicherbereichs einer ASP.NET-Anwendung
Register Ein weiteres nützliches Feature ist das Fenster REGISTER. Hier kann man sich die Werte ansehen, die sich in den entsprechenden Registern des Prozessors befinden.
Sandini Bib Debuggen
117
Abbildung 2.53: Anzeigen der Register des Prozessors
Disassembly Ein weiteres Feature, das sich viele Programmierer vielleicht schon länger gewünscht haben, ist, dass man die Disassembly des aktuellen ASP.NET-Programms ansehen kann, das gerade debugged wird. Dadurch kann man ganz genau analysieren, welche Assemblerbefehle durchgeführt werden.
Abbildung 2.54: Anzeigen der Disassembly
In der vorherigen Abbildung hat man z.B. die Disassembly des Get-Accessors der Eigenschaft Text des TextBox-Steuerelements gesehen. Nachdem wir uns jetzt die wichtigsten Fenster des Debuggers angesehen haben, möchte ich noch erklären, wie man den Debugger einsetzen kann. Um jetzt z.B. in unserer Funktion jede Zeile einzeln ausführen zu können, brauchen wir nur die Taste (F11) zu drücken. Dadurch wird Zeile für Zeile im Debugger ausgeführt.
Sandini Bib 118
2
Einführung in Visual Studio .NET
Über die verschiedenen Fenster können wir des weiteren auch noch genau nachvollziehen, was in jeder Zeile genau passiert ist, bzw. warum eine Zeile nicht so funktioniert, wie sie funktionieren sollte. Weitere nützliche Funktionen findet man, wenn man im Debugger die rechte Maustaste drückt. Hier bekommt man das folgende Menü zu sehen:
Abbildung 2.55: Die wichtigsten Befehle des Debuggers
Nachdem wir in diesem Beispiel gesehen haben, wie wir eine ASP.NET-Anwendung debuggen können, möchte ich im folgenden Beispiel noch zeigen, wie man eine Komponente debugged, die in einer ASP.NET-Seite aufgerufen wird.
2.5.2 Debuggen von Komponenten Wenn man in ASP z.B. auf eine Komponente zugegriffen hat, die in Visual Basic oder in Visual C++ programmiert wurde, konnte man leider nicht direkt vom ASP-Code aus in die Komponente debuggen. Unter Visual Studio .NET und dem .NET Framework sieht es aber ganz anders aus.
Sandini Bib Debuggen
119
Hier besteht die Möglichkeit, dass man direkt aus einer ASP.NET-Seite in eine Komponente debuggt. Dabei braucht die Komponente nicht einmal in derselben Sprache geschrieben zu sein wie der eigentliche ASP.NET-Code. Hier sieht man die enormen Vorteile der Common Language Runtime. Für dieses Beispiel habe ich wieder eine ASP.NET-Anwendung mit dem Namen ComponentDebugging erstellt. Anschließend habe ich zur Projektmappe noch eine Klassenbibliothek mit dem Namen MyComponent hinzugefügt. Damit man die Komponente in der ASP.NET-Anwendung verwenden kann, muss innerhalb der Anwendung noch ein Verweis auf die Komponente gesetzt werden. Wenn alle Arbeiten durchgeführt wurden, sieht die Projektmappe wie in der folgenden Abbildung aus:
Abbildung 2.56: Die Projektmappe, nachdem alle notwendigen Projekte und Verweise erstellt wurden
In der Komponente habe ich anschließend eine Funktion mit dem Namen GetHelloWorld hinzugefügt, die uns den String Hello World mit dem aktuellen Datum zurückliefert. Der Klasse selbst habe ich den Namen HelloWorld gegeben. Damit sieht der Code der Klasse wie folgt aus: using System; namespace MyComponent { public class HelloWorld { public String GetHelloWorld() { return "Hello World " + DateTime.Now; } } } Listing 2.1: Unsere Komponente HelloWorld
Sandini Bib 120
2
Einführung in Visual Studio .NET
Nachdem wir die Klassenbibliothek erfolgreich erstellt haben, können wir sie in unserer ASP.NET-Anwendung einsetzen. Zuerst müssen wir den Namespace der Komponente in unsere ASP.NET-Seite einbinden, damit wir die Klasse HelloWorld verwenden können. Dazu verwenden wir das using-Schlüsselwort, indem wir die folgende Zeile nach dem letzten using auf der ASP.NET-Seite einbinden: using MyComponent;
Dadurch sind jetzt alle Klassen dieses Namespaces in unserer Anwendung verfügbar. In der Funktion Page_Load brauchen wir jetzt nur noch die folgenden beiden Zeilen einzubinden, die unsere Komponente aufrufen. private void Page_Load(object sender, System.EventArgs e) { HelloWorld myWorld = new HelloWorld(); lblOutput.Text = myWorld.GetHelloWorld(); }
Zusätzlich habe ich auf der WebForm noch ein Label-Steuerelement mit der ID lblOutput definiert, in dem der Text angezeigt wird, der uns von der Komponente zurückgeliefert wird. Wenn wir die Anwendung mit (F5) starten, bekommen wir die folgende Ausgabe:
Abbildung 2.57: Die Verwendung unserer Komponente in einer ASP.NET-Anwendung
Um jetzt die Komponente zu debuggen, brauchen wir nur in der Zeile, die uns den String zurückliefert, einen Haltepunkt zu setzen (siehe Abbildung 2.58). Nachdem wir den Haltepunkt in der gewünschten Zeile gesetzt haben, brauchen wir nur nochmals die Anwendung zu starten, und anschließend hält der Debugger die Ausführung des Programms an der richtigen Stelle an, da ja unsere Komponente in der Funktion Page_Load aufgerufen wird (siehe Abbildung 2.59).
Sandini Bib Visual Database Tools
121
Abbildung 2.58: Setzen eines Haltepunktes in einer Komponente
Abbildung 2.59: Unser Haltepunkt in der Komponente wurde erreicht.
Wie wir anhand dieses Beispiels gesehen haben, ist das Debuggen von Komponenten unter Visual Studio .NET ganz einfach geworden, da man direkt von einer ASP.NETSeite in eine Komponente debuggen kann.
2.6 Visual Database Tools Nachdem wir uns im vorherigen Abschnitt näher mit dem Debugger von Visual Studio .NET beschäftigt haben, möchte ich in diesem Abschnitt näher auf die Visual Database Tools von Visual Studio .NET eingehen. Mithilfe dieser Werkzeuge ist es möglich, dass man Datenbankanwendungen innerhalb von Visual Studio .NET entwickeln kann, ohne dass man z.B. in den SQL Server Enterprise Manager oder in den Query Analyzer wechseln muss. Bevor wir uns näher mit den einzelnen Funktionen beschäftigen, möchte ich einen kurzen Überblick über die Visual Database Tools geben und erörtern, welche Funktionen überhaupt verfügbar sind und wie man sie einsetzen kann.
Sandini Bib 122
2
Einführung in Visual Studio .NET
Editionen Die Visual Database Tools sind in jeder Edition des Visual Studio .NET vorhanden. Allerdings gibt es einige Unterschiede bezüglich des Funktionsumfangs. In der folgenden Tabelle möchte ich die Unterschiede zwischen den verschiedenen Editionen veranschaulichen. Visual Studio .NET-Edition
Visual Database Tools-Features
Visual Basic .Net-Standard
– Anzeigen und Bearbeiten von Tabellen
Visual C#.NET-Standard
– Ausführen von gespeicherten Prozeduren auf der MSDE (Microsoft SQL Server Destop Engine) und auf Access-Datenbanken
Visual C++.NET-Standard Visual Studio .NET-Professional
– Anzeigen und Bearbeiten von Tabellen – Ausführen von gespeicherten Prozeduren auf jeder beliebigen Datenbank, die sie unterstützt – Erstellen von Tabellen und Sichten mit der MSDE
Visual Studio .NET EnterpriseDeveloper Visual Studio .NET EnterpriseArchitect
– Anzeigen und Bearbeiten von Tabellen – Ausführen von gespeicherten Prozeduren auf jeder beliebigen Datenbank, die sie unterstützt – Erstellen von Tabellen, Sichten, gespeicherten Prozeduren, Triggern und Funktionen auf der MSDE, dem SQL Server und Oracle
Tabelle 2.2: Die Features der Visual Database Tools in den verschiedenen Editionen von Visual Studio .NET
Nachdem wir jetzt die Unterschiede in den einzelnen Versionen kennen, möchte ich auf den folgenden Seiten näher auf die Werkzeuge eingehen, die die Visual Database Tools bilden. In diesem Abschnitt möchte ich wieder Schritt für Schritt zeigen, wie man eine einfache Datenbank mithilfe der Visual Database Tools entwerfen kann.
2.6.1 Datenbankdesigner Über den Datenbankdesigner ist es möglich, Datenbanken visuell zu modellieren. Wenn man mit dem Datenbankdesigner arbeitet, hat man die folgenden Möglichkeiten: 왘 Erstellen von Tabellen und Spalten 왘 Festlegen der Beziehungen zwischen den verschiedenen Tabellen 왘 Erstellen von Indizes und Einschränkungen
Sandini Bib Visual Database Tools
123
In diesem Beispiel möchte ich eine Datenbank entwickeln, die es ermöglicht, Personenund Adressdaten zu speichern. Die Datenbank soll dabei die folgenden Funktionen unterstützen: 왘 Unterscheidung zwischen natürlichen und juristischen Personen 왘 Speichern von mehreren Adressen für eine Person Im ersten Schritt habe ich im Server Explorer von Visual Studio .NET eine neue Datenbank angelegt. Dazu habe ich den Server Explorer geöffnet und den Menüpunkt CREATE NEW SQL SERVER DATABASE ausgewählt:
Abbildung 2.60: Erstellen einer neuen Datenbank
Im folgenden Dialogfenster braucht man dann nur noch den Servernamen und den Namen der Datenbank anzugeben. Wurde die Datenbank erfolgreich erstellt, wird sie bei den Datenbankverbindungen des Server Explorers hinzugefügt. Anschließend habe ich ein neues Datenbankprojekt mit dem Namen PersonData angelegt. Hier habe ich dann bei der Datenbankverbindung die zuvor angelegte Verbindung zur Datenbank PersonData ausgewählt. Nachdem diese Schritte durchgeführt sind, sind alle Vorkehrungen getroffen, damit wir mit dem Design der Datenbank beginnen können. An dieser Stelle möchte ich zeigen, wie man Tabellen mit dem Datenbankdesigner erstellen kann. Später möchte ich aber auch noch zeigen, wie Tabellen mit dem Tabellendesigner erstellt werden können. Um den Datenbankdesigner verwenden zu können, müssen wir ein Datenbankdiagramm anlegen. Dazu klicken wir im Server Explorer unter DATABASE DIAGRAMS auf NEW DIAGRAM (siehe Abbildung 2.61). Im nachfolgenden Dialogfenster können wir bereits vorhandene Tabellen zum Datenbankdiagramm hinzufügen. Da wir aber noch keine Tabellen erstellt haben, können wir hier auf SCHLIEßEN drücken. Anschließend können wir über das Menü DIAGRAM/ NEW TABLE eine neue Tabelle hinzufügen. Dieser Menüpunkt ist auch über die rechte Maustaste erreichbar. Den Namen der neuen Tabelle habe ich auf Person festgelegt.
Sandini Bib 124
2
Einführung in Visual Studio .NET
Abbildung 2.61: Erstellen eines neuen Datenbankdiagramms
Bei der Tabelle Person habe ich die folgenden beiden Spalten hinzugefügt: 왘 PersonID: Primärschlüssel der Tabelle 왘 MatchCode: Der MatchCode der Person Im Datenbankdesigner sieht anschließend die Tabelle Person wie folgt aus:
Abbildung 2.62: Die Tabelle Person im Datenbankdesigner
Wie wir aus der Abbildung sehen, habe ich die entsprechenden Datentypen für die einzelnen Spalten festgelegt und die Spalte PersonID zum Primärschlüssel der Tabelle gemacht. Das Einzige, was man jetzt noch festlegen muss, ist, dass die Spalte PersonID eine Identität ist. Dadurch wird diese Spalte mit einer fortlaufenden, eindeutigen Nummer gefüllt, über die der Datensatz eindeutig identifizierbar ist. Dazu habe ich über der Tabelle die rechte Maustaste gedrückt und die Eigenschaftenseite aufgerufen. Im Dialogfenster habe ich auf die Registerkarte Spalten gewechselt und die Spalte PersonID ausgewählt. Anschließend habe ich bei der Combobox Identity den Eintrag Yes ausgewählt. Dadurch wird die Spalte zu einer Identität:.
Sandini Bib Visual Database Tools
125
Abbildung 2.63: Festlegen der Identität für die Spalte PersonID
Anhand der Tabelle Person haben wir jetzt gesehen, wie man im Datenbankdesigner eine Tabelle anlegen und deren Eigenschaften bearbeiten kann. Im Folgenden möchte ich zeigen, wie der Tabellendesigner für die gleichen Aufgaben verwendet werden kann.
2.6.2 Tabellendesigner Nachdem wir im vorherigen Abschnitt die Tabelle Person mithilfe des Datenbankdesigners erstellt haben, möchte ich jetzt die beiden Tabellen NaturalPerson und JuristicPerson mithilfe des Tabellendesigners erstellen. Um den Tabellendesigner zu starten, geht man in den Server Explorer, markiert unter der Datenbank Person den Eintrag Tables und klickt mit der rechten Maustaste. Anschließend braucht man nur noch den Menüpunkt NEW TABLE auszuwählen.
Sandini Bib 126
2
Einführung in Visual Studio .NET
Abbildung 2.64: Starten des Tabellendesigners
Wenn der Tabellendesigner angezeigt wird, kann man wie gewohnt die Struktur der Tabelle festlegen. Die Tabelle NaturalPerson hat dabei den folgenden Aufbau:
Abbildung 2.65: Aufbau der Tabelle NaturalPerson
Die Spalte NaturalPersonID ist der Primärschlüssel der Tabelle und zugleich die Identität. Juristische Personen, wie z.B. Firmen, werden in der Tabelle JuristicPerson gespeichert, die nachfolgend dargestellt wird.
Abbildung 2.66: Aufbau der Tabelle JuristicPerson
Sandini Bib Visual Database Tools
127
Hier gilt für die Spalte JuristicPersonID das Gleiche wie für die Spalte NaturalPersonID der Tabelle NaturalPerson. Zum Schluss brauchen wir jetzt noch eine Tabelle, in der die Adressen unserer Personen gespeichert werden. Dazu habe ich die folgende Tabelle mit dem Namen Address angelegt:
Abbildung 2.67: Aufbau der Tabelle Address
In dieser Tabelle übernimmt die Spalte AddressID die gleichen Aufgaben und Funktionen wie die Spalte NaturalPersonID und JuristicPersonID. Sie muss daher als Primärschlüssel und als Identität festgelegt werden. Um jetzt die Beziehungen zwischen unseren Tabellen festlegen zu können, müssen wir nun noch einmal in unser Datenbankdiagramm wechseln. Wenn wir uns im Datenbankdiagramm befinden, müssen wir im ersten Schritt die neu angelegten Tabellen hinzufügen. Dazu klicken wir mit der rechten Maustaste und wählen den Menüpunkt Add Table aus. Im nachfolgenden Dialogfenster können wir die drei Tabellen Address, NaturalPerson und JuristicPerson zu unserem Diagramm hinzufügen. Um jetzt eine Beziehung zwischen den Tabellen Person und NaturalPerson festzulegen, klicken wir in der Tabelle Person auf die Spalte PersonID und ziehen den Mauscursor auf die Spalte PersonID in der Tabelle NaturalPerson. Danach erscheint das in Abbildung 2.68 gezeigte Dialogfenster, wo wir die Beziehung festlegen und bearbeiten können. Wenn wir in diesem Dialogfenster auf OK klicken, wird die Beziehung zwischen den beiden Tabellen erstellt und mit einer Linie im Datenbankdiagramm angezeigt. Nachdem wir jetzt wissen, wie man eine Beziehung zwischen zwei Tabellen erstellt, müssen anschließend noch die folgenden Beziehungen erstellt werden: 왘 Beziehung zwischen der Tabelle Person/PersonID und der Tabelle JuristicPerson/ PersonID 왘 Beziehung zwischen der Tabelle Person/PersonID und der Tabelle Address/PersonID
Sandini Bib 128
2
Einführung in Visual Studio .NET
Abbildung 2.68: Erstellung einer Beziehung zwischen zwei Tabellen
Nachdem alle notwendigen Beziehungen zwischen den Tabellen erstellt wurden, sieht unser Datenbankdiagramm wie folgt aus:
Abbildung 2.69: Unser fertiges Datenbankdiagramm
Sandini Bib Visual Database Tools
129
Nachdem wir unser Datenbankdiagramm erstellt haben, sind wir mit dem Design der Datenbank fertig. Im nächsten Schritt werden wir jetzt sehen, wie man mit den Visual Database Tools Abfragen, Sichten und gespeicherte Prozeduren entwickeln kann.
2.6.3 Abfrage- und Sichtendesigner Um auf unsere fertige Datenbank zugreifen zu können, brauchen wir natürlich verschiedene Abfragen oder Sichten. Außerdem ist es sehr sinnvoll, wenn der komplette Datenbankzugriff (Änderungen, Hinzufügungen) über gespeicherte Prozeduren abgewickelt wird. In diesem Abschnitt möchte ich jetzt zeigen, wie wir unsere Datenbank mit diesen verschiedenen Elementen ausbauen und erweitern können. Sehen wir uns im ersten Schritt die Abfragen an.
Abfragen Abfragen werden verwendet, wenn man Daten aus einer Datenbank anzeigen möchte. Dabei können Abfragen ganz leicht sein oder auch sehr kompliziert werden, wenn sich die Daten über mehrere Tabellen erstrecken, die miteinander verknüpft sind. Zum Glück gibt es bei den Visual Database Tools verschiedene Assistenten, die uns die Arbeit erleichtern und sie vereinfachen können. Wenn wir z.B. in unserem Datenbankprojekt eine Abfrage hinzufügen möchten, brauchen wir im Projektexplorer nur auf den Eintrag Queries zu wechseln, mit der rechten Maustaste zu klicken und anschließend den Punkt ADD NEW ITEM auszuwählen.
Abbildung 2.70: Hinzufügen einer Abfrage zu unserem Datenbankprojekt
Sandini Bib 130
2
Einführung in Visual Studio .NET
Wenn wir diesen Menüpunkt ausgewählt haben, brauchen wir im nachfolgenden Dialogfenster nur den Eintrag Database Query auszuwählen, damit eine neue Abfrage zu unserem Projekt hinzugefügt wird. Ich habe der neuen Abfrage den Namen GetAllNaturalPersons gegeben. Danach wird der Abfrageassistent gestartet. Im ersten Dialogfenster können wir die Tabellen zur Abfrage hinzufügen, die wir benötigen. Dabei habe ich die folgenden beiden Tabellen hinzugefügt: 왘 Person 왘 NaturalPerson Wenn wir uns den Abfrageassistenten näher ansehen, wird man feststellen, dass er automatisch die Beziehungen zwischen den beiden Tabellen erkennt und entsprechend anzeigt. Jetzt müssen wir dem Abfrageassistenten nur noch sagen, welche Spalten angezeigt werden sollen. Ich habe die folgenden Spalten ausgewählt: 왘 Salutation 왘 Title 왘 Birthday 왘 Birthplace 왘 MatchCode Natürlich möchten wir auch den Namen der Person wissen. Hier habe ich dem Abfrageassistenten gesagt, dass ich den FirstName und den LastName der Person in einer Spalte zurückbekommen möchte. Dazu habe ich folgenden Ausdruck in die Spalte Column eingegeben:
Abbildung 2.71: Hinzufügen eines benutzerdefinierten Ausdrucks zur Abfrage
Dadurch wird mir in der Spalte Name der Vorname und der Nachname der betreffenden Person zurückgegeben. Außerdem werden die beiden Namensteile durch ein Leerzeichen voneinander getrennt.
Sandini Bib Visual Database Tools
131
Der Abfrageassistent hat automatisch die folgende SQL-Abfrage generiert: SELECT NaturalPerson.Salutation, NaturalPerson.Title, NaturalPerson.FirstName + N' ‚ + NaturalPerson.FirstName AS Name, NaturalPerson.Birthday, NaturalPerson.Birthplace, Person.MatchCode FROM Person INNER JOIN NaturalPerson ON Person.PersonID = NaturalPerson.PersonID
Wenn wir anschließend die Abfrage öffnen bzw. ausführen, bekommt man die folgenden Daten zurückgeliefert:
Abbildung 2.72: Das Ergebnis unserer Abfrage
An diesem Beispiel haben wir gesehen, wie leicht es mithilfe der Visual Database Tools ist, eine Abfrage zu erstellen und an seine Bedürfnisse anzupassen, ohne dass man sich mit Verknüpfungen und SQL-Statements herumquälen muss.
Sichten (Views) Anstelle von Abfragen können wir auch mithilfe von Sichten Daten aus einer Datenbank abfragen. Jetzt wird sich vielleicht der eine oder andere fragen, warum es dann Sichten und Abfragen gibt. Der Unterschied ist leicht erklärt: Sichten kann man sich als eine Art Tabelle vorstellen. Man kann daher z.B. eine Sicht erstellen, die alle natürlichen Personen zurückliefert. Diese Sicht kann aber dann im Gegensatz zu Abfragen in anderen Abfragen wie eine Tabelle verwendet werden. Wie das genau geht, möchte ich anhand eines Beispiels zeigen. In diesem Beispiel erstelle ich zuerst eine Sicht, die mir alle juristischen Personen zurückliefert. Danach wird noch eine Abfrage erstellt, die diese Sicht verwendet, um noch alle Adressen der juristischen Personen zu ermitteln. Um eine Sicht mit den Visual Database Tools zu erstellen, markieren wir im Projektexplorer wieder den Eintrag Queries und klicken mit der rechten Maustaste. Im darauffolgenden Menü wählen wir wieder den Eintrag ADD NEW ITEM aus. Anschließend habe ich im Dialogfenster den Eintrag View Script markiert, der eine Vorlage für unsere Sicht erstellt. Die Sicht hat den Namen JuristicPersons bekommen.
Sandini Bib 132
2
Einführung in Visual Studio .NET
Um jetzt das SQL-Statement zu erzeugen, das alle juristischen Personen zurückliefert, habe ich wieder mit der rechten Maustaste geklickt und den Menüeintrag INSERT SQL ausgewählt. Dadurch startet wieder der Abfrageassistent, mit dem wir jetzt unsere Abfrage erstellen können. Zur Abfrage habe ich die beiden Tabellen Person und JuristicPerson hinzugefügt. Dabei habe ich das SQL-Statement wie folgt zusammengestellt:
Abbildung 2.73: Erstellen der Abfrage für die Sicht
Dadurch wurde wieder automatisch das folgende SQL-Statement generiert: SELECT JuristicPerson.CorporateName, JuristicPerson.CorporateStructure, JuristicPerson.FoundationDate, Person.MatchCode FROM Person INNER JOIN JuristicPerson ON Person.PersonID = JuristicPerson.PersonID
Zum generierten SQL Statement habe ich dann noch manuell die Spalte PersonID hinzugefügt. Jetzt können wir den Abfrageassistenten wieder schließen. Wenn wir uns den Code unserer Sicht ansehen, werden wir feststellen, dass das SQL-Statement automatisch übernommen wurde. Jetzt müssen wir unserer Sicht nur noch den Namen JuristicPersons geben. Dazu ersetzen wir alle Vorkommen von View_Name durch JuristicPersons innerhalb der Datei. Wenn wir jetzt im Projektexplorer mit der rechten Maustaste auf die Sicht klicken und anschließend den Menüpunkt RUN auswählen, wird die Sicht in der Datenbank erstellt, was wir danach auch im Server Explorer überprüfen können. Wenn ich die Sicht auf meinem Computer öffne, bekomme ich die folgenden Daten zurückgeliefert:
Abbildung 2.74: Anzeigen unserer erstellten Sicht
Sandini Bib Visual Database Tools
133
Die soeben erstellte Sicht können wir jetzt anschließend in einer Abfrage wiederverwenden. Dazu erstellen wir wieder wie zuvor eine Abfrage und geben ihr den Namen GetJuristicPersonsWithAddresses. Anschließend brauchen wir zur Abfrage nur unsere Sicht und die Tabelle Address hinzuzufügen. Da zwischen der Tabelle und der Sicht keine Beziehung besteht, müssen wir diese jetzt manuell im Abfrageassistenten festlegen, indem wir die Spalte PersonID unserer Sicht auf die Spalte PersonID der Tabelle Address ziehen. Dadurch wird automatisch ein INNER JOIN zwischen den beiden Objekten erzeugt:
Abbildung 2.75: Festlegen der Beziehung zwischen einer Sicht und einer Tabelle
Danach habe ich von der Sicht und von der Adresstabelle alle Spalten zur Abfrage hinzugefügt, bis auf die IDs. Die automatisch generierte Abfrage sieht wie folgt aus: SELECT JuristicPersons.CorporateName, JuristicPersons.CorporateStructure, JuristicPersons.FoundationDate, JuristicPersons.MatchCode, Address.Street, Address.ZipCode, Address.City, Address.Country FROM Address INNER JOIN JuristicPersons ON Address.PersonID = JuristicPersons.PersonID
Wenn ich anschließend die Abfrage ausführe, bekomme ich die folgenden Daten zurückgeliefert:
Abbildung 2.76: Die Ergebnisse unserer Abfrage
Sandini Bib 134
2
Einführung in Visual Studio .NET
Nachdem wir jetzt gesehen haben, wie man Daten abfragen kann, wird es an der Zeit, dass wir erfahren, wie wir Daten in die Datenbank bekommen. Dazu schauen wir uns jetzt die gespeicherten Prozeduren an.
Gespeicherte Prozeduren (stored Procedures) Wenn man auf eine Datenbank zugreift, sollte man immer, wenn es geht, gespeicherte Prozeduren verwenden. Gespeicherte Prozeduren sind um einiges schneller und sicherer als normale SQL-Statements. Außerdem wird der komplette SQL-Code vom übrigen C#-Code isoliert. Dadurch werden die Wartung und die Aktualisierung von Datenbankanwendungen um einiges vereinfacht und erleichtert. Gespeicherte Prozeduren kann man für alle Aufgaben verwenden. Meistens werden die folgenden Tätigkeiten mithilfe von gespeicherten Prozeduren durchgeführt: 왘 Abfragen von Daten 왘 Einfügen von Daten 왘 Aktualisieren von Daten Da wir unsere Datenbank bereits im vorherigen Abschnitt abgefragt haben, möchte ich jetzt zeigen, wie wir unsere Datenbank befüllen können. Wie man sich fast denken kann, wird dabei der Zugriff über gespeicherte Prozeduren abgewickelt. Um eine gespeicherte Prozedur zu schreiben, müssen wir wieder ein neues Objekt zu unserem Datenbankprojekt hinzufügen, nämlich ein Stored Procedure Script. Dazu gehen wir genauso vor, als wenn wir eine Abfrage erstellen würden. Der einzige Unterschied ist, dass wir im Dialogfenster den Eintrag Stored Procedure Script auswählen und anschließend auf OPEN klicken. Ich habe dieser gespeicherten Prozedur den Namen sp_insNaturalPerson gegeben. Anhand dieses Namens sieht man schon, dass es für gespeicherte Prozeduren eine Namenskonvention gibt, die man auch einhalten sollte. In allen Projekten von mir beginnen die gespeicherten Prozeduren mit dem Präfix sp_. Liefert mir die Prozedur Daten zurück, kommen anschließend die Buchstaben sel, gefolgt vom eigentlichen Namen der Prozedur, der großgeschrieben wird. Werden mit der Prozedur Daten in die Datenbank eingefügt, verwende ich die Buchstaben ins, und bei einem Update werden die Buchstaben up verwendet. Beispiele für Namen von gespeicherten Prozeduren sind daher wie folgt: 왘 sp_selNaturalPerson 왘 sp_insNaturalPerson 왘 sp_upNaturalPerson
Sandini Bib Visual Database Tools
135
Nachdem wir unsere gespeicherte Prozedur erstellt haben, müssen wir im ersten Schritt wieder den Namen der Prozedur im SQL-Script austauschen. Dazu muss der Text Stored_Procedure_Name durch den Namen der Prozedur, nämlich sp_insNaturalPerson, ersetzt werden. Um mit dem Abfrageassistenten die gespeicherte Prozedur entwickeln zu können, brauchen wir nur auf die rechte Maustaste zu klicken, und anschließend den Punkt INSERT SQL auszuwählen. Dadurch kommen wir wieder in den Abfrageassistenten der Visual Database Tools. Jetzt müssen wir den Typ der Abfrage auf eine Einfügeabfage umändern. Dadurch wird es uns möglich gemacht, dass wir Daten in die Datenbank einfügen können. Dazu wählen wir den Menüpunkt QUERY/CHANGE TYPE/INSERT VALUES aus. Anschließend müssen wir die Zieltabelle zur Einfügeabfrage hinzufügen. Dazu wählen wir die Tabelle Person aus. Da wir die gespeicherte Prozedur variabel gestalten wollen, verwenden wir Parameter. Über Parameter kann festgelegt werden, welche Daten in die Datenbank eingefügt werden sollen. In der SQL-Syntax werden Parameter mit dem Zeichen @ gekennzeichnet. Daher müssen wir dem Abfrageassistenten mitteilen, dass das Zeichen @ für Parameter verwendet wird. Dazu klicken wir mit der rechten Maustaste und wählen den Punkt PROPERTY PAGES aus. Im nachfolgenden Dialogfenster wechseln wir auf die Registerkarte PARAMETERS und geben im Textfeld PREFIX CHARACTERS das Zeichen @ ein:
Abbildung 2.77: Festlegen des Prefixes für gespeicherte Prozeduren
Sandini Bib 136
2
Einführung in Visual Studio .NET
Anschließend können wir die Spalte MatchCode markieren, damit sie in die Einfügeabfrage aufgenommen wird. Daher erscheint sie auch in der Spalte Column. In der Spalte New Value geben wir anschließend die Zeichenfolge @MatchCode ein. Diese Zeichenfolge bezieht sich auf den Parameter @MatchCode, den wir anschließend noch anlegen müssen. Der Abfrageassistent hat uns somit das folgende SQL-Statement generiert: INSERT INTO Person (MatchCode) VALUES (@MatchCode)
Wenn wir jetzt die gespeicherte Prozedur ausführen möchten, wählen wir aus dem Menü QUERY den Menüpunkt RUN aus. Dadurch erscheint ein Dialogfenster, in dem wir den Parameter @MatchCode eingeben können:
Abbildung 2.78: Angeben der Parameter der gespeicherten Prozedur
Der Wert, den wir in dieses Dialogfenster eingeben, wird anschließend in die Spalte MatchCode der Tabelle Person eingefügt. Wenn wir die gespeicherte Prozedur auf den SQL Server kopieren möchten, was eigentlich immer der Fall sein wird, müssen wir den Parameter noch im SQL-Code der gespeicherten Prozedur anlegen. Dazu fügen wir die folgende Zeile nach CREATE Procedure sp_insNaturalPerson
ein: @MatchCode nvarchar(50)
Mit dieser Zeile werden der Name und der Datentyp des Parameters festgelegt. Jetzt können wir unsere Prozedur noch so weit erweitern, dass automatisch ein Datensatz in die Tabelle Person und anschließend in die Tabelle NaturalPerson eingefügt wird.
Sandini Bib Features der Enterprise Architect Edition
137
Dadurch kann mit nur einer gespeicherten Prozedur eine natürliche Person in die Datenbank eingefügt werden. Der komplette Code der Prozedur sieht wie folgt aus: CREATE Procedure sp_insNaturalPerson @MatchCode nvarchar(50), @Salutation nvarchar(50), @Title nvarchar(50), @FirstName nvarchar(50), @LastName nvarchar(50), @Birthday datetime, @Birthplace nvarchar(50) AS DECLARE @PersonID int BEGIN TRAN INSERT INTO Person(MatchCode) VALUES (@MatchCode) SET @PersonID = @IDENTITY INSERT INTO NaturalPerson(PersonID, Salutation, Title, FirstName, LastName, Birthday, Birthplace) VALUES (@PersonID, @Salutation, @Title, @FirstName, @LastName, @Birthday, @Birthplace) COMMIT TRAN Listing 2.2: Die komplette gespeicherte Prozedur
In diesem Abschnitt haben wir gesehen, wie man mithilfe der Visual Database Tools eine Datenbank entwerfen und modellieren kann. Außerdem haben wir auch gesehen, wie man leistungsfähige Abfragen, Sichten und gespeicherte Prozeduren mithilfe des Abfrageassistenten erstellen kann.
2.7 Features der Enterprise Architect Edition mithilfe der Enterprise Architect Edition von Visual Studio .NET wird es jetzt möglich gemacht, dass man Softwaresysteme visuell entwerfen und anschließend daraus Quellcode erzeugen kann. Möglich gemacht wird dies durch das Zusatzprodukt Microsoft Visio, mit dem es unter anderem möglich ist, Datenbankdiagramme und UMLModelle zu modellieren. In diesem Abschnitt möchte ich zeigen, wie man ein Klassensystem mit der UML (Unified Modelling Language) erstellen, und anschließend daraus einen C#-Code generie-
Sandini Bib 138
2
Einführung in Visual Studio .NET
ren kann. Im zweiten Teil werden wir dann sehen, wie man mithilfe von Visio eine Datenbank erstellen und diese anschließend auf einen beliebigen Server einrichten kann.
2.7.1 Klassendesign mit der UML In diesem Abschnitt möchte ich näher darauf eingehen, wie man eine Klassenbibliothek mithilfe der UML erstellen und in C#-Code umwandeln kann. Dabei möchte ich eine Klassenbibliothek entwerfen, die auf unserem Datenbankdesign aus dem letzten Abschnitt basiert. Insgesamt werden daher für unsere Bibliothek die folgenden Klassen benötigt: 왘 Person 왘 NaturalPerson 왘 JuristicPerson 왘 Address Wenn wir diese Klassen näher betrachten, werden wir feststellen können, dass die beiden Klassen JuristicPerson und NaturalPerson von der Basisklasse Person abgeleitet werden. Da wir jetzt den grundlegenden Aufbau unseres Klassensystems kennen, fangen wir mit dem Design an. Dazu habe ich Microsoft Visio gestartet, das sich unter Programme befindet. Wenn das Programm gestartet wurde, kommen wir zur Startseite, wo wir unsere gewünschte Diagrammart auswählen können. Hier habe ich auf die Registerkarte SOFTWARE gewechselt:
Abbildung 2.79: Die Startseite von Microsoft Visio
Sandini Bib Features der Enterprise Architect Edition
139
Hier habe ich dann den vorletzten Eintrag, UML Model Diagram, ausgewählt. Danach wird ein neues UML-Diagramm angelegt, in dem wir dann alle üblichen UML-Diagramme erstellen können. In diesem Abschnitt konzentrieren wir uns auf ein Klassendesign. Die Oberfläche des UML-Diagramms teilt sich in die folgenden Abschnitte ein: 왘 Auf der linken Seite befinden sich alle notwendigen Formen, die man für das Erstellen eines Diagramms benötigt. 왘 Darunter befindet sich ein Dokumentationsfenster, in das man die Dokumentation des entsprechenden Elements eingeben kann. 왘 In der Mitte des Bildschirms befindet sich die eigentliche Arbeitsoberfläche, auf der man das jeweilige UML-Diagramm erstellen kann. Die folgende Abbildung zeigt die Arbeitsoberfläche, wenn man mit einem UML-Diagramm arbeitet:
Abbildung 2.80: Die Arbeitsoberfläche, wenn man mit einem UML-Diagramm arbeitet
Sandini Bib 140
2
Einführung in Visual Studio .NET
Wie man aus der vorherigen Abbildung ersehen kann, habe ich bei den Formen bereits auf den Eintrag UML Static Structure gewechselt, damit ich ein Klassensystem erstellen kann. Hier gibt es die folgenden wichtigen Objekte, die in einem Diagramm verwendet werden können: 왘 Class: Stellt eine Klasse in einem Klassensystem dar. 왘 Data Type: Stellt einen Datentyp in einem Klassensystem dar. 왘 Interface: Mit dieser Form kann man eine Schnittstelle implementieren. 왘 Generalizing: Mit diesem Element kann man die Vererbungsbeziehungen zwischen den einzelnen Klassen festlegen. 왘 Exception: Ausnahmen können mithilfe dieses Elements modelliert werden. Es gibt noch sehr viele weitere Elemente, auf die ich aber hier nicht näher eingehen möchte. Nachdem wir jetzt die wichtigsten Elemente kennen gelernt haben, möchte ich damit beginnen, dass ich die Klasse Person modelliere. Dazu habe ich das Element Class auf die Arbeitsoberfläche gezogen und das Eigenschaftenfenster mit einem Doppelklick darauf aufgerufen:
Abbildung 2.81: Das Eigenschaftenfenster unserer Klasse Person
Sandini Bib Features der Enterprise Architect Edition
141
Hier habe ich den Namen der Klasse bereits auf Person geändert. In diesem Dialogfenster kann man jetzt das komplette Verhalten der Klasse inkl. der Dokumentation dazu festlegen. Auf der ersten Seite des Dialogfensters habe ich ansonsten keine Änderungen mehr durchgeführt, außer dass ich eine entsprechende Dokumentation eingegeben habe. Auf der zweiten Seite ATTRIBUTES des Dialogfensters kann man die Eigenschaften der Klasse festlegen und näher spezifizieren. Hier habe ich unsere zwei Eigenschaften PersonID und MatchCode angelegt.
Abbildung 2.82: Festlegen der Eigenschaften der Klasse Person
Nachdem ich die Eigenschaften der Klasse Person festgelegt habe, habe ich auf die Registerkarte OPERATIONS gewechselt, auf der man die Funktionen der Klasse spezifizieren kann. Hier habe ich die beiden virtuellen Funktionen Load und Save deklariert.
Abbildung 2.83: Deklaration der Funktionen der Klasse Person
Sandini Bib 142
2
Einführung in Visual Studio .NET
Wenn man anschließend die gewünschte Funktion markiert und auf den Button PROPERTIES... klickt, kommt man zur Eigenschaftenseite der Funktion. Hier kann man dann das genaue Verhalten der Funktion inkl. Dokumentation festlegen. Wichtig für Funktionen ist die Registerkarte PARAMETERS, auf der man die einzelnen Parameter der Funktion anlegen und bearbeiten kann:
Abbildung 2.84: Anlegen der Parameter einer Funktion
In dieser Registerkarte habe ich der Funktion Load den Parameter PersonID hinzugefügt, der die ID der Person angibt, die aus der Datenbank geladen werden soll. Nachdem wir jetzt die Eigenschaften und Funktionen unserer Klasse festgelegt haben, können wir auf die Registerkarte CODE GENERATION OPTIONS der eigentlichen Klasse wechseln. Hier kann man jetzt angeben, in welcher Programmiersprache und in welcher Quelltextdatei der Code der Klasse geschrieben werden soll. Außerdem kann man sich den Code der Klasse in einer Art Vorschau ansehen (siehe Abbildung 2.85). Wenn man anschließend auf den Button OK klickt, kommt man wieder in das UMLModell, wo man anschließend unsere neue Klasse Person auf der Arbeitsoberfläche mit allen Eigenschaften und Funktionen sieht (siehe Abbildung 2.86). Nachdem wir jetzt wissen, wie man eine Klasse mithilfe der UML modelliert, müssen wir jetzt noch die beiden Klassen NaturalPerson und JuristicPerson erstellen. Dabei haben beide Klassen wieder die zwei Funktionen Load und Save. Diesmal sind es aber keine virtuellen Funktionen.
Sandini Bib Features der Enterprise Architect Edition
Abbildung 2.85: Festlegen der Codegenerierungsoptionen
Abbildung 2.86: Unsere modellierte Klasse Person
Die Klasse NaturalPerson hat die folgenden Eigenschaften: 왘 NaturalPersonID: long 왘 Salutation: string 왘 Title: string 왘 LastName: string 왘 FirstName: string 왘 Birthday: date 왘 Birthplace: string Die Klasse JuristicPerson implementiert die folgenden Eigenschaften: 왘 JuristicPersonID: long 왘 CorporateName: string 왘 CorporateStructure: string 왘 FoundationDate: date
143
Sandini Bib 144
2
Einführung in Visual Studio .NET
Nachdem ich die weiteren Klassen erstellt habe, sieht unser Klassensystem wie folgt aus:
Abbildung 2.87: Unsere drei Klassen
Nachdem wir jetzt unsere drei Klassen erstellt haben, brauchen wir nur noch die Beziehungen bzw. die Vererbungen zwischen den Klassen festzulegen. Dazu dient das Element Generalisierung. Insgesamt brauchen wir für unsere Klassenbibliothek zwei Generalisierungen, eine für die Klasse NaturalPerson und eine für die Klasse JuristicPerson. Diese Generalisierungen habe ich wie folgt verwendet:
Abbildung 2.88: Unsere fertige Klassenbibliothek
Sandini Bib Features der Enterprise Architect Edition
145
Wie wir anhand der Grafik erkennen können, muss der Pfeil der Generalisierung immer zur Basisklasse zeigen. Dadurch werden die beiden Klassen NaturalPerson und JuristicPerson von der Basisklasse Person abgeleitet. Da wir jetzt unser UML-Modell mit unserer Klassenbibliothek fertig gestellt haben, brauchen wir uns daraus nur noch den Quellcode erzeugen zu lassen und können anschließend die Funktionen programmieren. Dazu wählen wir den Menüpunkt UML/CODE/GENERATE... aus. Dadurch wird das folgende Dialogfenster angezeigt, in dem wir verschiedene Einstellungen vornehmen können:
Abbildung 2.89: Erzeugen von Code aus dem UML-Modell
Wie man aus der Abbildung ersehen kann, kann man direkt ein Visual Studio .NET Projekt aus dem UML-Modell erstellen lassen. Dem Projekt habe ich den Namen PersonData gegeben. Danach habe ich noch in der linken Listbox die Klassen ausgewählt, für die Code erzeugt werden soll. Wenn wir anschließend auf OK klicken, wird im Zielpfad das gewünschte Projekt angelegt, das wir anschließend mit Visual Studio .NET öffnen können. In diesem Abschnitt haben wir jetzt gesehen, wie man mithilfe der UML Klassenbibliotheken entwerfen und anschließend in Code umwandeln kann. Dadurch kann man zuerst einmal ein Klassensystem modellieren und dieses genau spezifizieren, bevor man dann anschließend mit dem Programmieren der einzelnen Funktionen anfängt.
Sandini Bib 146
2
Einführung in Visual Studio .NET
2.7.2 Datenbankdesign mit Visio In diesem Abschnitt möchte ich kurz darauf eingehen, wie man eine Datenbank mithilfe von Microsoft Visio modellieren und erstellen kann. Wenn wir Visio starten, wählen wir beim Startbildschirm die Registerkarte DATABASE aus, aus der wir aus verschiedenen Datenbankmodellen das Modell DATABASE MODEL DIAGRAM auswählen:
Abbildung 2.90: Auswählen eines Datenbankdiagramms
Die wichtigsten zwei Formen, die man für ein Datenbankdesign benötigt, sind das Element Entity, das eine Tabelle darstellt, und das Element Relations, dass eine Beziehung zwischen zwei Tabellen darstellt. Um unsere erste Tabelle Person zu modellieren, habe ich ein Entity-Objekt auf die Arbeitsoberfläche gezogen und ihm den Namen Person gegeben:
Abbildung 2.91: Erstellen der Tabelle Person
Wenn man die Tabelle markiert, sieht man dann im unteren Bereich die verschiedenen Eigenschaften der Tabelle. Hier habe ich dann anschließend auf die Kategorie Columns gewechselt, wo ich die Spalten der Tabelle wie folgt festgelegt habe:
Sandini Bib Features der Enterprise Architect Edition
147
Abbildung 2.92: Festlegen der Spalten unserer Tabelle Person
Danach habe ich noch die anderen beiden Tabellen NaturalPerson und JuristicPerson erstellt. Das Datenbankmodell sieht daher anschließend wie folgt aus:
Abbildung 2.93: Unsere drei Tabellen
Jetzt müssen wir nur noch mit zwei Relations-Objekten die Beziehungen zwischen unseren beiden Tabellen definieren. Dazu habe ich die zwei Objekte auf die Arbeitsoberfläche gezogen und im unteren Bereich des Diagramms anschließend die richtigen Primärschlüssel- und Fremdschlüsselfelder ausgewählt:
Sandini Bib 148
2
Einführung in Visual Studio .NET
Abbildung 2.94: Erstellen einer Beziehung zwischen zwei Tabellen
Nachdem die notwendigen Beziehungen zwischen den Tabellen erstellt wurden, sieht unser Datenbankdiagramm wie folgt aus:
Abbildung 2.95: Unser fertiges Datenbankdiagramm
Nachdem jetzt unser Datenbankdiagramm fertig ist, können wir damit beginnen, daraus eine Datenbank zu erzeugen. Dazu habe ich den Menüpunkt DATABASE/GENERATE... ausgewählt. Im Dialogfenster, das angezeigt wird, habe ich den Dateinamen auf PersonData.ddl geändert, und die Checkbox GENERATE NEW DATABASE markiert. Dadurch wird aus dem Datenbankdiagramm eine neue Datenbank erstellt. Im nächsten Dialogfenster muss man den Namen der DSN und den Namen der Datenbank angeben. Der neuen Datenbank habe ich den Namen PersonData-Visio gegeben. Wenn man anschließend auf FINISH klickt, werden die Datenbank und die dazugehörigen Tabellen erzeugt. Anhand dieses Beispiels haben wir gesehen, wie man eine Datenbank mithilfe von Visio entwerfen und anschließend erzeugen kann.
Sandini Bib Zusammenfassung
149
2.8 Zusammenfassung Dieses Kapitel hat gezeigt, wie man das Visual Studio .NET sinnvoll einsetzen kann. Dabei haben wir gesehen, welche Projektarten es gibt und wie man sie einsetzt. Danach habe ich gezeigt, wie man das tradionelle Hello World-Programm mithilfe von Visual Studio .NET als ASP.NET-Anwendung realisieren kann. Ein sehr wichtiger Punkt bei der ASP.NET- und bei der ADO.NET-Programmierung ist die Datenbindung. Daher habe ich auch ein umfangreicheres Beispiel gezeigt, dass veranschaulicht hat, wie man fast ohne Programmierung eine Datenbanktabelle auslesen kann und mithilfe von ASP.NET-Steuerelementen auf einer WebForm anzeigen kann. Ein weiterer wichtiger Punkt bei der Programmierung ist das Debugging. Daher bin ich auch kurz auf die wichtigsten Neuerungen und Features des integrierten Debuggers von Visual Studio .NET eingegangen und habe gezeigt, wie man sehr schnell zu entsprechenden Ergebnissen kommen kann. Das Visual Studio .NET zielt darauf ab, dass damit ERP-Anwendungen (Enterprise Ressource Planning) erstellt werden. Solche Anwendungen basieren immer auf einer Datenbank. Daher sind auch in dieser Version des Visual Studios wieder die Visual Database Tools enthalten, mit denen man Datenbankanwendungen programmieren kann, ohne dass man die vertraute Entwicklungsumgebung verlassen muss. Die Produktpalette von Visual Studio .NET wurde um eine zusätzliche Edition erweitert: die Visual Studio .NET Enterprise Architect Edition. Mit dieser Edition ist es jetzt möglich geworden, dass man Softwaresysteme planen und modellieren kann. Unterstützt wird man dabei von Microsoft Visio, mit dem man UML-Modelle und Datenbankdiagramme erstellen kann. Der Clou dabei ist, dass man die erstellten Modelle in Quellcode konvertieren kann. Dadurch ergeben sich schnellere Entwicklungszyklen, da man die Software sozusagen visuell modellieren und planen kann.
Sandini Bib
Sandini Bib
3 Einführung in ASP.NET Da wir uns in den letzten beiden Kapiteln mit den Grundlagen von Microsoft .NET und von Visual Studio .NET beschäftigt haben, können wir uns jetzt ASP.NET näher ansehen. ASP.NET ist der Nachfolger von ASP 3.0. Dabei kann man aber ASP.NET nicht einfach als ein ASP 4.0 bezeichnen, da sich die Art der ASP-Programmierung in ASP.NET sehr stark verändert hat. Die Gründe, warum ASP.NET eingeführt wurde, wurden bereits in Kapitel 1 näher erläutert.
3.1 Entwurfsziele von ASP.NET Um zu verstehen, wie ASP.NET arbeitet, sehen wir uns in diesem Abschnitt die Entwurfsziele von ASP.NET näher an. Später in diesem Buch werden diese Entwurfsziele noch näher betrachtet und mit verschiedenen Beispielen veranschaulicht. Wichtige Ziele von ASP.NET sind: 왘 Programmieren von ASP.NET Seiten ohne die Notwendigkeit von Scriptsprachen 왘 ASP.NET Seiten sollen typsicher und kompilierbar sein. 왘 Verringern des Codes und der Zeit, die benötigt werden um ASP.NET-Anwendungen zu schreiben 왘 ASP.NET soll durch Drittanbieter erweiterbar sein, die ihre eigenen Controls implementieren. 왘 Einfaches Deployment von ASP.NET-Anwendungen 왘 ASP.NET ist der Nachfolger von ASP, daher kann man vorhandenen ASP Code mit nur wenigen Änderungen wiederverwenden. 왘 ASP.NET bietet eine große Unterstützung bezüglich Debugging und dem Schreiben von Code.
Sandini Bib 152
3
Einführung in ASP.NET
Keine Notwendigkeit von Scriptsprachen ASP wurde mit dem Hintergedanken programmiert, dass ASP-Programmierer für die Erstellung von Web-Anwendungen Scriptsprachen wie z.B. VB-Script oder JavaScript verwenden. Durch die Verwendung von Scriptsprachen ergaben sich aber mehrere Nachteile. Als Erstes sei erwähnt, dass Scriptsprachen nicht kompiliert werden, sondern auf dem Server nur interpretiert werden. Daher ergibt sich daraus ein enormer Geschwindigkeitsnachteil gegenüber kompiliertem Code. Eine weitere Schwachstelle von Scriptsprachen ist, dass diese nicht typsicher sind wie andere, kompilierte Sprachen. Fassen wir nochmals die Nachteile von Scriptsprachen zusammen: 왘 Code wird interpretiert und nicht kompiliert. 왘 Scriptsprachen sind nicht typsicher, es gibt nur Variants. 왘 Sie unterstützen nur die späte Bindung von Methoden. 왘 Jede Instanz einer Scripting Engine benötigt Speicher. Als ASP-Programmierer sind einem diese Probleme sicherlich im Laufe der Zeit bekannt geworden. Mit ASP.NET wurden all diese Probleme von Grund auf gelöst. Darum ist auch ASP.NET kein Nachfolger von ASP, weil ASP.NET von Grund auf neu konzipiert und programmiert wurde. ASP.NET wurde mithilfe von C# programmiert und bedient sich des .NET Frameworks.
Performance Um eine perfekte Performance zu erreichen und die Abhängigkeit von Scriptsprachen zu verhindern, bedient sich ASP.NET Assemblies. Was Assemblies genau sind, wurde bereits in der Einführung zu Microsoft .NET erklärt. Der grundlegende Aufrufprozess einer ASP.NET-Seite sieht folgendermaßen aus: 왘 Ein Client tippt eine URL in die Adressleiste seines Browsers ein. Diese Anfrage wird dann an die entsprechende ASP.NET-Seite auf dem gewünschten Webserver weitergeleitet. 왘 Die ASP.NET-Seite wird dann geparst und an den entsprechenden Compiler weitergeleitet. 왘 Der Compiler kompiliert die ASP.NET-Seite und erstellt daraus eine Assembly. 왘 Das .NET Framework erzeugt dann aus der Assembly das Klassenobjekt der ASP.NET-Seite. 왘 Das Klassenobjekt der ASP.NET-Seite wird ausgeführt und liefert den Inhalt der Seite an den Client zurück.
Sandini Bib Entwurfsziele von ASP.NET
153
Wenn eine Seite das erste Mal aufgerufen wird, kompiliert ASP.NET die entsprechende Seite in eine Assembly. Die Assembly bekommt dann einen eindeutigen Namen und wird in das folgende Verzeichnis kopiert: %systemroot%/Microsoft .NET/Framework/ v0.0.0.0/Temporary ASP.NET Files. Dabei steht %systemroot% für das Windows-Systemverzeichnis und v0.0.0.0 für die entsprechende Version des .NET Frameworks. Die erzeugte Assembly beinhaltet eine einzelne Klasse, die die ASP.NET-Seite repräsentiert. Diese Klasse ist von der Basisklasse System.Web.UI.Page abgeleitet. Diese Klasse beinhaltet den ganzen Code, um die entsprechende ASP.NET-Seite anzuzeigen, und um alle Zugriffe, die auf die ASP.NET Seite erfolgen, zu verarbeiten. Die Kompilierung der ASP.NET-Seite kann aber bei komplexen Seiten ein paar Sekunden dauern. Dennoch ist zu beachten, dass die Kompilierung nur beim ersten Aufruf einer Seite durchgeführt wird. Anschließend wird die Assembly aus dem Verzeichnis Temporary ASP.NET Files genommen, was die Ausführungsgeschwindigkeit sehr erhöht. Wenn jedoch der IIS neu gestartet wird, muss eine angeforderte ASP.NET-Seite wieder neu kompiliert werden. Wenn eine Klasse für eine ASP.NET-Seite kompiliert wird, werden auch alle Abhängigkeiten die bestehen (z.B. Include-Dateien) kompiliert. Diese Abhängigkeiten werden überprüft, bevor die Seite beim Client angezeigt wird. Wenn irgendwelche Abhängigkeiten geändert wurden, werden diese neu kompiliert, damit die ASP.NET-Seite immer aktuell angezeigt werden kann. Wenn die Abhängigkeiten nicht verändert wurden, werden diese wieder aus dem Cache geladen, damit eine hohe Ausführungsgeschwindigkeit gewährleistet werden kann.
Einfaches Deployment Um heute eine ASP Anwendung auf einen Webserver zu installieren, der sich im laufenden Betrieb befindet, sind einige Anstrengungen notwendig. Sehr kompliziert wird es, wenn die Anwendung außerdem noch aus COM-Komponenten besteht, die beim System registriert werden müssen. Außerdem müssen bei der Installation von ASPAnwendungen noch oft Daten in die Metabase des IIS eingetragen werden. Noch komplizierter wird es, wenn eine ASP-Anwendung auf einer Serverfarm installiert werden muss, da hier der ganze Vorgang auf allen Servern der Serverfarm durchgeführt werden muss. All diese Probleme machen einen Administrator große Kopfzerbrechen, da hier sehr oft der Webserver neu gestartet werden muss. Dem ist aber nicht so unter ASP.NET Das Deployment von ASP.NET-Anwendungenen ist sehr viel einfacher geworden. Um eine Anwendung zu installieren, sind folgende zwei Schritte notwendig: 왘 Erzeugen eines virtuellen Verzeichnisses 왘 Kopieren der Anwendungsdateien in das Verzeichnis
Sandini Bib 154
3
Einführung in ASP.NET
Das Entfernen einer Anwendung ist ebenfalls ganz einfach: 왘 Löschen des virtuellen Verzeichnisses 왘 Löschen der Anwendungsdateien Wie hier zu sehen ist, braucht man keine Komponenten mehr zu registrieren. Außerdem gibt es bei ASP.NET grundlegende Änderungen bei der Programmierung von Web-Anwendungen: 왘 Die Konfiguration einer Anwendung wird mithilfe von XML-Konfigurationsdateien durchgeführt. Es werden keine Konfigurationsdaten mehr in der Registrierung und in der Metabase des IIS gespeichert. Die eigenen Konfiguration kann man dann ebenfalls in diesen XML-Konfigurationsdateien speichern. Man braucht dazu dann z.B. auf kein Datenbanksystem mehr zurückzugreifen. 왘 Durch die Common Language Runtime müssen keine Komponenten mehr beim System registriert werden. Die Common Language Runtime sucht einfach die entsprechenden Komponenten im Bin-Verzeichnis der Anwendung. Wenn die Komponente dort nicht gefunden wird, wird sie im Global Assembly Cache (GAC) gesucht. Da eine Komponente nur an diesen beiden Orten sein kann, braucht sie beim System nicht mehr registriert zu werden. Da außerdem die Komponente durch ihre Metadaten beschrieben wird, kann die Common Language Runtime hieraus die nötigen Informationen gewinnen. 왘 ASP.NET ist auf der Grundlage der Common Language Runtime programmiert worden. Im Gegensatz zu ASP wurde dies auf der Grundlage von COM programmiert. Das heißt jetzt, dass man Assemblies einfach dadurch updaten kann, dass man die entsprechende Assembly einfach mit der neueren Version überschreibt. Die Common Language Runtime entlädt dann die ältere Version aus dem Speicher, wenn alle Zugriffe darauf abgeschlossen wurden. Um den letzten Punkt möglich zu machen, verwendet die Common Language Runtime eine Technik, die sich Shadow Copy nennt. Durch dieses Feature werden Assemblies im Speicher nie mehr blockiert, was heißt, dass man eine neuere Version der Assembly einfach durch Überschreiben installieren kann. Außerdem braucht durch diesen Vorgang der IIS und der Computer nicht mehr neu gestartet werden. Das Shadow-Copy-Feature funktioniert so, dass Assemblies immer in einen Cache kopiert werden. Wenn jetzt eine Komponente geladen wird, wird sie aus dem Cache geladen und nicht von ihrer ursprünglichen Position im Dateisystem. Dadurch kann sie im Speicher nie mehr blockiert werden. Das Shadow-Copy-Feature ist nur für ASP.NET-Anwendungen verfügbar, die ihre Komponenten im Bin-Verzeichnis speichern.
Sandini Bib Entwurfsziele von ASP.NET
155
Unterstützung von Tools ASP war ein sehr großer Technologiesprung, was die Entwicklung von InternetAnwendungen betroffen hat. Aber die Unterstützung durch Entwicklungstools war nicht sehr groß. Microsoft brachte dann Visual Interdev heraus, mit dem man bequem ASP-Seiten programmieren konnte. Aber die Funktionen eines Visual Studios hatte Visual Interdev leider nicht. Das beste Hilfsmittel eines ASP-Programmierers war der Editor und der leistungsfähigste Debugger war Response.Write. Das Debugging von ASP-Anwendungen, die COM-Komponenten verwenden, war unter Visual Interdev auch nicht möglich. Wenn man unter ASP eine COM-Komponente debuggen wollte, musste man die Komponente in der entsprechenden Entwicklungsumgebung (meistens Visual Basic) extra öffnen und dann debuggen. Unter ASP.NET kann man jetzt direkt Komponenten debuggen, die in der ASP.NET-Anwendung aufgerufen werden. Mit Microsoft .NET bringt Microsoft auch eine neue Version des Visual Studios heraus. Diese Version heißt Visual Studio .NET und wurde bereits im vorherigen Kapitel näher betrachtet. In Visual Studio .NET gibt es kein Visual Interdev mehr. Die Funktionen von Visual Interdev wurden komplett in Visual Studio .NET integriert und können in der gesamten Entwicklungsumgebung verwendet werden. Große Fortschritte gibt es beim Debugging von ASP.NET-Anwendungen. Hier kann man jetzt direkt in .NET Komponenten debuggen und wieder zurück in die ASP.NETAnwendung. Die Entwicklung und das Debugging einer ASP.NET-Anwendung kann man jetzt mit der Entwicklung und dem Debugging einer Visual Basic-Anwendung vergleichen.
Leichtere, flexiblere Konfiguration Die Konfiguration von ASP.NET-Anwendungen wurde auch sehr vereinfacht. ASPAnwendungen wurden mithilfe der Registrierung und durch die Metabase des IIS konfiguriert. Wenn man die Anwendung auf einen anderen Webserver verlegen wollte, war diese Methode nicht die beste. Denn jetzt musste man die Registrierungseinträge und die Einträge in der Metabase des IIS ebenfalls mitkopieren. In ASP.NET wird eine Web-Anwendung an einem zentralen Ort konfiguriert: in einer Datei namens web.config. Diese liegt im Rootverzeichnis der Web-Anwendung. Hier kann man dann benutzerdefinierte Einträge, wie z.B. Verbindungszeichenfolgen für Datenbankverbindungen, hinzufügen. Wenn jetzt die Web-Anwendung auf einen anderen Server kopiert wird, braucht nur noch diese web.config-Datei mitkopiert zu werden, und die ASP.NET-Anwendung ist sozusagen "up-to-date". Diese Konfigurationsdateien werden hierarchisch auf Ordnerebene angewendet. Wenn z.B. die Konfigurationsdatei im Rootverzeichnis der Web-Anwendung liegt, gilt sie auch für alle Unterordner der Anwendung. Nehmen wir an, wir haben ein Unterverzeichnis mit dem Namen SubFolder1. Wenn es in diesem Ordner wiederum eine
Sandini Bib 156
3
Einführung in ASP.NET
web.config-Datei gibt, überschreibt diese Konfigurationsdatei die Einstellungen der web.config Datei im Rootverzeichnis der Web-Anwendung. Jetzt stellt sich noch die Frage, was bezüglich der Konfiguration in einer solchen Konfigurationsdatei gespeichert wird. Diese Einstellungen beziehen Folgendes ein: 왘 Timeout einer Anfrage 왘 Welche .NET-Klassen für die Kompilierung des Quellcodes verantwortlich sind 왘 Wie oft der Arbeitsprozess von ASP.NET erneuert werden soll 왘 Welche Sicherheitseinstellungen standardmäßig aktiviert sind Eine einfache Konfigurationsdatei sieht folgendermaßen aus:
Um jetzt auf diese Konfigurationsdatei zugreifen zu können, muss man den Namespace System.Configurations in die ASP.NET-Anwendung importieren und z.B. folgende Anweisung ausführen: Einfaches Konfigurationsbeispiel
Mit dieser Anweisung wird dann der ConnectionString in der Variable strConnectionString gespeichert und anschließend ausgegeben. Wenn wir die Anwendung ausführen, sieht das Ergebnis folgendermaßen aus:
Sandini Bib Entwurfsziele von ASP.NET
157
Abbildung 3.1: Verwendung von web.config
HTTP-Runtime Die HTTP-Runtime bietet die gleiche Funktionalität wie die ISAPI-Erweiterungen und ISAPI Filter (ISAPI: Internet Service Application Programming Interface) bei den heutigen ASP-Anwendungen. Die HTTP-Runtime ist aber viel einfacher zu programmieren und zu verwenden. Die HTTP-Runtime sitzt zwischen der ASP.NET-Anwendung und der Common Language Runtime und lässt sich mithilfe von .NET-Klassen ansprechen. Die HTTP-Runtime leistet Folgendes: 왘 Webaufrufe werden von einer http-Request-Handler-Klasse abgearbeitet. 왘 Services wie z.B. das State Management werden durch HTTP Modulklassen implementiert. Die Anzahl der HTTP-Module ist in ASP.NET nicht begrenzt. Welche Module verwendet werden, wird wiederum verzeichnisbasierend in der XML-Konfigurationsdatei web.config festgelegt. Diese Konfigurationsdatei legt z.B. fest, welches Modul für das ASP.NET-State-Management verwendet wird. Dadurch wird es möglich, ASP.NET durch Module von Drittanbietern umfassend zu erweitern und an die eigenen Bedürfnisse anzupassen. ASP.NET ist selbst durch einen HTTP-Handler implementiert worden. Wenn man sich die Datei machine.config ansieht, die sich im Verzeichnis C:\WinNT\Microsoft .NET\Framework\v0.0.0.0\config befindet, findet man einen Abschnitt, der sich httphandlers nennt:
Sandini Bib 158
3
Einführung in ASP.NET
Listing 3.1: HTTP-Handler-Abschnitt in machine.config
Aus diesem Abschnitt geht hervor, dass alle Webanfragen mit einer Erweiterung .aspx von der Klasse System.Web.UI.PageHandlerFactory abgearbeitet werden. WebServices mit der Erweiterung .asmx werden von der Klasse System.Web.Services.WebServceHandlerFactory weiterverarbeitet.
Sandini Bib Programmierung von ASP.NET-Seiten
159
Die Programmiersprache ist nebensächlich Wenn man eine ASP.NET-Anwendung programmiert, ist die Wahl der Programmiersprache nur an die persönlichen Vorlieben gebunden. Jede .NET-Programmiersprache bietet die gleiche Funktionalität und Leistung. Keine .NET-Programmiersprache ist gegenüber einer anderen eingeschränkt. Für Sprachen von Drittanbietern, wie z.B. Cobol oder Perl, müssen die Compilerherstellen einen Codegenerator verfügbar machen, der sich von der Klasse System.CodeDOM.CodeGenerator ableitet. Sobald dies geschehen ist, kann man die Programmiersprache im kompletten .NET Framework verwenden.
3.2 Programmierung von ASP.NET-Seiten Die ASP.NET-Programmierung ist mit der Windows-Programmierung vergleichbar, da beide Programmiermodelle ereignisorientiert aufgebaut sind. Um die Programmierung von ASP.NET-Seiten vollständig zu verstehen, muss man wissen, welche Ereignisse in den verschiedenen Phasen einer Seite aufgerufen werden. Der Code innerhalb dieser Ereignisse wird sequenziell ausgeführt, aber die Ereignisse werden nur verarbeitet, wenn sie durch den Benutzer ausgelöst werden.
Server-Roundtrip Sobald der Benutzer in einer WebForm auf dem Client ein Ereignis auslöst, wird dieses auf dem Server behandelt. In der Vergangenheit wurden sehr viele Ereignisse des Benutzers clientseitig per JavaScript behandelt. Dies hatte aber den Nachteil, dass JavaScript auf dem Zielbrowser verfügbar sein musste. Unter ASP.NET muss JavaScript auf dem Zielbrowser nicht mehr verfügbar sein. Wenn ein Ereignis clientseitig ausgelöst, und anschließend serverseitig behandelt wird, nennt man diesen Vorgang einen Server-Roundtrip. Genauer gesagt ist ein Server-Roundtrip die Abfolge der folgenden Ereignisse: 왘 Ein Ereignis wird clientseitig durch den Benutzer ausgelöst. 왘 Es wird eine entsprechende Funktion serverseitig aufgerufen, die das Ereignis behandelt. 왘 Das Ergebnis der Funktion wird an den Client zurückgeschickt. Der Server-Roundtrip wird so implementiert, dass die ASP.NET-Seite an sich selbst gepostet wird. Diesen Vorgang nennt man Postback. Über die Eigenschaft IsPostback kann man ermitteln, ob die Seite das erste Mal aufgerufen wurde oder ob ein ServerRoundtrip, also ein Postback stattgefunden hat.
Sandini Bib 160
3
Einführung in ASP.NET
Um ASP.NET-Anwendungen zu optimieren, muss man darauf achten, dass die ServerRoundtrips verringert werden. Denn jeder Server-Roundtrip kostet Zeit und im Internet ist Zeit sehr kostbar. Wenn dann der Client das Ergebnis der serverseitigen Funktion zurückbekommt, wird dieses dann entsprechend der Programmierung auf dem Zielbrowser angezeigt. Daraus könnte man jetzt folgern, dass dann jedes Mal die ASP.NET-Seite komplett neu angezeigt werden muss. Daraus würden ASP.NET-Anwendung entstehen, die ein sehr großes Flackern auf dem Bildschirm verursachen würden. Dem ist aber nicht so. ASP.NET wurde so aufgebaut, dass nur der Teil der ASP.NET-Seite zurückgeliefert wird, der sich während des Aufrufes der serverseitigen Funktion geändert hat. Und auch nur dieser Teil der ASP.NET-Seite wird dann neu im Browser dargestellt. Daher haben ASP.NET-Anwendungen ziemlich die gleiche Leistung wie WindowsAnwendungen, was die Oberfläche betrifft. Man könnte jetzt annehmen, dass zwischen den verschiedenen Aufrufen von serverseitigen Funktionen kein Zusammenhang besteht, da das Internet ja zustandslos ist. Zustandslos heißt, dass ein Aufruf vom vorherigen Aufruf nichts weiß. Aber wie kann man dann den Zustand zwischen dem Aufruf von zwei serverseitigen Funktionen gewährleisten? Unter ASP.NET wird das so genannte ViewState und das State-Management eingeführt. Das State-Management wird durch ein zusätzliches HTTP-Modul implementiert. Wie der ViewState genau implementiert wurde, sehen wir später in einem Beispiel.
Die verschiedenen Phasen einer WebForm Wie schon vorher erwähnt, gibt es beim Aufruf einer ASP.NET-Seite verschiedene Phasen, die verschiedene Ereignisse auf dem Server auslösen. Diese Ereignisse können natürlich durch den Programmierer behandelt werden. In jeder Phase wird die entsprechende Funktion auf dem Server aufgerufen und der Code ausgeführt. Diese verschiedenen Phasen werden für jeden Roundtrip wiederholt. Die folgende Tabelle zeigt die verschiedenen Phasen einer ASP.NET-Seite. Phase
Beschreibung
Initialisierung (Page_Init-Ereignis)
Dieses Ereignis wird beim Initialisieren der Seite aufgerufen.
Konfiguration (Page_Load-Ereignis)
Der ViewState und der State der Controls werden wiederhergestellt und dann wird das Page_Load-Ereignis aufgerufen.
Ereignisbehandlung
Wenn die Seite durch ein Ereignis aufgerufen wird, wird die entsprechende Ereignisbehandlungsmethode aufgerufen.
Tabelle 3.1: Verschiedene Phasen einer ASP.NET-Seite
Sandini Bib Programmierung von ASP.NET-Seiten
161
Phase
Beschreibung
Säuberung (Page_Unload-Ereignis)
Die Seite ist fertig mit der Anzeige und wird verworfen.
Tabelle 3.1: Verschiedene Phasen einer ASP.NET-Seite (Forts.)
Der Unterschied zwischen Page_Init und Page_Load ist der, dass erst in Page_Load gewährleistet wird, dass der ViewState der Controls wiederhergestellt wurde. Man kann auf die Controls in Page_Init zugreifen, aber die Controls haben hier erst ihre Standardwerte. Die Werte durch das Postback werden erst in Page_Load wiederhergestellt. Sehen wir uns jetzt ein kleines Beispiel an. Die folgende Abbildung zeigt die Ausgabe der ASP.NET Seite.
Abbildung 3.2: Verwendung von Page_Load
Die Anwendung dient dazu, die Zahlungsmethode für den Versand auszuwählen. Dabei kann zwischen den verschiedenen Kreditkarten ausgewählt werden. Sehen wir uns jetzt dazu den Code an: void Page_Load(Object sender, EventArgs e) { if (!IsPostBack) { SqlConnection cnnConnection; SqlCommand cmdCommand; String strConnectionString; String strSql;
Sandini Bib 162
3
Einführung in ASP.NET
strSql = "SELECT * FROM Shippers"; strConnectionString = "server=localhost;uid=sa;pwd=;database=Northwind"; cnnConnection = new SqlConnection(strConnectionString); cnnConnection.Open(); cmdCommand = new SqlCommand(strSql, cnnConnection); SqlDataReader myReader = cmdCommand.ExecuteReader(); ShipMethod.DataSource = myReader; ShipMethod.DataBind(); cnnConnection.Close(); } } void PlaceOrder_Click(Object sender, EventArgs e) { YouSelected.Text = "Ihre Bestellung wird durch " + ShipMethod.SelectedItem.Text + " zugestellt."; } Bitte wählen Sie die Versandmethode aus: Listing 3.2: ASP.NET-Beispielanwendung
Sehen wir uns nun den Programmcode näher an. In den ersten beiden Zeilen werden die Namespaces importiert, damit man vom SQL-Server Daten abfragen kann. Anschließend wird ein serverseitiger Scriptblock definiert, der in C# geschrieben ist. Dies wird durch die beiden Attribute language und runat möglich gemacht. Mit der Eigenschaft IsPostback wird überprüft, ob die Seite das erste Mal angezeigt wird. IsPostback gibt False zurück, wenn die Seite das erste Mal aufgerufen wird. IsPostback gibt True zurück, wenn die Seite während eines Server-Roundtrips wieder neu angezeigt wird.
Sandini Bib Programmierung von ASP.NET-Seiten
163
Anschließend werden ein Connection-Objekt und ein Command-Objekt für die Datenbankabfrage deklariert. Dann werden die beiden Stringvariablen mit der Verbindungszeichenfolge und mit der SQL-Abfrage initialisiert. Danach wird die Datenbankverbindung hergestellt und die Abfrage durch cmdCommand.ExecuteReader() ausgeführt. Diese Funktion gibt ein SqlDataReader-Objekt zurück, was soviel wie ein lesbares Recordset ist. Schließlich wird dieses Recordset mithilfe der Funktion DataBind() an die DropdownListbox gebunden und angezeigt. Die Ereignisfunktion PlaceOrder_Click() wird aufgerufen, wenn der Benutzer auf den Button mit der Aufschrift "Auftrag ausführen" klickt. Diese weist dem Label YouSelected den Text der ausgewählten Versandmethode zu. Nach dem Scriptblock wird dann noch die WebForm definiert. Hier kommt eine DropDownList-Box, ein Button und ein Label zum Einsatz. Hier sieht man auch schon den großen Vorteil der Server-Controls, denn auf die verschiedenen Server-Controls hat man auch im serverseitigen Code Zugriff. Fassen wir nochmals die wichtigsten Punkte zusammen: 왘 Auf Server-Controls kann im serverseitigen Code zugegriffen werden, wodurch leistungsfähige Web-Anwendungen möglich werden. Diese Controls werden als normaler HTML-Text im Browser dargestellt. So kann man jeden beliebigen Browser für ASP.NET-Anwendungen verwenden. 왘 Das Page_Load-Ereignis wird immer aufgerufen, wenn die Seite geladen wird. Mit der Eigenschaft IsPostback kann man überprüfen, ob die Seite das erste Mal aufgerufen wird, oder ob ein Server-Roundtrip stattgefunden hat. In dieser Ereignisfunktion werden normalerweise die Controls der WebForm initialisiert. 왘 Ereignisse von Controls werden nur durch den Benutzer ausgelöst. Ereignisprozeduren für Controls werden durch Eigenschaften bei den Server-Controls festgelegt.
ViewState Im vorigen Beispiel werden die verschiedenen Versandfirmen nur angezeigt, wenn die ASP.NET-Seite das erste Mal angezeigt wird. Aber was geschieht, wenn die Seite nach einem Server-Roundtrip erneut angezeigt wird? Dann wird ja der Code im Page_LoadEreignis nicht aufgerufen. ASP.NET speichert den Zustand der Server-Controls im sogenannten ViewState. Aber wie sieht nun dieser ViewState in einer ASP.NET-Seite aus? Der ViewState wird dekodiert in einem Hidden-Field der WebForm als String gespeichert. Da ja der Client von ASP.NET und dem ViewState nichts weiß, muss dafür ein Hidden-Field gewählt werden, damit sozusagen nur die Serverseite davon etwas mit-
Sandini Bib 164
3
Einführung in ASP.NET
bekommt. Schauen wir uns das Beispiel von weiter vorne an. Wenn wir den Quelltext der ASP.NET-Seite im Internet Explorer öffnen, werden wir den ViewState zu sehen bekommen. Er könnte z.B. wie folgt innerhalb der WebForm als ein Hidden-Field gespeichert werden: Listing 3.3: ViewState in ASP.NET
Wie wir hier sehen, ist der ViewState für einen Menschen nicht entschlüsselbar. Aber ASP.NET fängt mit dieser Dekodierung etwas an und kann so den Status der ServerControls zwischen den Server-Roundtrips speichern, damit die Zustandslosigkeit des Internets überbrückt werden kann. Ein weiterer Vorteil davon, dass der ViewState auf dem Client gespeichert wird und nicht auf dem Server, ist, dass man problemlos die ASP.NET-Anwendung z.B. auf einer Serverfarm einrichten kann, ohne sich Sorgen zu machen, dass Anfragen auf verschiedene Server verteilt werden. Da ja der ViewState nicht auf dem Server gespeichert wird, können in einer Web Farm beliebig viele Server verschiedene Anfragen zwischen den Server-Roundtrips behandeln. Dies ist mit normalen ASP nicht ohne weitere Programmierung möglich. Natürlich verursacht der ViewState einen gewissen Overhead, der über das Internet übertragen wird. Daher gibt es auch die Möglichkeit, den ViewState für eine ganze ASP.NET-Seite zu deaktivieren. Natürlich kann man den ViewState auch nur für ein Server-Control deaktivieren. Wenn man den ViewState für eine ASP.NET-Seite deaktivieren will, muss man folgende Page Direktive am Anfang der Seite aufnehmen:
Jedes Server-Control hat eine Eigenschaft mit dem Namen EnableViewState. Standardmäßig ist diese Eigenschaft auf True gesetzt, was bedeutet, das für jedes Server-Control, dass man einer ASP.NET-Seite hinzufügt, der ViewState aktiviert ist. Setzt man diese Eigenschaft z.B. explizit auf False, wird damit der ViewState für das gewünschte Server-Control deaktiviert. In unserem vorherigen Beispiel könnte man z.B. beim Label-Control den ViewState deaktivieren, da ja im Label-Control nur Statusinformationen angezeigt werden, die nicht bei einem Server-Roundtrip mitgeführt werden müssen.
Sandini Bib Programmierung von ASP.NET-Seiten
165
Ereignisbehandlung Wie wir bereits gesehen haben, ist das Eventhandling in ASP.NET-Anwendungen ganz anders als in tradionellen Windows-Anwendungen, da in ASP.NET-Anwendungen Ereignisse auf dem Client ausgelöst, und auf dem Server behandelt werden. Der Transfer zwischen dem Client und dem Server wird über HTTP Post abgewickelt. Als Entwickler braucht man nicht zu wissen, wie dieser Vorgang genau aussieht, da uns hier das .NET Framework die komplette Arbeit abnimmt. Da für jedes Ereignis, dass auf dem Client ausgelöst wird, ein Server-Roundtrip notwendig ist, können die ausgelösten Ereignisse die Performance der ASP.NET-Anwendung stark beeinflussen. Daher gibt es keine Ereignisse wie z.B. OnMouseOver. Das Ereignis OnChange wurde für ASP.NET ein wenig anders implementiert. Dieses Ereignis wird nicht ausgelöst, wenn sich z.B. in einer Textbox der Text ändert, sondern wenn die Textbox den Fokus verliert. Dadurch wird die Performance der ASP.NET-Anwendung wesentlich besser und die Anwendung wird benutzerfreundlicher, da sie flexibler zu bedienen ist. Die Funktionsignatur der Eventhandler-Funktion ist für alle Ereignisse gleich. Es gibt zwei Parameter, die dem Eventhandler übergeben werden. void cmdSubmit_OnClick(Object sender, EventArgs e) { }
Der erste Parameter bezieht sich auf das Objekt, das das Ereignis ausgelöst hat. Wenn man z.B. einen Eventhandler für mehrere Ereignisse verwendet, kann man über diesen ersten Parameter feststellen, welches Objekt das Ereignis augelöst hat. Der zweite Parameter gibt über nähere Informationen des Ereignisses Aufschluss. Hier kann man z.B. die Position der Maus während des Ereignisses abfragen. Sehen wir uns jetzt an, wie man ein bestimmtes Ereignis mit einer EventhandlingFunktion verbindet. Nehmen wir an, dass wir einen Button mit der Aufschrift "Abschicken" haben, der die oben definierte Funktion aufruft.
Die Eigenschaft OnClick gibt an, welche Ereignisprozedur aufgerufen werden soll, wenn auf den Button geklickt wird. In diesem Beispiel wird hier unsere Funktion cmdSubmit_OnClick angegeben. Nachfolgend ist ein Beispiel zu finden, das "Hello World" in einem Label ausgibt, wenn man den Button SHOW! klickt.
Sandini Bib 166
3
Einführung in ASP.NET
void cmdShow_OnClick(Object sender, EventArgs e) { lblHelloWorld.Text = "Hello World"; } < Listing 3.4: Verwendung von Eventhandlern in ASP.NET
Fassen wir nochmals die einzelnen Schritte, die nötig sind, zusammen, um einen Eventhandler in ASP.NET zu definieren und mit einem Ereignis zu verlinken: 왘 Im Server-Control wird der Eventhandler dem Ereignisnamen zugeordnet.
왘 Anschließend muss noch eine Ereignisfunktion deklariert werden, die den gewünschten Namen und die besprochenen zwei Parameter hat. void MethodName(Object sender, EventArgs e) { }
Sandini Bib Die Page-Klasse und deren Verarbeitung
167
3.3 Die Page-Klasse und deren Verarbeitung Für jede ASP.NET-Seite wird während des Aufrufs eine Page-Klasse kompiliert, die für die komplette Verarbeitung und für die Anzeige der ASP.NET-Seite zuständig ist. Die Basisklasse ist im Namespace System.Web.UI angesiedelt und heißt Page. Die PageKlasse, die erzeugt wird, kann aus folgenden Bestandteilen bestehen: 왘 Aus der .aspx Seite, die angefordert wurde 왘 Aus der .NET Klassendatei, die den Code für die Page Klasse definiert 왘 User Controls, die auf der Seite verwendet werden Diese dynamische erzeugte Klasse kann immer erzeugt werden, wenn eine Anforderung für die ASP.NET-Seite eintrifft. Wenn sie erzeugt wurde, werden dann alle Zugriffe durch diese Klasse abgewickelt. Alle Controls werden ebenfalls während der Erzeugung der Klasse angelegt und auch sie schicken dann die Ergebnisse mittels der Klasse an den Client zurück. Die Klasse wird nur dann kompiliert, wenn der Inhalt der ASP.NET-Seite geändert wurde. Ansonsten wird die Seite aus dem Cache geladen. Wenn der IIS neu gestartet wird, muss die Klasse ebenfalls neu kompiliert werden. Durch die Verwendung des Caches ergeben sich große Leistungsverbesserungen hinsichtlich der Geschwindigkeit. In den folgenden Tabellen werden die Ereignisse, Eigenschaften und Methoden der Klasse näher beschrieben. Ereignis
Beschreibung
Init-Ereignis
Wird ausgelöst, wenn die Seite initialisiert wird
Load-Ereignis
Wird ausgelöst, wenn die Seite und alle Controls mit ihrem ViewState geladen wurden
Unload-Ereignis
Wird ausgelöst, wenn die Seite mit der Verarbeitung der Anfragen fertig ist. Das passiert, wenn alle Daten an den Client zurückgesendet werden.
PreRender-Ereignis
Wird ausgelöst, bevor die Daten zum Client geschrieben werden
AbortTransaction-Ereignis
Wird ausgelöst, wenn die Transaktion der Seite für abgebrochen erklärt wird
CommitTransaction-Ereignis
Wird ausgelöst, wenn die Transaktion der Seite für fertig erklärt wird
Error-Ereignis
Wird ausgelöst, wenn eine unbehandelte Ausnahme auf der Seite auftritt. Solche unbehandelten Ausnahmen können ebenfalls durch die ASP.NET-Seite benutzerdefiniert behandelt werden.
Tabelle 3.2: Ereignisse der Page Klasse
Sandini Bib 168
3
Einführung in ASP.NET
Eigenschaften
Beschreibung
Application-Eigenschaft
Referenz zum aktuellen Application-Objekt. Für jede Web-Anwendung gibt es eine Instanz von diesem Objekt. Es wird von allen Clients benutzt, die auf die Web-Anwendung zugreifen.
Cache-Eigenschaft
Diese Eigenschaft ist eine Referenz zum Cache-Objekt. Es kann benutzt werden, um Daten zwischen den Server-Roundtrips zwischenzuspeichern. Das Cache-Objekt wurde in der Form eines Dictionary implementiert.
ClientTarget-Eigenschaft
Diese Eigenschaft ist dafür da, dass die Browser-Ermittlung, die in ASP.NET eingebunden ist, überschreiben kann. Die Browser-Ermittlung legt fest, für welchen Browsertyp die ASP.NET-Seite angezeigt werden soll.
EnableViewState-Eigenschaft
Diese Boolean-Eigenschaft legt fest, ob die Controls während eines Server-Roundtrips ihren Status behalten sollen oder nicht. Diese Einstellung gilt für die ganze Seite und deren Controls.
ErrorPage-Eigenschaft
Wenn die Seite kompiliert wird und wenn ein Fehler auftritt, dann will man meistens eine benutzerdefinierte Seite anzeigen, die eine Fehlermeldung anzeigt. ASP.NET generiert seine eigene Fehlermeldungsseite. Will man aber festlegen, welche Seite bei einem Fehler anzeigt werden soll, dann kann man in dieser Eigenschaft eine URL von einer Fehlermeldungsseite speichern.
IsPostback-Eigenschaft
Diese Eigenschaft ist True, wenn die Seite aufgrund eines ServerRoundtrips angezeigt wird. Wird die Seite das erste Mal anzeigt, dann ist diese Eigenschaft auf False gesetzt, und die Controls haben noch keinen ViewState. Daher müssen in diesem Fall die Controls in Page_Load durch den Programmierer initialisiert werden.
IsValid-Eigenschaft
Diese Eigenschaft ist True, wenn alle Validation-Controls angeben, dass ihre Überprüfungen positiv ausgeführt wurden. Sobald aber auch nur bei einem Validation Control die Überprüfung fehlschlägt, wird diese Eigenschaft auf False gesetzt. Durch diese Eigenschaft kann man die Performance einer ASP.NET-Seite wesentlich steigern, wenn man weiß, dass man Operationen nur durchführen muss, wenn alle Überprüfungen positiv durchgeführt wurden.
Request-Eigenschaft
Diese Eigenschaft ist eine Referenz auf das Request-Objekt von ASP.NET. Durch dieses Objekt hat man auf HTTP-Request Zugriff.
Response-Eigenschaft
Diese Eigenschaft ist eine Referenz auf das Response-Objekt von ASP.NET. Durch dieses Objekt hat man auf HTTP-Response Zugriff.
Server-Eigenschaft
Diese Eigenschaft ist eine Referenz auf das Server Objekt.
Tabelle 3.3: Eigenschaften der Page Klasse
Sandini Bib Die Page-Klasse und deren Verarbeitung
169
Eigenschaften
Beschreibung
Session-Eigenschaft
Diese Eigenschaft ist eine Referenz auf das Session-Objekt. Im Session Objekt kann man verschiedene Daten speichern, die während der Ausführung einer ASP.NET-Anwendung verfügbar sind. So kann man z.B. Daten zwischen verschiedenen ASP.NET-Seiten austauschen.
Trace-Eigenschaft
Diese Eigenschaft ist eine Referenz auf das Trace Objekt der aktuellen ASP.NET-Seite. Wenn man das Tracing für die aktuelle Seite aktiviert kann, kann man mithilfe dieses Eigenschaften benutzerdefinierte Funktionen in das Trace-Log schreiben.
TraceEnabled-Eigenschaft
Diese Eigenschaft gibt an, ob das Tracing für die ASP.NET-Seite aktiviert ist oder nicht.
User-Eigenschaft
Diese Eigenschaft liefert nähere Informationen über den Benutzer, der die Seite aufgerufen hat.
Validators-Eigenschaft
Dies Eigenschaft ist eine Referenz auf eine Auflistung, die alle Validation Controls der ASP.NET-Seite enthält. Man kann sich durch diese Auflistung bewegen, um z.B. verschiedene Parameter der Validation Controls einzustellen.
Tabelle 3.3: Eigenschaften der Page Klasse (Forts.) Methode
Beschreibung
DataBind-Methode
Führt die Datenbindung für alle Controls auf der aktuellen ASP.NETSeite aus
FindControl-Methode
Über diese Methode kann man ein bestimmtes Control auf der Seite finden.
LoadControl-Methode
Diese Methode lädt dynamisch ein User-Control von einer .ascxDatei.
LoadTemplate-Methode
Diese Methode lädt dynamisch eine Vorlage.
MapPath-Methode
Diese Methode ermittelt den physischen Pfad für ein bestimmtes virtuelles Verzeichnis.
ResolveURL-Methode
Konvertiert eine virtuelle URL zu einer absoluten URL
Validate-Methode
Diese Methode fordert alle Validation Controls auf, ihre Überprüfungen durchzuführen.
Tabelle 3.4: Methoden der Page-Klasse
Alle diese Ereignisse, Eigenschaften und Methoden sind direkt von der ASP.NET-Seite aufrufbar, ohne dass man sich des Page-Objekt bedient. Die folgenden beiden Zeilen haben z.B. die identische Wirkung:
Außerdem gibt es auch keine Performanceunterschiede. Daher kann die Form wählen, die einem besser gefällt. Wahrscheinlich wird man die zweite Form wählen, da diese schneller zu schreiben ist.
HttpRequest-Objekt Über das HttpRequest-Objekt hat man auf das traditionelle Request-Objekt von ASP Zugriff. Das HttpRequest-Objekt wird auf die Request-Eigenschafgt der Page-Klasse abgebildet, weshalb direkt durch die Request-Eigenschaft auf das HttpRequest-Objekt zugreifen kann. Meistens wird man das HttpRequest-Objekt dazu verwenden, um Zugriff auf die verschiedenen Servervariablen zu bekommen. In ASP wurden die Servervariablen in einer Auflistung gespeichert. In ASP.NET sind die Servervariablen jetzt in Eigenschaften gespeichert. Diese Eigenschaften befinden sich direkt im HttpRequest-Objekt, was den Zugriff um einiges vereinfacht. Sehen wir uns ein paar Eigenschaften und Methoden dieses Objekts näher an: Eigenschaft / Methode
Beschreibung
AcceptTypes
Gibt die MIME-Typen an, die der Client akzeptiert
ApplicationPath
Der virtuelle Anwendungspfad
ContentLength
Die Länge (in Bytes) der Anfrage
ContentType
Der MIME-Typ der Anfrage
FilePath
Der virtuelle Pfad der Anfrage
Headers
Eine Auflistung der HTTP-Header
HttpMethod
Die HTTP-Methode, die für den Aufruf benutzt wurde
Path
Der virtuelle Pfad der Anfrage
PathInfo
Zusätzliche Informationen über den Pfad
PhysicalApplicationPath
Der physische Pfad des Anwendungswurzelverzeichnisses
PhysicalPath
Der physische Pfad der Anfrage
RawUrl
Die rohe URL der Anfrage
RequestType
Die HTTP-Methode, die für die Anfrage benutzt wurde
TotalBytes
Die Anzahl der Bytes im Eingabestrom.
Url
Ein Uri-Objekt, dass nähere Informationen über die Anfrage beinhaltet
Tabelle 3.5: Eigenschaften und Methoden des HttpRequest-Objekts
Sandini Bib Die Page-Klasse und deren Verarbeitung
171
Eigenschaft / Methode
Beschreibung
UrlReferrer
Ein Uri-Objekt, dass nähere Informationen über die letzte Anfrage beinhaltet
UserAgent
Der Typ des Browsers
UserHostAddress
Die IP-Adresse des Benutzers
UserHostName
Der DNS-Name des Benutzers
UserLanguages
Ein Array, in dem die Sprachpräferenzen des Benutzers gespeichert werden
Tabelle 3.5: Eigenschaften und Methoden des HttpRequest-Objekts (Forts.)
Nachdem wir jetzt die verschiedenen Servervariablen besprochen haben, schauen wir uns die Ausgabe des folgenden Beispielprogramms an.
Abbildung 3.3: Ausgabe der Servervariablen
Sandini Bib 172
3
Einführung in ASP.NET
Dieses Beispielprogramm liest die Servervariablen der aktuellen ASP.NET-Seite aus und gibt sie in einer WebForm aus. Der Quelltext dazu sieht folgendermaßen aus: Auslesen der Servervariablen void Page_Load(Object sender, EventArgs e) { StringBuilder myBuilder = new StringBuilder(); myBuilder.Append("AcceptTypes: ").Append(Request.AcceptTypes).Append(""); myBuilder.Append("ApplicationPath: ").Append(Request.ApplicationPath).Append(""); myBuilder.Append("ContentLength: ").Append(Request.ContentLength).Append(""); myBuilder.Append("ContentType: ").Append(Request.ContentType).Append(""); myBuilder.Append("FilePath: ").Append(Request.FilePath).Append(""); myBuilder.Append("Headers: ").Append(Request.Headers).Append(""); myBuilder.Append("HttpMethod: ").Append(Request.HttpMethod).Append(""); myBuilder.Append("Path: ").Append(Request.Path).Append(""); myBuilder.Append("PathInfo: ").Append(Request.PathInfo).Append(""); myBuilder.Append("PhysicalApplicationPath: ").Append(Request.PhysicalApplicationPath).Append(""); myBuilder.Append("PhysicalPath: ").Append(Request.PhysicalPath).Append(""); ^ myBuilder.Append("RawUrl: ").Append(Request.RawUrl).Append(""); myBuilder.Append("RequestType: ").Append(Request.RequestType).Append(""); myBuilder.Append("TotalBytes: ").Append(Request.TotalBytes).Append(""); myBuilder.Append("Url: ").Append(Request.Url).Append(""); myBuilder.Append("UrlReferrer: ").Append(Request.UrlReferrer).Append(""); myBuilder.Append("UserAgent: ").Append(Request.UserAgent).Append(""); myBuilder.Append("UserHostAddress: ").Append(Request.UserHostAddress).Append(""); myBuilder.Append("UserHostName: ").Append(Request.UserHostName).Append(""); myBuilder.Append("UserLanguages: ").Append(Request.UserLanguages).Append(""); Response.Write(myBuilder.ToString()); }
Sandini Bib Die Page-Klasse und deren Verarbeitung
173
Listing 3.5: Verwendung der Servervariablen in ASP.NET
Wenn man die aktuelle Anfrage des Clients z.B. auf der Serverseite in eine Datei speichern möchte, könnte man den Befehl SaveAs des Objekts Request verwenden: Request.SaveAs("C:\\CurrentRequest.txt", true);
Über den zweiten Parameter wird festgelegt, ob die Headerinformationen der Anfrage auch mitgespeichert werden sollen. Wenn er auf true gesetzt wird, werden diese Informationen auch gespeichert.
HttpResponse-Objekt Über das Objekt HttpResponse hat man direkten Zugriff auf die Ausgabe, die an den Client zurückgesendet wird. Das HttpResponse-Objekt wird auf die Request-Eigenschaft der Page-Klasse abgebildet, weshalb man direkt über die Request-Eigenschaft auf das HttpResponse-Objekt zugreifen kann. In ASP hat es die Buffer-Eigenschaft gegeben. In ASP.NET wurde diese durch die BufferOutput-Eigenschaft ersetzt. Diese Boolean-Eigenschaft bestimmt, wie die Ergebnisse zum Client geschickt werden. Wenn es auf true gesetzt wird, wird die Ausgabe erst an den Client gesendet, wenn alle Bearbeitungen auf dem Server abgeschlossen wurden. Ist die Eigenschaft jedoch auf false gesetzt, wird die Ausgabe sobald wie möglich an den Client zurückgeschickt. Eine bei den ASP-Programierern sehr beliebte Funktion war immer Response.Write. Diese Funktion ist in ASP.NET natürlich auch den Programmierern erhalten geblieben. Diese Funktion hat einen Parameter, der den String festlegt, der an den Client geschickt werden soll: Response.Write("Hello World!");
Natürlich ist es auch z.B. möglich, Grafiken auf dem Server dynamisch zu generieren und diese dann an den Client zurückzuschicken. Typische Anwendungsbereiche wären z.B. Unternehmensdiagramme, die aus den Daten der Unternehmensdatenbank Graphen generieren könnten. Ein Beispiel, das serverseitig eine Grafik erstellt, wird in der folgenden Abbildung dargestellt:
Sandini Bib 174
3
Einführung in ASP.NET
Abbildung 3.4: Serverseitige Erstellung einer Grafik
Und dazu hier der Quelltext:
Listing 3.6: Serverseitige Erstellung einer Grafik
Die wichtigste Zeile in diesem Beispiel ist die Zeile mit der Methode Save des BitmapObjekts. Die Deklaration der Funktion sieht folgendermaßen aus: public void Save(Stream stream, ImageFormat format)
Der erste Parameter ist ein Stream. Das Bitmap-Objekt schickt die kompletten Bytes, aus denen die Grafik besteht, an diesem Stream. Und der zweite Parameter legt fest, in welchem Format das Bitmap gespeichert werden soll. Dabei gibt es folgende Formate: ImageFormat
Beschreibung
Bmp
Speichert die Daten im Bitmap-Format (bmp)
Emf
Speichert die Daten im Enhanced Windows Metafile-Format (Emf)
Exif
Speichert die Daten im Exchangeable Image-Format (Exif)
Gif
Speichert die Daten im Graphics Interchange-Format (Gif)
Icon
Speichert die Daten im Windows Icon Image-Format (Icon)
Jpeg
Speichert die Daten im Joint Photographics Experts Groups Image-Format (Jpeg)
MemoryBmp
Speichert die Daten in einem Speicherbitmap
Png
Speichert die Daten im W3C Portable Network Graphics Image-Format (Png)
Tiff
Speichert die Daten im Tag Image File-Format (Tiff)
Wmf
Speichert die Daten im Windows Metafile Image-Format (Wmf)
Tabelle 3.6: ImageFormat-Auflistung
Wenn man z.B. den Output einer ASP.NET-Anwendung in eine Datei schreiben will, kann man sich der Funktion WriteFile des HttpResponse-Objektes bedienen: HttpResponse.WriteFile Funtion void Page_Load(Object sender, EventArgs e) { Response.WriteFile("C:\\HtmlOutput.html"); }
Sandini Bib 176
3
Einführung in ASP.NET
Listing 3.7: Verwendung der Funktion HttpResponse.WriteFile
Im vorigen Beispiel wird die komplette Ausgabe in die Datei C:\HtmlOutput.html geschrieben.
Die verschiedenen Phasen einer WebForm Wenn eine WebForm auf dem Client angezeigt wird, werden auf dem Server verschiedene Funktionen durchgeführt, die man in vier Phasen aufgliedern kann: 왘 Konfiguration 왘 Ereignisbehandlung 왘 Anzeige 왘 Entladung Es gibt auch noch andere Phasen, aber diese werden nicht in allen Web-Anwendungen verwendet, da sie speziellere Funktionen durchführen. Diese Phasen werden hauptsächlich für Server-Controls verwendet, die sich in diesen Phasen selbst initialisieren und sich dann selbst auf der ASP.NET-Seite anzeigen. Sehen wir uns diese 4 Phasen jetzt im einzelnen an.
Konfiguration Dies ist die erste Phase, die in einer ASP.NET-Seite aufgerufen wird. Wenn die Phase während eines Postbacks aufgerufen wird, wird der ViewState der Seite und der Controls wiederhergestellt. Danach wird das Ereignis Page_Load ausgelöst. Mithilfe der Funktion Page_Load kann man dieses Ereignis abfangen und behandeln. Das heißt, dass der Code in dieser Funktion auf alle Controls innerhalb der ASP.NET-Seite zugreifen kann. Eine typische Page_Load-Funktion schaut wie folgt aus: void Page_Load(Object sender, EventArgs e) { if (!IsPostBack) { ShoppingCart myShoppingCart = new ShoppingCart(GetConnection()); myShoppingCart.InitializeControls(); }
Sandini Bib Die Page-Klasse und deren Verarbeitung
177
ShoppingCart myShoppingCart = new ShoppingCart(GetConnection()); myShoppingCart.AddSelectedItemToCart(); myShoppingCart.CheckOut(); } Listing 3.8: Beispiel einer Page_Load-Funktion
Wenn die Seite das erste Mal aufgerufen wird, werden die verschiedenen Controls auf der Seite initialisiert. Dies kann durch die Funktion IsPostback abgefragt werden: if (!IsPostBack) { // Seite wird das erste Mal aufgerufen, daher müssen hier die // Controls initialisiert werden }
Wenn die ASP.NET-Seite aufgrund eines Postbacks aufgerufen wird, kann man mit den übergebenen Daten weiterarbeiten. Mithilfe der Params-Auflistung des HttpRequestObjekts kann man auf die verschiedenen Parameter des QueryStrings zurückgreifen: strBookTitle = Request.Params["BookTitle"];
Ereignisbehandlung In dieser Phase einer ASP.NET-Seite werden die Ereignisse behandelt, die die Controls oder die ASP.NET-Seite auslösen. Diese Ereignisse werden dann mithilfe der Ereignisfunktionen auf dem Server abgefangen und behandelt. Das Ereignis wird mithilfe eines Server-Roundtrips aufgerufen. Dieser Aufruf wird durch die Clients initialisiert. Eine Ereignisbehandlungsfunktion hat immer die gleiche Funktionssignatur: Sie besteht aus zwei Parametern. Der erste Parameter gibt das Control an, das das Ereignis ausgelöst hat, und der zweite Parameter gibt zusätzliche Informationen über das Ereignis an. void cmdCalculate_Click(Object sender, EventArgs e) { // Hier kann man jetzt den Code schreiben, der das Ereignis behandelt. }
Das Ereignis cmdCalculate_Click wird aufgerufen, wenn der Benutzer auf den Button CALCULATE klickt. Da dies ein Button-Control ist, wird der Server-Roundtrip sofort gestartet. Im Zuge dieses Roundtrips wird dann die Ereignisfunktion cmdCalculate_Click aufgerufen. Der Button wird mit der folgenden Syntax deklariert:
Sandini Bib 178
3
Einführung in ASP.NET
Wenn man dann im Zuge einer Ereignisbehandlung den Benutzer an eine andere Seite weiterleiten möchte, kann man dazu die Funktion HttpResponse.Redirect verwenden. Response.Redirect("http://www.eu.microsoft.com");
Anzeige Die Anzeigephase ist dann erreicht, wenn die Controls ihren HTML-Output an den Client zurückgeben. Außerdem besteht der HTML-Output z.B. auch aus den Ergebnissen der Funktion Response.Write.
Entladung Dies ist die letzte Phase, die eine ASP.NET-Seite erreichen kann. In dieser Phase wird die Seite verworfen und alle belegten Ressourcen werden freigegeben. Diese Phase ruft das Ereignis Page_Unload auf. Über die Funktion Page_Unload kann man dieses Ereignis serverseitig behandeln. Ein solcher Aufruf würde wie folgt aussehen: void Page_Unload() { // Und hier folgt der Code }
In dieser Funktion kann man z.B. alle Datenbankverbindungen und offenen Dateien schließen. Man sollte alle Objekte explizit zerstören, da man ja nicht genau weiß, wann dies der Garbage Collector durchführt. Ansonsten würde noch für einige Zeit Speicher belegt sein, der nicht mehr vom Programm gebraucht wird.
3.4 Page-Direktiven Wenn man eine ASP.NET-Anwendung erstellt, kann man verschiedene Eigenschaften und Funktionen anhand von so genannten Page-Direktiven deklarativ festlegen. Eine wichtige Direktive ist z.B. die @Page-Direktive, die wir schon weiter vorne des Öfteren kennen gelernt haben. Eine andere wichtige Direktive ist die @Import-Direktive, die einen gewünschten Namespace in die ASP.NET-Anwendung importiert.
@Page-Direktive Über diese Direktive werden der ASP.NET-Seite spezielle Attribute zugewiesen. Diese werden dann vom WebForm-Parser analysiert und durch den Compiler entsprechend umgesetzt. Diese Direktive kann, wie auch die meisten anderen Direktiven, irgendwo auf der Seite verwendet werden. Aber die Konvention legt fest, dass alle Direktiven am Beginn der ASP.NET-Seite deklariert werden. Es ist auch festzustellen, dass nur eine
Sandini Bib Page-Direktiven
179
@Page-Direktive pro Seite deklariert werden kann. Die folgende Tabelle zeigt alle Attribute der @Page-Direktive mit ihren Standardwerten. Attribut
Wert (Standardwert fett)
Verwendung
AspCompat
true oder false
Setzt die Eigenschaften der Seite so, dass sie in einem Single-Thread-Apartment läuft. Dies erlaubt den Zugriff auf COM-Komponenten, die in VB geschrieben wurden.
AutoEventWireup
true oder false
Gibt an, ob die Ereignisse der Seite aktiviert werden
Buffer
true oder false
Gibt an, ob der Ausgabebuffer aktiviert ist
ClassName
Gültiger Klassenname
Gibt den Klassennamen an, von dem die ASP.NETSeite abgeleitet wird
ClientTarget
Gültiger Browsername
Gibt den Browser an, für den die Seite bestimmt ist
CodePage
Gültiger CodePage-Wert
Gibt die Codepage der Antwort an, wenn sie von der Codepage des Servers abweicht
CompilerOptions
Gültige Compileroptionen
Eine Liste alle Compileroptionen, die verwendet werden sollen, wenn die Seite kompiliert wird
ContentType
Gültiger MIME-Typ
Setzt den ContentType der Ausgabe
Culture
Gültige CultureID
Die CultureID setzt die Sprach-, Kalender- und Ausgabeoptionen.
Debug
true oder false
Gibt an, ob bei der Kompilierung der DebugModus aktiviert ist
Description
Beschreibung der ASP.NET-Seite, wird vom Compiler ignoriert
EnableSessionState
true, ReadOnly oder false Gibt an, ob die ASP.NET-Seite Zugriff auf das Session-Objekt hat. Bei ReadOnly hat man Zugriff auf das Session-Objekt, kann aber die Sessionvariablen nicht verändern.
EnableViewState
true oder false
Gibt an, ob der Server den ViewState der Controls und der Seite speichern soll
EnableViewStateMac
true oder false
Gibt an, ob der ViewState durch den Server überprüft werden soll
ErrorPage
Gültige URL
Gibt die URL an, die ASP.NET bei einem Fehler anzeigen soll
Explicit
true oder false
Gibt an, das die Explicit-Option von Visual Basic verwendet werden soll
Tabelle 3.7: Attribute der @Page-Direktive
Sandini Bib 180
3
Einführung in ASP.NET
Attribut
Wert (Standardwert fett)
Verwendung
Inherits
Gültiger Klassenname
Gibt den Namen der Klasse an, von der die ASP.NET-Seite abgeleitet werden soll
Language
Gültige .NET-Programmiersprache
Gibt die .NET-Programmiersprache an, die für die Programmierung des serverseitigen Codes verwendet wurde
LCID
Gültige lokale ID
Gibt die lokale ID an, die verwendet werden soll, wenn sie sich von der ID des Webservers unterscheidet
ResponseEncoding
Gültiger ZeichenEncoding-Name
Encoding-Format, dass von Response verwendet werden soll
SmartNavigation
true oder false
Gibt an, ob die Smartnavigation aktiviert werden soll
Src
Gültige Quelltextdatei
Gibt den Dateinamen für die Code Behind-Datei an
Strict
true oder false
Gibt an, ob die Visual Basic-Option Strict verwendet werden soll
Trace
true oder false
Gibt an, ob Tracing für die ASP.NET-Seite aktiviert ist
TraceMode
SortByTime oder SortByCategorie
Gibt die Sortierreihenfolge der Tracemeldungen an
Transaction
NotSupported, Supported, Required, RequiredNew
Gibt die Transaction-Einstellungen für die ASP.NET-Seite an
Waring Level
0, 1, 2 oder 4
Gibt den Compiler Warning Level an
Tabelle 3.7: Attribute der @Page-Direktive (Forts.)
Wenn man z.B. das Tracing-Attribut aktiviert, werden alle Tracing-Informationen am Ende der ASP.NET-Seite angezeigt. Die folgende Abbildung zeigt einen Teil dieser Informationen.
Sandini Bib Page-Direktiven
181
Abbildung 3.5: Ausgabe der Tracing-Informationen
@Import-Direktive Diese Direktive wird verwendet, um explizit einen Namespace in die ASP.NET-Seite zu importieren. Dadurch kann man bei der Programmierung auf alle Klassen innerhalb dieses Namespaces zugreifen. Man kann einen .NET Framework-Namespace oder einen benutzerdefinierten Namespace importieren. Durch die Verwaltung von Namespaces wird eine Klasse eindeutig identifiziert. Der Import eines Namespaces schaut wie folgt aus:
Da man mit der Import-Direktive nur einen Namespace importieren kann, muss man die Direktive mehrmals verwenden, wenn man mehrere Namespaces importieren will. Das .NET Framework importiert standardmäßig einige Namespaces, die dann durch den Programmierer nicht mehr importiert werden müssen. Diese sind: 왘 System 왘 System.Collections.Specialized 왘 System.IO 왘 System.Text.RegularExpressions
@Implements-Direktive Die @Implements-Direktive ermöglicht, dass eine ASP.NET-Seite eine Schnittstelle implementiert. Wenn man eine Schnittstelle implementiert, garantiert man, dass die ASP.NET-Seite die zur Verfügung gestellten Funktionen, Eigenschaften und Ereignisse implementiert. Ein Beispiel kann folgendermaßen aussehen:
Laut Konvention müssen alle Schnittstellen mit einem großen I beginnen (I steht für Interface).
@Register-Direktive Wenn man ein benutzerdefiniertes Control auf einer ASP.NET-Seite einsetzt, muss man dem Compiler einige Dinge über dieses Control mitteilen. Dies geschieht mithilfe der Direktive @Register. Die Informationen, die dem Compiler mitgeteilt werden, bestehen aus dem Namespace und der Assembly, in denen das benutzerdefinierte Control implementiert wurde. Wenn diese Informationen nicht angegeben wurden, generiert der Compiler einen Fehler, da er das benutzerdefinierte Control nicht finden kann.
Die erste Verwendung der @Register-Direktive dient der Einbindung von benutzerdefinierten Controls auf der ASP.NET-Seite. Der TagPrefix-Parameter gibt an, welcher Tag für das benutzerdefinierte Control verwendet werden soll. Nehmen wir an, dass wir folgende Deklaration haben:
Sandini Bib Page-Direktiven
183
Jedes Mal, wenn wir das Workflow-Control verwenden wollen, müssen wir das Präfix DataDialog angeben:
Das TagName-Präfix gibt an, unter welchem Namen das benutzerdefinierte Control verwendet werden kann. Der letzte Parameter Src gibt an, in welcher Datei das benutzerdefinierte Control implementiert wurde. Die zweite Verwendung der @Register-Direktive dient der Einbindung von benutzerdefinierten Server-Controls. Diese Controls sind kompiliert und in einer Assembly untergebracht. Der TagName-Parameter hat die gleiche Verwendung wie im vorigen Beispiel. Der Namespace-Parameter gibt den Namespace an, in dem das benutzerdefinierte Server-Control implementiert wurde. Und der Assembly-Parameter gibt zum Schluss noch an, in welcher Assembly das benutzerdefinierte Control liegt.
Anschließend können wir das benutzerdefinierte Server-Control mit der folgenden Anweisung auf der ASP.NET-Seite verwenden:
Außerdem können benutzerdefinierten Server-Controls eigens definierte Parameter übergeben werden, wie hier z.B. WorkflowID.
@Assembly-Direktive Mit der @Assembly-Direktive kann man direkt eine Assembly in die ASP.NET-Seite einbinden und die Klassen, die in ihr enthalten sind, direkt verwenden. Man kann entweder die Assembly oder den Pfad der Quelltextdatei, in der sie implementiert wurde, der Direktive übergeben:
oder
Sandini Bib 184
3
Einführung in ASP.NET
@OutputCache-Direktive Mit dieser Direktive kann festgelegt werden, wie die ASP.NET-Seite auf dem Server gecachet werden soll. Die ASP.NET Premium Edition unterstützt leistungsfähige Cachingfunktionen, die die Geschwindigkeit einer ASP.NET-Anwendung beträchtlich erhöhen können. Wenn das Caching für eine ASP.NET-Anwendung aktiviert ist, wird die ASP.NET-Seite beim ersten Aufruf kompiliert und in einen Cache gelegt. Wenn die Seite dann das nächste Mal angefordert wird, wird sie direkt aus dem Cache geladen. Dadurch erhöht sich die Ausführungsgeschwindigkeit von ASP.NET-Anwendung beträchtlich gegenüber den traditionellen AS-Anwendung. Wenn jedoch der Webserver oder der IIS neu gestartet werden müssen, muss auch die ASP.NET-Seite neu kompiliert werden. Sogar wenn eine Anfrage von einem anderen Benutzer kommt, kann die ASP.NETSeite aus dem Cache verwendet werden. Wenn man innerhalb von ASP.NET-Seiten z.B. Datenbankabfragen hat, brauchen diese nur beim ersten Aufruf ausgeführt zu werden, und anschließend wird das Ergebnis aus dem Cache verwendet. Sehen wir uns jetzt an, wie man diese Direktive in einer ASP.NET-Seite verwendet:
Der Duration-Parameter wird verwendet, um die Anzahl der Sekunden festzulegen, die die ASP.NET-Seite zwischengespeichert werden soll. Der Location-Parameter gibt den Ort an, wo die gecachte Seite gespeichert werden soll. Hier gibt es insgesamt fünf Möglichkeiten: Wert
Beschreibung
Any
Der Cache wird auf dem Client, auf einem Downstream-Server (Proxyserver) oder auf dem Webserver verwaltet. Das ist die Standardeinstellung.
Client
Der Cache wird auf dem Client verwaltet, der die Anfrage gesendet hat.
Downstream
Der Cache wird auf einem Downstream-Server, wie einem Proxyserver, verwaltet, der die Anfrage verarbeitet hat.
Server
Der Cache wird auf dem Webserver verwaltet, der die Anfrage bearbeitet hat.
None
Für diese ASP.NET-Seite ist kein Caching aktiviert.
Tabelle 3.8: Mögliche Cache-Einstellungen in ASP.NET
Sandini Bib Page-Direktiven
185
Über den Parameter VaryByCustom können für das Caching benutzerdefinierte Einstellungen festgelegt werden, um mehrere Versionen der ASP.NET-Seite zu cachen. Wenn man folgende Zeile einschließt
wird für jeden Browser eine eigene ASP.NET-Seite gecachet, die nach 60 Sekunden für ungültig erklärt wird. Über den Parameter VaryByHeader kann man für jeden übergebenen http-Header eine eigene ASP.NET-Seite cachen. Im folgenden Beispiel, das die Direktive @OutputCache verwendet, wird eine eigene Version der ASP.NET-Seite gecachet, wenn der Parameter Accept-Language des HTTP Headers anders lautet:
Nehmen wir an, dass die ASP.NET-Seite vier Anfragen mit den folgenden Accept-Language-Werten bekommt: 왘 de-lu 왘 en-us 왘 fr 왘 en-us Da die zweite und vierte Anfrage identisch sind, was die Accept-Language betrifft, wird für diese beiden Anfragen nur eine Version der ASP.NET-Seite gecachet. Für die erste und dritte Anfrage wird eine eigene Version der Seite gecachet, die wiederum nach 60 Sekunden für ungültig erklärt wird. Über den Parameter VaryByParam kann das Caching der ASP.NET-Seite über die übergebenen Parameter gesteuert werden. Nehmen wir folgende Zeile an:
Nachfolgend werden folgende Anfragen an den Webserver gestellt: http://localhost/caching/caching.aspx?Stadt=Graz http://localhost/caching/caching.aspx?Stadt=Wien&Anzahl=5 http://localhost/caching/caching.aspx?Stadt=Innsbruck http://localhost/caching/caching.aspx?Stadt=Graz&Anzahl=10
Für diese vier Anfragen werden drei Versionen der ASP.NET-Seite gecachet, da ja die erste und die vierte Anfrage im Stadt-Parameter gleich sind. Hier wird wiederum nach 60 Sekunden der Cache für ungültig erklärt.
Sandini Bib 186
3
Einführung in ASP.NET
3.5 Code Behind Wie schon im ersten Kapitel erwähnt, war ein wesentlicher Schwachpunkt von ASP, dass der HTML-Code und der ASP-Code in einer Anwendung nicht getrennt waren. Daher war es immer sehr schwer, den Präsentationscode unabhängig vom ASP-Code zu warten. Unter ASP.NET gibt es die Möglichkeit, den HTML-Code und den ASPCode zu trennen. Dadurch werden für ASP.NET-Anwendungen Entwicklungsteams möglich, die getrennt den HTML- und den ASP-Code programmieren können. So können sich die reinen Grafiker um den Präsentationscode und die Programmierer um den eigentlichen Code kümmern. Entwicklungstools wie z.B. das Visual Studio .NET trennen automatisch den HTML- und den ASP-Code. Dieses Feature wird als Code Behind bezeichnet.
Verwendung von Code Behind Um dieses Feature zu nutzen, muss man für eine ASP.NET-Seite explizit eine Klasse vom ASP.NET-Objekt ableiten. Diese Klasse ermöglicht den Zugriff auf die Server-Controls und kontrolliert die Postback-Architektur. Wenn diese Klasse abgeleitet wurde, erzeugt man den HTML-Teil der ASP.NET-Anwendung und weist diesem Teil die abgeleitete Klasse zu. In dieser Page-Klasse muss man diverse Namespaces wie System und System.Web.UI importieren, damit man auf die Basisfunktionalität vom .NET Framework zugreifen kann. Sehen wir uns jetzt das Beispiel mit den Versandmethoden von früher an. Wenn wir dieses Beispiel mithilfe von Code Behind verwirklichen, müssen wir zwei Dateien erstellen: ShippingMethods.aspx und ShippingMethods.aspx.cs. using using using using using
public class ShipMethodsClass : Page { // öffentliche Variablen, die die Verbindung zu den ServerControls auf der ASP.NET Seite herstellen public DropDownList ShipMethod; public Button PlaceOrder; public Label YouSelected; public void Page_Load(Object sender, EventArgs e) { if (!IsPostBack())
Sandini Bib Code Behind
187
{ SqlConnection cnnConnection; SqlCommand cmdCommand; String strConnectionString; String strSql; strSql = "SELECT * FROM Shippers" strConnectionString = "server=localhost;uid=sa;pwd=;database=Northwind"; cnnConnection = new SqlConnection(strConnectionString); cnnConnection.Open(); cmdCommand = new SqlCommand(strSql, cnnConnection); SqlDataReader myReader = cmdCommand.ExecuteReader(); ShipMethod.DataSource = myReader; ShipMethod.DataBind(); } } public void PlaceOrder_Click(Object sender, EventArgs e) { YouSelected.Text = "Ihre Bestellung wird durch " + ShipMethod.SelectedItem.Text + " zugestellt."; } } Listing 3.9: ShippingMethods.aspx.cs Bitte wählen Sie die Versandmethode aus: Listing 3.10: ShippingMethods.aspx
Sandini Bib 188
3
Einführung in ASP.NET
In der ersten Quelltextdatei wird die gewünschte Page-Klasse deklariert. Um diese beiden Dateien miteinander zu verknüpfen, verwendet man die @Page-Direktive in der ASP.NET-Seite. Hier wird die Code-Behind-Datei angegeben:
Der Parameter Inherits gibt den Klassennamen und der Parameter Src den Pfad der Quelltextdatei an. Die Variablen der Server-Controls müssen den gleichen Namen tragen, mit dem sie in der ASP.NET-Seite deklariert wurden, sonst ist das .NET Framework nicht in der Lage, die Controls in der Quelltextdatei anzusprechen. Außerdem müssen diese Variablen als public deklariert werden, damit sie ansprechbar sind: public DropDownList ShipMethod; public Button PlaceOrder; public Label YouSelected;
Alle Beispiele in diesem Buch werden das Code-Behind-Feature nicht benutzen, da die Darstellung der Beispiele ohne dieses Feature viel leichter ist, da der Code kürzer wird. Aber wie gesagt, Visual Studio .NET verwendet dieses Feature standardmäßig, da es bei größeren Projekten eine wirkliche Trennung zwischen Präsentationscode und Anwendungscode ermöglicht.
3.6 Server-Controls Auf den vorherigen Seiten haben wir schon einige Server-Controls im Einsatz gesehen. In diesem Abschnitt wollen wir uns diesen Server-Controls jetzt näher widmen. Die Server-Controls sind das Herz von ASP.NET. Sie erlauben ein Programmiermodell, in dem die Benutzer Ereignisse auf dem Client auslösen. Diese werden dann anschließend auf dem Server abgearbeitet. Einen solchen Vorgang nennt man Server-Roundtrip. Durch dieses Programmiermodell sind Anwendungen, die die gleiche Funktionalität wie Windows-Anwendungen haben, möglich. Wie wir bereits wissen, ist das .NET Framework ja komplett erweiterbar, was auch bedeutet, dass man seine eigenen Server-Controls programmieren kann. Außerdem kann man Server-Controls von Drittanbietern integrieren. In diesem Abschnitt wollen wir die Antworten auf die folgenden Fragen klären: 왘 Was sind Server-Controls 왘 Wie kann man interaktive ASP.NET-Seiten mit ihnen programmieren 왘 Beschreibung aller Server-Controls Wie wir bereits gesehen haben, basiert ASP.NET auf den Server-Controls und auf den WebForm-Controls. Die WebForm-Controls werden im nächsten Kapitel näher beschrie-
Sandini Bib Server-Controls
189
ben. Wenn man eine traditionelle Windows-Anwendung ansieht, basiert alles auf Ereignissen. Dies war aber in ASP nicht vorgesehen. Hier gab es keine Ereignisse. Aber mit ASP.NET hat sich das geändert. Server-Controls und WebForm-Controls können Ereignisse clientseitig auslösen, die anschließend serverseitig abgefangen und behandelt werden können. Dies ermöglicht Anwendungen, die die gleiche Funktionalität wie unsere heutigen Windows-Anwendungen haben.
Verwendung von Html-Server-Controls Wenn ein Anwender auf einen Button klickt, wird ein Ereignis ausgelöst, das durch einen serverseitigen Eventhandler abgefangen und bearbeitet wird. Sehen wir uns die Deklaration eines Server-Controls an:
Die dazugehörige serverseitige Ereignisfunktion wird wie folgt implementiert: void MeineFunktion(Object sender, EventArgs e) { // Hier folgt der eigentliche Code, der das Ereignis behandelt }
Wie wir in diesem Beispiel gesehen haben, werden die Server-Controls auf die eigentlichen HTML-Elemente wie z.B. abgebildet. Dies hat auch einen guten Grund: dadurch wird die Portierung existierender ASP-Anwendungen auf ASP.NET erleichtert, da man nur das runat-Attribut dem Server-Control hinzufügen muss, damit es ein echtes Server-Control wird. Wenn man eine neue ASP.NET-Anwendung zu programmieren anfängt, sollte man die leistungsfähigeren WebForm-Controls verwenden, auf die im nächsten Kapitel näher eingegangen wird. Wenn ein Server-Control auf der ASP.NET-Seite deklariert wurde, kann es anschließend im serverseitigen Code angesprochen werden und ist außerdem ein vollwertiges Objekt unter dem .NET Framework. Das bedeutet, dass man auf alle Funktionen, Eigenschaften und Ereignisse des Server-Controls Zugriff hat. Wie in traditionellen Anwendungen kann man auf die Werte von Textboxen, Buttons, ListControls usw. Zugriff bekommen. In der folgenden Page_Load-Funktion wird z.B. auf einige Werte von Server-Controls zugegriffen:
Wann soll man jetzt die Server-Controls anstelle von normalen Html-Elementen nehmen? Soll z.B., wenn man eine Textbox benötigt, eine normale Html-Textbox oder ein Server-Control verwenden? Insgesamt gibt es für dieses Problem drei Lösungen: 왘 Verwendung des Html-Elementes 왘 Verwendung des Server-Controls 왘 Verwendung des WebForm-Controls Bei der Verwendung des Html-Elementes sieht die Deklaration wie folgt aus:
Wenn wir daraus ein Server-Control machen wollen, müssen wir den Parameter runat hinzufügen und den Parameter name auf id ändern:
Ein WebForm-Control wird auf folgende Weise deklariert:
Wenn man den runat Parameter hinzufügt, wird das Server-Control bzw. das WebForm-Control bei der Seitenanforderung kompiliert und ist dann im serverseitigen Code für diverse Bearbeitungen verfügbar. Das heißt, wenn wir z.B. auf eine Textbox serverseitig zugreifen wollen, müssen wir entweder ein Server-Control oder ein WebForm-Control erstellen. Wenn wir aber keinen serverseitigen Zugriff brauchen, kann man ein normales HtmlElement erstellen. In folgenden Situationen brauchen z.B. keine Server-Controls oder WebForm-Controls:
Sandini Bib Server-Controls
191
왘 Wenn das Element während der Ausführung nur clientseitig verwendet wird, z.B. durch JavaScript. 왘 Wenn das Element nur ein Submit-Button ist, der die WebForm an den Server schickt. 왘 Wenn das Element ein Hyperlink ist, der eine andere URL öffnet 왘 Immer dann, wenn das Element serverseitig durch den Servercode nicht angesprochen wird. Nach den Erklärungen auf den vorhergehenden Seiten sollte man jetzt in der Lage sein, die Vorteile von Server-Controls aufzuzählen: 왘 Sie generieren HTML-Code, der das Control im Browser implementiert. 왘 Ein Server-Control, das auf einer Seite deklariert wurde, kann serverseitig durch Code angesprochen werden. 왘 Automatische Verwaltung des ViewStates des Controls. 왘 Einfacher Zugriff auf das Server-Control, ohne dass man das Request-Objekt benötigt. 왘 Es gibt die Möglichkeit, Ereignisse auszulösen und diese abzufangen. Dadurch werden strukturierte Anwendungen möglich. 왘 Eine einfache Möglichkeit, um leistungsfähige Web-Anwendungen zu erstellen, die die gleiche Funktionalität wie Windows-Anwendungen haben. 왘 Eine einfache Möglichkeit, um mehrere Zielgeräte ohne Codeänderung zu unterstützen. Die Server-Controls, die das .NET Framework bietet, gliedern sich grob in sechs große Gruppen: 왘 HTML Server-Controls sind die serverbasierte Variante der HTML-Elemente. Sie generieren eine HTML-Ausgabe, die fast genauso wie die entsprechenden HTMLElemente aussieht. 왘 ASP.NET Validation-Controls sind eine Gruppe von spezialisierten Controls, die den Inhalt von verschiedenen Controls überprüfen können. Es ist daher sehr einfach, die Eingabe des Benutzers zu überprüfen und zu validieren. Sie unterstützen die Überprüfung clientseitig, serverseitig oder auf beiden Seiten. Wenn z.B. auf dem Zielbrowser JavaScript nicht aktiviert ist, wird die Überprüfung serverseitig vorgenommen. Sie passen sich sozusagen dem Zielsystem an.
Sandini Bib 192
3
Einführung in ASP.NET
왘 ASP.NET WebForm-Controls sind sehr leistungsfähige Controls, die in einem -Element verwendet werden, wie z.B. Textboxen, Hyperlinks und andere Elemente. Den WebForm-Controls widmen wir uns im nächsten Kapitel eingehender. 왘 ASP.NET List Controls bieten eine große Auswahl, wenn man Listen auf einer ASP.NET Seite erstellen möchte. Diese Listen kann man an eine beliebige Datenquelle binden, ohne dass man sich explizit über die Darstellung der Daten kümmern muss. 왘 ASP.NET Rich Controls sind Controls wie z.B. das Calendar-Control oder der Ad Rotator. Sie generieren eine sehr komplexe Ausgabe, die sich sehr oft clientseitig JavaScript bedient. 왘 ASP.NET Mobile Controls sind separate Controls, die für die Programmierung von mobilen Geräten, wie z.B. Handheld PCs, verwendet werden. Sie generieren wiederum eine unterschiedliche Ausgabe, je nachdem, welches Zielgerät die Seite mit den Mobile Controls aufruft. Die HTML Server-Controls sind im Namespace System.Web.UI.HtmlControls implementiert. Hier gibt es einige Basisklassen, von denen die anderen Controls abgeleitet wurden.
HtmlControl-Basisklasse Die Basisklasse für alle HTML Server-Controls ist SystemWeb.UI.HtmlControls.HtmlControl. Diese Klasse beinhaltet Methoden, Eigenschaften und Ereignisse, die für alle Html Controls gleich sind: Mitglied
Beschreibung
Attributes-Eigenschaft
Gibt eine Collection mit den Attributen für das Control auf der ASP.NET-Seite zurück. Diese Collection besteht aus einem Namen/ Werte-Paar.
ClientID-Eigenschaft
Gibt die ID für das Control zurück, die von ASP.NET generiert wurde
Controls-Eigenschaft
Gibt eine ControlCollection für das Objekt zurück, das die ChildControls vom aktuellen Control enthält
Disabled-Eigenschaft
Über diese Eigenschaft kann eingestellt werden, ob das Control aktiviert oder deaktiviert sein soll.
EnableViewState-Eigenschaft
Über diese Eigenschaft kann eingestellt werden, ob für das Control das ViewState Management aktiviert oder deaktiviert werden soll. Standardmäßig ist das ViewState Management für die Controls eingestellt.
ID-Eigenschaft
Setzt oder liefert die ID vom Control, die vom Programmierer festgelegt wurde
Tabelle 3.9: Mitglieder von HtmlControl
Sandini Bib Server-Controls
193
Mitglied
Beschreibung
Page-Eigenschaft
Gibt eine Referenz auf das Page-Objekt zurück, das das Control enthält
Parent-Eigenschaft
Gibt eine Referenz auf das Parent-Control zurück, das das Control enthält
Style-Eigenschaft
Ist eine Referenz auf eine Collection von CSS Style Eigenschaften, die auf dieses Control angewendet werden
TagName-Eigenschaft
Gibt den Namen des Elementes zurück, wie z.B. oder
Visible-Eigenschaft
Setzt oder liefert, ob das Control auf der ASP.NET-Seite sichtbar ist. Standardmäßig ist diese Eigenschaft auf true eingestellt, was bedeutet, dass das Control auf der ASP.NET-Seite sichtbar ist
DataBind-Methode
Führt die Datenbindung für das Control und seine Child-Controls aus
FindControl.Methode
Sucht im aktuellen Container nach dem übergebenen Control
HasControls-Methode
Gibt true oder false zurück, je nachdem, ob das aktuelle Controls Child-Controls hat oder nicht
DataBinding-Ereignis
Tritt auf, wenn das Control an eine Datenquelle gebunden wird
Tabelle 3.9: Mitglieder von HtmlControl (Forts.)
HtmlGenericControl Klasse Wenn man sich die HTML-Spezifikation des W3C durchsieht, wird man sicherlich auf rund 100 HTML-Elemente kommen, die in der Spezifikation definiert wurden. Da aber im .NET Framework nicht für alle diese Elemente eigene Klassen eingeführt wurden, wurde eine Klasse geschaffen, die all die Eigenschaften und Methoden für viele dieser Controls implementiert. Die Problematik wird noch dadurch erweitert, dass die HTML-Elemente auf verschiedenen Browser anders abgebildet werden. Wenn wir z.B. ein -Element auf einer ASP.NET-Seite deklarieren, kann man folgendermaßen darauf zugreifen: void Page_Load(Object sender, EventArgs e) { divResult.InnerHtml = strResult; }
Wenn z.B. der Wert der Variable strResult "Das ist das Ergebnis" ist, wird folgender HTML-Code durch ASP.NET generiert: Das ist das Ergebnis
Sandini Bib 194
3
Einführung in ASP.NET
Die HtmlGenericControl-Klasse hat zwei wichtige Eigenschaften: Eigenschaft
Beschreibung
InnerHtml
Über diese Eigenschaft hat man auf die komplette HTML-Beschreibung des ServerControls Zugriff (zB: ...)
InnerText
Über diese Eigenschaft hat man auf die Textrepräsentation des Server-Controls Zugriff (z.B: Das ist ein Test)
Tabelle 3.10: Eigenschaften von HtmlGenericControl
Für jedes Control, das man in einem -Element verwenden kann, gibt es eine eigene Klasse. Durch diese Klasse hat man dann auf spezielle Eigenschaften, Methoden und Ereignisse des Controls Zugriff. Sehen wir uns die verschiedenen Controls mit ihren Eigenschaften und Ereignissen an: HTML Element
Tabelle 3.11: Die verschiedenen HTML-Server-Controls (Forts.)
Zuweisen von Style-Eigenschaften Alle Server-Controls (auch die WebForm-Controls) haben eine Style-Eigenschaft, über die man dem Control eine CSS-Eigenschaft zuweisen kann. Man kann z.B. einem -Element folgenden Style zuweisen: void Page_Load(Object sender, EventArgs e) { divResult.InnerHtml = "Großer, roter Text"; divResult.Style["font-family"] = "Tahoma"; divResult.Style["font-weight"] = "bold"; divResult.Style["font-size"] = "30px"; divResult.Style["color"] = "red"; } Listing 3.12: Zuweisung von Styles an Server-Controls
Sandini Bib 196
3
Einführung in ASP.NET
Und so schaut das Ergebnis aus:
Abbildung 3.6: Zuweisung von Styles an Server-Controls
Wenn wir uns den Quelltext im Browser ansehen, werden wir ein Style-Attribut zu sehen bekommen, das wie folgt aussieht: Großer, roter Text
Im nächsten Abschnitt schauen wir uns jetzt die verschiedenen HTML Server-Controls im Detail an.
Das HtmlGenericControl-Klasse Dies ist die einzige Klasse, die mehrere HTML-Elemente abbilden kann. Die TagNameEigenschaft ist bei der Klasse auf schreibbar gesetzt. Das HtmlGenericControl kann dazu verwendet werden, jedes beliebige HTML-Element auszugeben. Das Control wird wie folgt deklariert: HtmlGenericControl
Man kann dann z.B. mit der Zeile HtmlGenericControl MyGenericControl;
eine Variable anlegen, mit der man dann auf die verschiedenen Methoden, Eigenschaften und Ereignisse der HtmlGenericControl-Klasse zugreifen kann. Über die Zeile MyGenericControl.Attributes["bgcolor"] = "red";
könnte man z.B. die Hintergrundfarbe des -Elementes setzen.
Sandini Bib Server-Controls
197
Das HtmlAnchor-Control Mit diesem Control kann man das HTML-Element programmieren. Das Frame für den Link kann man über den Parameter Target angeben. Hier können auch die Werte _blank, _self, _parent und _top angegeben werden. Sie haben die gleiche Bedeutung wie in der HTML-Spezifikation. Die Syntax für das Deklarieren eines HtmlAnchorControl sieht folgendermaßen aus: Linktext
Das folgende Beispiel zeigt, wie man das HtmlAnchor-Control gemeinsam mit dem Repeater-Control einsetzt, um Hyperlinks aus einer Datenquelle zu erstellen. void Page_Load(Object sender, EventArgs e) { DataTable dt = new DataTable(); DataRow dr; dt.Columns.Add(new dt.Columns.Add(new dt.Columns.Add(new dt.Columns.Add(new dt.Columns.Add(new
Listing 3.13: Verwendung der Datenbindung für das HtmlAnchor-Control
Die Ausgabe des Beispiels sieht folgendermaßen aus:
Abbildung 3.7: Datenbindung mit dem HtmlAnchor Control
Sandini Bib Server-Controls
199
Der Link für das HtmlAnchor-Control könnte folgendermaßen dynamisch beim Aufruf der ASP.NET Seite geändert werden: void Page_Load(Object sender, EventArgs e) { MyAnchorControl.HRef = "http://www.microsoft.com"; }
Das HtmlImage-Control Wenn wir ein Bild im Browser darstellen, und darauf serverseitig zugreifen wollen, kann man sich des HtmlImage-Controls bedienen. Es regelt den Zugriff mit dem HTML-Element . Das HtmlImage-Control wird wie folgt auf einer ASP.NET-Seite deklariert:
Das folgende Beispiel zeigt, wie man die Anzeige eines Bildes aufgrund der Eingabe des Benutzers anpassen kann. Dabei wird im EventHandler für cmdSubmit_Click im Image-Verzeichnis nach der Datei gesucht, die der Benutzer in der Listbox ausgewählt hat. void cmdSubmit_Click(Object sender, EventArgs e) { ImageCtrl.Src = "images/" + SelectCtrl.Value; } Einfaches HtmlImage Beispiel
Bitte wählen Sie eine Grafikdatei zur Anzeige aus: Bild 1 Bild 2 Bild 3 Bild 4 Bild 5
Sandini Bib 200
Listing 3.14: Verwendung des HtmlImage-Controls
Die Ausgabe sieht wie folgt aus:
Abbildung 3.8: Ausgabe der HtmlImage-Klasse
3
Einführung in ASP.NET
Sandini Bib Server-Controls
201
Das HtmlForm-Control Mit diesem Server-Control kann man das HTML-Element programmieren. Man kann auf einer ASP.NET-Seite nur ein -Element definieren. Standardmäßig ist die Methode der HtmlForm auf POST gesetzt, und der Action-Parameter ist auf die Url der eigenen Seite gesetzt, was bedeutet, dass ein Postback durchgeführt wird, wenn die ASP.NET-Seite abgeschickt wird. Mit diesem Control kann man z.B. den Action-Parameter anstatt auf POST auf GET setzen. Dadurch wird aber der komplette View-State- und Postback-Mechanismus von ASP.NET deaktiviert, da diese Features auf HTTP-POST basieren. Die Deklaration eines HtmlForm-Controls erfolgt mit folgender Syntax: Andere Controls usw.
Das folgende Beispiel zeigt drei HtmlButton Controls. Für jeden Klick auf einen der Button wird ein eigener Ereignishandler aufgerufen. In diesem Handler wird dann der Name des ausgewählten Buttons in einem -Element angezeigt. void cmdButton1_Click(Object sender, EventArgs e) { Span1.InnerHtml = "Sie haben den Button 1 ausgewählt."; } void cmdButton2_Click(Object sender, EventArgs e) { Span2.InnerHtml = "Sie haben den Button 2 ausgewählt."; } void cmdButton3_Click(Object sender, EventArgs e) { Span3.InnerHtml = "Sie haben den Button 3 ausgewählt."; } Einfaches HtmlForm Beispiel Button 1 &nbps;
Sandini Bib 202
3
Einführung in ASP.NET
Button 2 &nbps;
Button 3 Listing 3.15: Verwendung des HtmlForm-Controls
Die Ausgabe aus dem vorherigen Beispiel ist die folgende:
Abbildung 3.9: Ausgabe des HtmlForm-Controls
Sehen wir uns jetzt an, wie die Augabe der ASP.NET-Seite auf dem Client aussieht:
Wie schon vorher gesagt, wird der Methoden-Parameter auf HTTP-POST gesetzt und der Action-Parameter auf die gleiche ASP.NET-Seite. Dadurch wird die PostbackArchitektur in ASP.NET implementiert.
Sandini Bib Server-Controls
203
Wenn über ein -Element eine Datei auf den Webserver geladen werden soll, muss noch folgender Parameter in der ASP.NET-Seite gesetzt werden:
Das HtmlButton-Control Mit diesem Control kann man das HTML-Element programmieren. Das Element ist ein Container, was bedeutet, dass innerhalb von einem andere Controls deklariert werden können. Der gesamte Inhalt wird auf den Button selbst gerendert. Durch diesen Mechanismus kann man so genannte ImageButtons erstellen: Klick auch auf mich! Mit RollOver-Effekt
Listing 3.16: Verwendung des HtmlButton-Controls
Sandini Bib Server-Controls
205
Das Beispiel erzeugt folgende Ausgabe:
Abbildung 3.11: Verwendung eines HtmlButton Controls
Das HtmlInputButton Control Mit diesem Control wird auf die HTML-Elemente , und zugegriffen. Das HtmlInputButton-Control wird dazu verwendet, um diese drei Arten von Buttons zu erstellen und zu programmieren. Syntax zur Erstellung eines HtmlInputButton:
Der folgende Quelltext zeigt ein einfaches Beispiel für eine Benutzerauthentifizierung. Die ASP.NET-Seite besteht aus zwei Textfeldern, in die der Benutzer seinen Benutzernamen und sein Passwort eingeben kann. Über das HtmlInputButton-Control werden zwei Buttons erstellt, wobei einer den Anmeldevorgang einleitet und der andere die Anmeldung abbricht. void cmdSubmit_Click(Object sender, EventArgs e) { if ((Name.Value == "aks") && (Password.Value == "Porsche")) { Message.InnerHtml = "Benutzeranmeldung war erfolgreich."; } else { Message.InnerHtml = "Benutzeranmeldung ist fehlgeschlagen.";
Sandini Bib 206
3
Einführung in ASP.NET
} } void cmdReset_Click(Object sender, EventArgs e) { Name.Value = ""; Password.Value = ""; Message.InnerHtml = ""; } Bitte geben Sie Ihren Namen ein: Bitte geben Sie Ihr Passwort ein: Listing 3.17: Verwendung des HtmlInputButton-Controls
Die folgende Abbildung zeigt die Ausgabe des Beispielcodes:
Abbildung 3.12: Verwendung des HtmlInputButton-Controls
Sandini Bib Server-Controls
207
Das HtmlInputText-Control Mit diesem Control kann man die HTML-Elemente und programmieren. Mit diesem Control kann man eine einzeilige Textbox erzeugen. Wenn der Passwortmodus aktiv ist, wird jeder eingegebene Buchstabe durch einen Stern dargestellt, wodurch das Passwort für Dritte nicht ablesbar ist. Die Syntax für die Deklaration sieht wie folgt aus:
Im folgenden Beispiel kann der Benutzer in ein HtmlInputText-Control seinen Namen eingeben. Wenn er anschließend auf den Button klickt, wird der eingegebene Name in einem -Element angezeigt. void cmdShow_Click(Object sender, EventArgs e) { Message.InnerHtml = "Ihr Name ist " + Name.Value; } Bitte geben Sie Ihren Namen ein: Listing 3.18: Verwendung des HtmlInputText-Controls
Das Beispiel generiert folgende Ausgabe:
Sandini Bib 208
3
Einführung in ASP.NET
Abbildung 3.13: Verwendung des HtmlInputText Controls
Das HtmlInputCheckBox-Control Wenn man das HTML-Element programmieren will, steht einem das HtmlInputCheckBox-Control zur Verfügung. Über die Eigenschaft Checked kann man feststellen, ob das Control markiert wurde. Die Syntax für die Deklaration sieht wie folgt aus:
Im nächsten Beispiel wird die Verwendung dieses Controls veranschaulicht. Auf der ASP.NET-Sete werden eine Checkbox und ein Button definiert. Im Eventhandler des Buttons wird dann ermittelt, ob die Checkbox markiert wurde, und eine entsprechende Meldung ausgegeben. void cmdShow_Click(Object sender, EventArgs e) { if (chkCheckBox.Checked == true) { Span1.InnerHtml = "Die Checkbox wurde markiert."; } else { Span1.InnerHtml = "Die Checkbox wurde nicht markiert."; } }
Sandini Bib Server-Controls
209
HtmlInputCheckBox Beispiel Checkbox
Listing 3.19: Verwendung des HtmlInputCheckBox-Controls
Das Beispiel erzeugt folgende Ausgabe:
Abbildung 3.14: Verwendung des HtmlInputCheckBox-Controls
Das HtmlInputRadioButton-Control Wenn man einen Radiobutton mit dem HTML-Element abbilden möchte, kann man das HtmlInputRadioButton-Control verwenden. Über die Eigenschaft Checked kann man wiederum feststellen, ob das Control durch den Benutzer ausgewählt wurde. Die Eigenschaft Name legt den Gruppennamen des Controls fest. Wenn z.B. innerhalb einer Gruppe ein Control markiert wird, wird beim vorhergehenden Control, das markiert war, die Markierung aufgehoben.
Sandini Bib 210
3
Einführung in ASP.NET
Die Deklaration sieht wie folgt aus:
Im folgenden Beispiel werden zwei HtmlInputRadioButton-Controls deklariert, die zur gleichen Gruppe gehören. Daher kann immer nur ein Control dieser Gruppe momentan aktiv sein. Im Ereignis OnServer_Change wird festgestellt, welches Control markiert ist, und eine entsprechende Meldung ausgegeben. void OnServer_Change(Object sender, EventArgs e) { if (Radio1.Checked == true) { Span1.InnerHtml = "Option 1 wurde markiert."; } else if(Radio2.Checked == true) { Span1.InnerHtml = "Option 2 wurde markiert."; } else if(Radio2.Checked == true) { Span1.InnerHtml = "Option 3 wurde markiert."; } } HtmlInputRadioButton Beispiel Option 1 Option 2 Option 3
Listing 3.20: Verwendung des HtmlInputRadioButton-Controls
Sandini Bib Server-Controls
211
Die Ausgabe des Beispiels sieht wie folgt aus:
Abbildung 3.15: Verwendung des HtmlInputRadioButton Controls
Das HtmlInputImage-Control Wenn man das HTML-Element programmieren möchte, kann man sich der Klasse HtmlInputImage bedienen. Die Deklaration des Controls sieht folgendermaßen aus:
Das folgende Beispiel implementiert zwei HtmlInputImage-Controls, die über DHTMLEreignisse genauer gesteuert werden. Über die DHTML-Ereignisse werden die Bilder ausgetauscht, wenn der Mauszeiger außerhalb bzw. innerhalb des Controls ist. void cmButton1_Click(Object sender, ImageClickEventArgs e) { Span1.InnerHtml = "Sie haben auf Button 1 geklickt."; } void cmdButton2_Click(Object sender, ImageClickEventArgs e)
Sandini Bib 212
3
Einführung in ASP.NET
{ Span1.InnerHtml = "Sie haben auf Button 2 geklickt."; } HtmlInputImage Beispiel
Mit RollOver Effekt
Listing 3.21: Verwendung des HtmlInputImage-Controls
Das Beispiel erzeugt folgende Ausgabe:
Abbildung 3.16: Verwendung des HtmlInputImage-Controls
Sandini Bib Server-Controls
213
Das HtmlInputFile-Control Mit diesem Control kann man das HTML-Element programmieren. Dieses Control kann man dazu verwenden, um den Benutzern zu ermöglichen, verschiedene Dateien auf den Webserver zu laden. Unter ASP waren für diesen Vorgang Komponenten von Drittanbietern notwendig. Das .NET Framework bietet dafür eine direkte Unterstützung an. Sehen wir uns die Syntax für die Deklaration des Controls an:
Das nächste Beispiel zeigt uns ein einfaches und immer wiederkehrendes Szenario: Ein Benutzer möchte eine Datei auf den Webserver laden. Das Beispiel deklariert ein HtmlInputFile-Control und einen Button, der den Download startet. Zu beachten ist noch, dass der Parameter enctype des -Elementes auf multipart/form-data gesetzt werden muss. void cmdButton_Click(Object sender, EventArgs e) { // Anzeigen der Informationen über die Datei MyFileName.InnerHtml = MyFile.PostedFile.FileName; MyContentType.InnerHtml = Myfile.PostedFile.ContentType; MyContentLength.InnerHtml = MyFile.PostedFile.ContentLength.ToString(); FileDetails.Visible = true; // Datei auf den Webserver kopieren MyFile.PostedFile.SaveAs("C:\\UploadedFiles\\UploadFile.txt"); } ASP.NET File Upload Beispiel Bitte wählen Sie die Datei zum Upload aus:
Sandini Bib 214
3
Einführung in ASP.NET
Dateiname: ContentType: ContentLength: Listing 3.22: Verwendung des HtmlInputFile-Controls
Die nächste Abbildung zeigt die Ausgabe des Beispiels:
Abbildung 3.17: Verwendung des HtmlInputFile-Controls
Das HtmlInputHidden-Control Mit dem HtmlInputHidden-Control kann man das -Element programmieren. Dieses Control kann man gemeinsam mit den Controls HtmlInputButton und HtmlInputText verwenden, um Informationen über den Benutzer zu erzeugen, zu speichern und zu verändern.
Sandini Bib Server-Controls
215
Die Deklaration des Controls sieht folgendermaßen aus:
Das folgende Beispiel zeigt, wie man wärend des Server-Roundtrips Informationen in einem HtmlInputHidden-Control speichern kann. void Page_Load(Object sender, EventArgs e) { if (Page.IsPostBack) { Span1.InnerHtml = "Hidden Value: " + HiddenValue.Value + ""; } } void cmdSubmit_Click(Object sender, EventArgs e) { HiddenValue.Value = StringContents.Value; } HtmlInputHidden Control Beispiel Bitte geben Sie eine Zeichenfolge ein:
Hier wird der Text angezeigt, der zuvor eingegeben wurde. Listing 3.23: Verwendung des HtmlInputHidden-Controls
Das Beispiel erzeugt die folgende Ausgabe:
Sandini Bib 216
3
Einführung in ASP.NET
Abbildung 3.18: Verwendung des HtmlInputHidden-Controls
Das HtmlSelect-Control Dieses Control kann man dazu verwenden, um das HTML-Element zu programmieren. Ein solches HtmlSelect-Control kann man z.B. auch an eine Datenquelle binden. Dies sieht dann folgendermaßen aus: HashTable myTable = new HashTable(5); myTable.Add("Österreich", "Wien"); myTable.Add("Italien", "Rom"); myTable.Add("Deutschland", "Berlin"); myTable.Add("England", "London"); myTable.Add("Frankreich", "Paris"); mySelectControl.DataSource = myTable; mySelectControl.DataBind();
Anstatt das HtmlSelect-Control an eine Datenquelle zu binden, kann man auch direkt -Elemente verwenden: Wert 1 Wert 2 Wert 3 Wert 4
Die -Elemente brauchen kein runat="server", da sie automatisch in ListItem Objekte konvertiert werden, wenn die ASP.NET-Seite kompiliert wird.
Sandini Bib Server-Controls
217
Die Syntax für die Deklaration des Controls sieht wie folgt aus: Wert 1 Wert 2 Wert 3
Das folgende Beispiel verwendet ein HtmlSelect-Control, um die Hintergrundfarbe eines -Elementes zu ändern. Außerdem ist es möglich, neue Einträge für die Auswahl der Hintergrundfarbe hinzuzufügen: void cmdApply_Click(Object sender, EventArgs e) { Span1.Style["background-color"] = ColorSelect.Value; } void cmdAdd_Click(Object sender, EventArgs e) { ColorSelect.Items.Add(Text1.Value); } HtmlSelect Beispiel Bitte wählen Sie eine Farbe aus: SkyBlue LightGreen Gainsboro LemonChiffon
CellContent
CellContent
CellContent
Die Deklaration des HtmlTableCell-Controls hat folgende Syntax:
CellContent
Beim folgenden Beispiel kann der Benutzer die Anzahl der Tabellenspalten und Tabellenzeilen eingeben und die ASP.NET-Seite erstellt dann dynamisch die gewünschte Tabelle. void cmd_ViewTable(Object sender, EventArgs e) { int nRows = Convert.ToInt32(TextRows.Value.ToString()); int nCells = Convert.ToInt32(TextCells.Value.ToString()); int nRowCount, nCellCount; HtmlTableRow myRow; HtmlTableCell myCell; for(nRowCount=0;nRowCount void MyCheckBox1_Changed(Object sender, EventArgs e) { lblMessage.Text = "CheckBox 1 Checked: " + MyCheckBox1.Checked; } void MyCheckBox2_Changed(Object sender, EventArgs e) { lblMessage.Text = "CheckBox 2 Checked: " + MyCheckBox2.Checked; } void MyCheckBox3_Changed(Object sender, EventArgs e) { lblMessage.Text = "CheckBox 3 Checked: " + MyCheckBox3.Checked; } void cmdSubmit_Click(Object sender, EventArgs e) { lblMessage.Text = "CheckBox 1 Checked: " + MyCheckBox1.Checked + ""; lblMessage.Text += "CheckBox 2 Checked: " + MyCheckBox2.Checked + ""; lblMessage.Text += "CheckBox 3 Checked: " + MyCheckBox3.Checked; } void cmdShow_Click(Object sender, EventArgs e) { String strConnectionString = "data source=localhost;initial catalog=northwind;integrated security=sspi;"; SqlConnection cnnConnection = new SqlConnection(strConnectionString); SqlDataAdapter myAdapter = new SqlDataAdapter("select Productname, QuantityPerUnit from Products", cnnConnection); DataSet ds = new DataSet(); myAdapter.Fill(ds, "Products");
Sandini Bib 268
4
WebForm-Controls
Abbildung 4.11: Verwendung der WebForm-Controls Table, TableRow und TableCell foreach(DataRow myDataSetRow in ds.Tables["Products"].Rows) { TableRow myRow = new TableRow(); TableCell myCell = new TableCell(); myCell.Text = myDataSetRow["ProductName"].ToString(); myRow.Cells.Add(myCell); myCell = new TableCell(); myCell.Text = myDataSetRow["QuantityPerUnit"].ToString(); myRow.Cells.Add(myCell); myTable.Rows.Add(myRow); } cnnConnection.Close(); } Table Beispiel mit Datenanbindung
Sandini Bib Standard WebForm-Controls
269
Listing 4.12: Datenanbindung beim WebForm-Control Table
Das Beispiel erzeugt die folgende Ausgabe:
Abbildung 4.12: Datenanbindung beim WebForm-Control Table
Die WebForm-Controls Literal und PlaceHolder Mit diesen beiden Controls kann man benutzerdefinierten Text auf einer ASP.NETSeite ausgeben lassen. Im vorletzten Beispiel mit dem TableCell-Control haben wir bereits gesehen, wie man ein solches WebForm-Control sinnvoll einsetzen kann. Wenn man z.B. Folgendes deklariert: http://www.microsoft.com/windows2000 Windows 2000 20
Sandini Bib Rich Controls
281
Windows windowsxp.gif http://www.microsoft.com/windowsxp Windows XP 20 Windows Listing 4.17: Advertisements.xml void MyAdRotator1_Created(Object sender, AdCreatedEventArgs e) { lblOutput.Text = "AdRotator erzeugt."; } AdRotator Beispiel
Listing 4.18: Verwendung des AdRotator-Controls
Das Beispiel erzeugt die in Abbildung 4.16 gezeigte Ausgabe.
Das Calendar-Control Das komplexeste Steuerelement in ASP.NET ist sicherlich das Calendar-Control. Mit diesem Control ist es möglich, einen vollständigen Kalender zu erzeugen, auf dem man jedes beliebige Datum auswählen kann. Dabei wird auf der ASP.NET-Seite immer ein ganzer Monat angezeigt. Wenn man einen anderen Monat ansehen möchte, kann man bequem über das Steuerelement auswählen. Clientseitig wird das Calendar-Control durch eine Html-Tabelle realisiert. Bei diesem Control wird auch clientseitig sehr stark JavaScript verwendet, was bedeutet, dass eine sehr hohe Performance zu erreichen ist.
Sandini Bib 282
4
WebForm-Controls
Abbildung 4.16: Verwendung des AdRotator-Controls
Im folgenden Beispiel wird gezeigt, wie ein solches Control aussehen kann: void OnDayChanged(Object sender, EventArgs e) { IntermediateResult.Text = "Ausgewähltes Datum: " + MyCal.SelectedDate.ToShortDateString(); } void cmdSubmit_Click(Object sender, EventArgs e) { Result.Text = "Sie haben folgendes Datum ausgewählt: " + MyCal.SelectedDate.ToLongDateString(); } Calendar Beispiel
Sandini Bib Rich Controls
283
Listing 4.19: Verwendung des Calendar Controls
Das Beispiel erzeugt die folgende Ausgabe:
Abbildung 4.17: Verwendung des Calendar-Controls
Sandini Bib 284
4
WebForm-Controls
Standardmäßig wird der aktuelle Monat mit dem aktuellen Datum angezeigt. Über die LinkButtons (< und >) kann man zum vorherigen bzw. nächsten Monat springen. Außerdem kann man das Calendar-Control so programmieren, dass direkt im Programmcode der Monat gewechselt werden kann. Wenn man die Navigation zum vorherigen oder nächsten Monat unterbinden möchte, kann man die Eigenschaft ShowNextPrevMonth auf False setzen. Mit der Eigenschaft VisibleDate legt man fest, welches aktuelle Datum im Calendar-Control angezeigt werden soll. Mit dem folgenden Code wird das aktuelle Datum auf den 25. Oktober 2001 gesetzt. Calendar1.VisibleDate = new DateTime(2001, 10, 25);
Das Ereignis OnVisibleMonthChanged zeigt an, dass ein anderer Monat ausgewählt wurde. Das nächste Beispiel zeigt, wie man dieses Ereignis einsetzen könnte. void MonthChanged(Object sender, MonthChangedEventArgs e) { if (e.NewDate.Month > e.PreviousDate.Month) { lblOutput.Text = "Sie haben den nächsten Monat ausgewählt."; } else { lblOutput.Text = "Sie haben den vorherigen Monat ausgewählt."; } } OnVisibleMonthChanged Beispiel Wählen Sie einen anderen Monat aus:
Listing 4.20: Verwendung des OnVisibleMonthChanged-Ereignisses
Sandini Bib Rich Controls
285
Das Beispiel erzeugt die folgende Ausgabe:
Abbildung 4.18: Verwendung des OnVisibleMonthChanged Ereignisses
Man kann das Erscheinungsbild des Calendar-Controls mit verschiedenen Eigenschaften ändern. Standardmäßig ist keine spezielle Formatierung bei der Erstellung des Controls vorhanden. Über die folgenden Eigenschaften kann man das äußere Erscheinungsbild anpassen: Erscheinungsbild
Eigenschaften
Schriftart, Text und Hintergrundfarbe
Font, ForeColor, BackColor
Größe
Height, Width
Ränder und Zwischenraum zwischen den Tagen
CellPadding, CellSpacing
Farbe, Größe und Stil für den Rand und die Tabellenlinien
BorderColor, BorderStyle, ShowGridLines
Tabelle 4.9: Verschiedene Eigenschaften des Calendar-Controls
Sandini Bib 286
4
WebForm-Controls
Um das Erscheinungsbild der Tage der Woche abzuändern, hat man folgende Eigenschaften zur Verfügung: Erscheinungsbild
Eigenschaften
Soll die Überschrift für die Tage der Woche angezeigt werden?
Tabelle 4.10: Verschiedene Eigenschaften der Wochentage
Um das Aussehen der Monate zu ändern, gibt es folgende Eigenschaften: Erscheinungsbild
Eigenschaften
Entfernen der Monatsnavigation
ShowTitle (True oder False)
Format des Monatsnamens
MonthNameFormat, TitleFormat
Format und Text der Monatsnavigation
NextPrevFormat, NextMonthText, PrevMonthText
Farbe, Schriftart, Randstil und Randbreite Tabelle 4.11: Verschiedene Eigenschaften der Monate
Standardmäßig werden Tage im Calendar-Control als Zahlen dargestellt. Wenn die Datumsauswahl aktiviert ist, werden die Zahlen als Links angezeigt. Die Darstellung der einzelnen Tage kann jedoch angepasst werden, wodurch Folgendes ermöglicht wird: 왘 Programmgesteuertes Hervorheben bestimmter Tage, indem z.B. Feiertage andersfarbig dargestellt werden 왘 Programmgesteuertes Angeben, ob ein einzelner Tag ausgewählt werden kann 왘 Hinzufügen von Informationen zu einer Tagesanzeige, z.B. Termin- oder Ereignisfunktion Wenn das Calendar-Control auf dem Browser ausgegeben wird, wird das Ereignis DayRender ausgelöst, das durch serverseitigen Code behandelt werden kann. Das Ereignis wird für jeden Tag aufgerufen, der im Control angezeigt wird. So kann programmgesteuert überprüft werden, welche Tage in welchem Format angezeigt werden.
Sandini Bib Rich Controls
287
Die DayRender-Ereignisfunktion übernimmt zwei Argumente: einen Verweis auf das Calendar-Control, und ein Objekt vom Typ DayRenderEventArgs. Durch das letztere Objekt kann wiederum auf zwei andere Objekte zugegriffen werden: 왘 Cell ist ein TableCell-Objekt, mit dem die Darstellung eines einzelnen Tages festgelegt werden kann. 왘 Day: Mit ihm können Informationen über den Tag abgefragt werden. Außerdem kann programmgesteuert festgelegt werden, ob der Tag ausgewählt werden kann. Zudem gibt es noch eine Controls-Auflistung, womit es möglich wird, einen Inhalt zum Tag hinzuzufügen. Im folgenden Beispiel werden freie Tage im Kalender in Gelb ausgegeben, während Wochentage in Grün angezeigt werden. void MyCalender_OnDayRender(Object sender, DayRenderEventArgs e) { Style VacationStyle = new Style(); VacationStyle.BackColor = System.Drawing.Color.Yellow; VacationStyle.BorderColor = System.Drawing.Color.Purple; VacationStyle.BorderWidth = 3; Style WeekendStyle = new Style(); WeekendStyle.BackColor = System.Drawing.Color.Green; if (e.Day.Date >= new DateTime(2000, 11, 23)) && (e.Day.Date 1) { DateTime firstDate = theDates(0); DateTime lastDate = theDates(theDates.Count – 1); txtFirstDate.Text = firstDate.ToString(); txtLastDate.Text = lastDate.ToString(); } }
Im folgenden Code wird der Datumsbereich dargestellt, den der Benutzer ausgewählt hat: void MyCalendar_SelectionChanged(Object sender, EventArgs e) { SelectedDatesCollection theDates = MyCalendar.SelectedDates(); TimeSpan timeSpan = theDates[theDates.Count – 1] = theDates[0]; txtTextBox.Text = String.Format("Sie haben {0] Tage ausgewählt.", timeSpan.Days + 1); }
Das XML-Control Das letzte Rich Control, das ich hier noch näher beschreiben möchte, ist das XMLControl. Dieses Control ist in der Lage, ein XML-Dokument oder das Resultat einer Xsl-Transformation anzuzeigen. Die Transformation des XML-Dokuments wird dabei serverseitig durchgeführt. Die Basisklasse des Controls ist in diesem Fall nicht WebControl, sondern Control, was bedeutet, dass es die standardmäßigen Darstellungseigenschaften nicht gibt. Es gibt insgesamt sechs Eigenschaften, die für das XML-Control zur Verfügung stehen. Die folgende Tabelle beschreibt diese näher. Eigenschaften
Beschreibung
Document
Eine Referenz vom XmlDocument-Objekt, die das XML-Dokument bzw. das Ergebnis einer Xsl-Transformation beinhaltet.
DocumentContent
Stellt einen String dar, der den Inhalt des XML-Dokuments enthält, das angezeigt bzw. transformiert werden soll.
Tabelle 4.14: Eigenschaften des XML-Controls
Sandini Bib Rich Controls
291
Eigenschaften
Beschreibung
DocumentSource
Stellt einen String dar, der den physischen bzw. virtuellen Pfad des XMLDokuments enthält, das angezeigt bzw. transformiert werden soll.
Transform
Eine Referenz vom XslTransform-Objekt, das das Xsl- oder Xslt-Stylesheet enthält, das zur Transformation des XML-Dokuments verwendet wird.
TransformSource
Stellt einen String dar, der den physischen bzw. virtuellen Pfad des XslDokuments enthält, das zur Transformation verwendet wird.
TransformArgumentList
Eine Referenz vom XsltArgumentList-Objekt, das die Argumente hält, die für die Transformation an das Stylesheet übergeben werden.
Tabelle 4.14: Eigenschaften des XML-Controls (Forts.)
Die Syntax für die Deklaration des XML-Controls sieht wie folgt aus:
Das folgende Beispiel besteht aus drei Dateien: aus der XML-Datei, der Xsl-Datei und der ASP.NET-Seite, die mithilfe des XML-Controls die Transformation der XML-Datei vornimmt. Klaus Aschenbrenner Theodor Koerner Straße 151/6/23 Graz 8010 Senior Developer DataDialog.NET Herbert Aschenbrenner Hochweg 20a Spital am Semmering 8684
Sandini Bib 292
4
WebForm-Controls
System Adminstrator Stadtwerke Muerzzuschlag GmbH Listing 4.23: People.xml
Sandini Bib Rich Controls doc.Load(Server.MapPath("People.xml")); XslTransform trans = new XslTransform(); trans.Load(Server.MapPath("People.xsl")); MyXml1.Document = doc; MyXml1.Transform = trans; } Xml Beispiel Listing 4.25: Verwendung des XML-Controls
Das Beispiel erzeugt die folgende Ausgabe:
Abbildung 4.19: Verwendung des Xml Controls
293
Sandini Bib 294
4
WebForm-Controls
4.5 Datenbindung Jede dynamische Webseite und auch jede webbasierende Anwendung muss meistens auf eine Datenbank zugreifen können. Mit ASP war man in der Lage, mithilfe eines OLEDB-Treibers auf jede beliebige Datenquelle, wie z.B. SQL-Server, Exchange-Server usw., zuzugreifen. Außerdem ist es ziemlich einfach, Daten abzufragen und auch zu ändern. Unter ASP hat man das alles mithilfe der ADODB-Objekte erledigt. ASP.NET führt mit der Datenbindung eine komplett neue Architektur ein, die es möglich macht, dass man fast ohne Programmierung Steuerelemente an eine beliebige Datenquelle binden kann. Die Server-Controls sind so programmiert worden, dass sie mit jeder Art von Datenquelle zusammenarbeiten können, nicht nur mit relationalen oder XML-Daten. In diesem Abschnitt sehen wir jetzt, wie die Datenbindung mit den Server-Controls genau funktioniert und wie man sie effizient verwenden kann. Auf den nächsten Seiten werden folgende Fragen geklärt: 왘 Was genau die Datenbindung is, und wie arbeitet sie 왘 Wie die Datenbindung bei den Server-Controls angewendet wird 왘 Wie das Aussehen der datengebundenen Controls geändert werden kann 왘 Wie man Daten innerhalb der datengebundenen Controls ändern kann
Konzepte der Datenbindung Datenbindung ist ein Begriff, den eigentlich jeder Programmierer kennen sollte. In den letzten Jahren hatte man bei der Windows-Programmierung sehr viel mit der Datenbindung zu tun. In Programmiersprachen wie Visual Basic und in Programmen wie Access bedeutet der Begriff Datenbindung, dass man ein Steuerelement mit einem bestimmten Wert aus einer Datenquelle füllt. Diese Werte können z.B. eine Collection wie ein Recordset sein, aber auch einzelne Werte, die aus der Datenquelle ermittelt werden. Wenn man eine Datenquelle in Visual Basic an ein DataGrid bindet, werden automatisch alle Spalten ermittelt und diese inkl. Überschriften angezeigt. Das bedeutet, dass die Datenbindung das Leben des Programmierers sehr vereinfacht, da er sich um fast nichts mehr kümmern muss. Man braucht nur noch zu wissen, welche Daten man binden muss, und den Rest erledigt die Datenbindung selbst. Bei der Datenbindung auf Websites ist aber zu beachten, dass das HTTP-Protokoll verbindungslos ist, was bedeutet, dass bei zwei Aufrufen der gleichen Seite der Webserver diese Aufrufe als zwei komplett getrennte Aufrufe verarbeitet. Daher kann die tradionelle Datenbindung von Visual Basic oder Access für Web-Anwendung nicht verwendet werden. Mit dem Internet Explorer 4.0 wurden clientseitige und auch serverseitige Komponenten eingeführt, mit denen die Datenbindung vereinfacht wurde.
Sandini Bib Datenbindung
295
Aber die clientseitige Datenbindung hat sich in der Vergangenheit leider nie richtig durchgesetzt. Was ist, wenn der Client die Datenbindung nicht unterstützt? Daher hat sich der Trend in den letzten Jahren zur serverseitigen Datenbindung bewegt. Das .NET Framework unterstützt daher eine leistungsfähige Datenbindung, die komplett serverseitig abgewickelt wird. Der Client bekommt anschließend nur das Resultat der Datenbindung zurückgeliefert. Daher kann man mit diesem Feature auch alle möglichen Clients bedienen, da man sich keine Sorgen mehr machen muss, ob die Datenbindung clientseitig unterstützt wird oder nicht. Das folgende Beispiel zeigt, wie die Datenbindung in ASP realisiert wurde und wie sie jetzt in ASP.NET aussieht. Response.Write Response.Write Response.Write Response.Write Response.Write
"table>" "
Datum
" "
Titel
Sandini Bib Datenbindung
301
Listing 4.28: Datenbindung für die wichtigsten WebForm-Controls
Wiederholte Datenbindung Die zweite Variante der Datenbindung unter ASP.NET ist die wiederholte Datenbindung. Sie wird dann verwendet, wenn man von einer Datenquelle nicht nur einen einfachen Wert zurückbekommt, sondern eine Vielzahl von Werten, wie z.B. Collections, Arrays, Dictionaries usw. In ASP.NET gibt es insgesamt acht Server-Controls, die mit der wiederholten Datenbindung an eine Datenquelle gebunden werden können. Alle haben bestimmte Eigenschaften und Methoden, mit denen man genau festlegen kann, welche Werte der Datenquelle gebunden werden sollen. Die Daten werden dann automatisch von den Controls gebunden und ausgegeben. Das ListControl erzeugt z.B. für jeden Eintrag ein -Element. Sehen wir uns diese Server-Controls näher an: 왘 Das HTML Element , das durch das Server-Control System.Web.UI.HtmlControls.HtmlSelect implementiert wird. Es erzeugt standardmäßig ein -Element und für jeden Dateneintrag wird ein -Element erzeugt. 왘 Das -Control, dass durch die Klasse System.Web.UI.WebControls.ListBox implementiert wird. Es erzeugt auch ein -Element und es wird wiederum für jeden Eintrag ein -Element erzeugt. 왘 Das -Control, das durch die Klasse System.Web.UI.WebControls.DropDownList implementiert wird. Wie auch die vorher erwähnten erzeugt es wiederum ein -Element und für jeden Eintrag ein -Element. Der einzige Unterschied ist, dass eine DropDownList anstatt einer ListBox erstellt wird. 왘 Das -Control, das durch die Klasse System.Web.UI.WebControls.CheckBoxList implementiert wird. Es erzeugt für jeden Eintrag in der Datenquelle ein -Element. 왘 Das -Control, das durch die Klasse System.Web.UI.WebControls.RadioButtonList implementiert wird. Es erzeugt für jeden Eintrag in der Datenquelle ein -Element. Außerdem wird der Gruppenname der verschiedenen Elemente auf den gleichen Namen gesetzt. Dadurch wird es möglich gemacht, dass innerhalb der Gruppe immer nur ein Element ausgewählt werden kann.
Sandini Bib 302
4
WebForm-Controls
왘 Das -Control, das durch die Klasse System.Web.UI.WebControls.Repeater implementiert wird. Standardmäßig generiert dieses Control keine Benutzeroberfläche. Es wiederholt den Inhalt, den man innerhalb des Controls definiert. Normalerweise definiert man mit diesem Control eine Datenquelle und für jeden Eintrag in der Datenquelle gibt dann das Format innerhalb des Repeater-Controls mithilfe von Templates an. 왘 Das -Control, das durch die Klasse System.Web.UI.WebControls.DataList implementiert wird. Mit diesem Control definiert man die Datenquelle und für jeden Eintrag innerhalb der Datenquelle wird eine Zeile im DataList-Control angelegt. 왘 Das -Control, das durch die Klasse System.Web.UI.WebControls.DataGrid implementiert wird. Das ist ein Control, dass man an verschiedene Datenquellen, wie z.B. an DataView-, DataSet- oder DataReader-Objekte, binden kann. Intern erstellt das DataGrid-Control eine Html-Tabelle, die zur Laufzeit dynamisch erstellt wird. Das Control kümmert sich dabei komplett um die Darstellung des Inhalts, was bedeutet, dass der Programmierer nicht einmal wissen muss, wie viele Spalten die Datenquelle zurückgibt. Das Control erstellt automatisch die richtige Anzahl an Spalten und Zeilen. Außerdem kann man das Aussehen dieses Controls mit einer sehr großen Anzahl von Eigenschaften steuern. Mit diesem Control bleiben wirklich fast keine Wünsche mehr offen, die nicht implementiert wurden. Diese acht Controls haben alle die gleichen Eigenschaften, Ereignisse und Methoden, mit denen die Datenbindung gezielt gesteuert werden kann. Die folgenden Tabellen beschreiben sie näher. Eigenschaft
Beschreibung
DataTextField
Gibt an, welches Feld oder welche Spalte den Wert für die Anzeige enthält
DataValueField
Gibt an, welches Feld oder welche Spalte den Wert für die ValueEigenschaft des Controls enthält
DataTextFormattingString
Gibt an, wie die Werte für die Anzeige auf der Oberfläche durch ASP.NET formatiert werden sollen. "{0:C}" steht z.B. für die Formatierung von Währungen und "{0:dddd MMMM dd, yyyy}" für die Formatierung von Datumswerten.
DataMember
Gibt an, welches Set von Zeilen für die Datenbindung verwendet werden soll. Kann z.B. innerhalb eines DataSet den Namen der DataTable für die Datenbindung angeben.
Tabelle 4.15: Eigenschaften der datengebundenen Controls
Sandini Bib Datenbindung
303
Methoden
Beschreibung
DataBind
Diese Funktion veranlasst das Control, die Datenbindung für sich selbst durchzuführen.
FindControl
Liefert eine Referenz auf ein Child-Control innerhalb des Containers. Eine Tabellenspalte innerhalb einer Tabellenzeile wäre z.B. ein Child-Control.
Tabelle 4.16: Methoden der datengebundenen Controls
Ereignisse
Beschreibung
DataBinding
Tritt auf, wenn die Datenbindung für das entsprechende Control durchgeführt wird. Das Control, für das die Datenbindung durchgeführt wird, wird als Parameter an die Ereignisbehandlungsfunktion übergeben.
SelectedIndexChanged
Tritt auf, wenn sich der ausgewählte Index für das entsprechende Control ändert und die ASP.NET-Seite an den Server gepostet wird.
Tabelle 4.17: Ereignisse der datengebundenen Controls
Da wir jetzt die verschiedenen Controls kennen, die an eine Datenquelle gebunden werden können, stellt sich jetzt noch die Frage, welche Datenquellen dies sind. Technisch gesehen, können die Controls an jede Datenquelle gebunden werden, die die Schnittstellen IEnumerable, ICollection oder IListSource implementiert. Sehen wir uns das im Detail an: 왘 Eine Collection, wie wir sie z.B. bei Request.Form zurückbekommen. Oder eine Collection von DataTables, die ein DataSet definiert, oder auch eine Collection, die man selbst erstellt und mit Werten füllt. 왘 Eine ArrayList, die eine einfache Liste von Werten enthalten kann. Eine ArrayList ist immer ein guter Weg, um z.B. eine ListBox mit Werten zu füllen. Man erstellt daher zuerst die ArrayList mit den Werten und bindet sie anschließend an die ListBox oder aber auch an ein DropDownList-Control. 왘 Eine HashTable oder ein Dictionary-Objekt. Beide Objekte enthalten Werte, die durch einen eindeutigen Schlüssel identifizierbar sind. Diese Arten von Datenquellen sind besonders für Steuerelemente geeignet, die ein Text- und eine Value-Eigenschaft enthalten. Darunter fallen wiederum die beiden Steuerelemente ListBox und DropDownList. 왘 Ein ADO.NET-DataView-Objekt, das die Zeilen einer DataTable beinhaltet. Diese Zeilen können aus einer Datenbank stammen oder manuell im serverseitigen Code durch den Programmierer erstellt worden sein. Ein DataView-Objekt hat keine Ver-
Sandini Bib 304
4
WebForm-Controls
bindung zur Ursprungsdatenbank, was bedeutet, dass das Objekt zwischen den verschiedenen Schichten einer n-Tier-Anwendung problemlos durchgereicht werden kann. 왘 Ein ADO.NET-DataSet-Objekt, das eine oder mehrere DataTables enthalten kann. Die DataTable repräsentiert wiederum die verschiedenen Zeilen einer Datenbank, die aus einer Datenbank selbst stammen oder manuell im serverseitigen Code durch den Programmierer erstellt werden können. Ebenso wie das DataViewObjekt, ist das DataSet-Objekt verbindungslos. Darum kann es wiederum zwischen den verschiedenen Schichten einer n-Tier-Anwendung problemlos durchgereicht werden. 왘 Ein ADO.NET-DataReader-Objekt, das nur ein vorwärts gerichteter, lesender Cursor auf eine bestimmte Datenbank ist. Er kann mehrere Zeilen und Spalten beinhalten. Allerdings ist zu beachten, dass das DataReader-Objekt nicht verbindungslos ist, das heißt, dass beim Lesen aus dem DataReader-Objekt immer noch eine Verbindung zur Datenbank besteht!
Syntax für wiederholte Datenbindung Wenn wir Steuerelemente an eine Datenquelle binden, die nur einfache Werte enthält, haben wir folgende Syntax verwendet:
Diese Syntax kann man aber für die wiederholte Datenbindung nicht verwenden, da es hier immer mehr als ein Feld gibt, das die Datenquelle enthält. Eine HashTable oder ein Dictionary-Objekt hat z.B. immer eine Key- und eine Value-Eigenschaft. Beim DataReader- oder beim DataView-Objekt gibt es z.B. immer mehrere Zeilen und Spalten. Daher müssen wir bei der wiederholten Datenbindung genau angeben, an welches Feld das Steuerelement gebunden werden soll. Wenn wir z.B. eine HashTable an ein Steuerelement binden möchten, können wir mit der folgenden Anweisung die Key Eigenschaft binden: Key:
Die nächte Anweisung würde z.B. die Value Eigenschaft an das Steuerelement binden: Value:
Wenn wir eine Collection, ein DataView- oder ein DataReader-Objekt an ein Steuerelement binden, müssen wir das Feld für die Datenbindung näher spezifizieren: Wert vom DataView / DataReader Objekt:
Sandini Bib Datenbindung
305
oder Wert von einer Collection:
Wenn wir für die Datenbindung ein Repeater-Steuerelement verwenden, gibt es ein neues Feature, das die Datenbindung unter ASP.NET sehr vereinfacht, nämlich die so genannten Templates. Ein Template definiert für die Datenbindung den Inhalt, der für jede Zeile in der Datenquelle angezeigt werden soll. Die Steuerelemente, die in ASP.NET Templates unterstützen, sind das Repeater-, das DataList- und das DataGridControl. Im folgenden Code wird durch die Verwendung eines Repeater-Steuerelements der Inhalt einer HashTable mit Templates angezeigt. Listing 4.29: Verwendung von Templates für die Datenbindung
Für jede Zeile in der Datenquelle wird der Inhalt, der zwischen dem definiert ist, ausgegeben. In diesem Fall wird die Key und die Value-Eigenschaft ausgegeben. Zum Schluss müssen wir jetzt noch das Repeater-Control an eine Datenquelle wie z.B. eine HashTable binden: MyRepeater.DataSource = myValues; MyRepeater.DataBind();
Anschließend wird durch ASP.NET die Datenbindung mit dem Repeater-Control durchgeführt. Das Beispiel erzeugt die folgende Ausgabe, wenn wir die ArrayList mit den folgenden Werten initialisieren: ArrayList myValues = new ArrayList(); myValues.Add("Hello"); myValues.Add("World"); myValues.Add("!"); Listing 4.30: Initialisierung der ArrayList
Das Beispiel erzeugt die folgende Ausgabe:
Sandini Bib 306
4
WebForm-Controls
Abbildung 4.21: Datenbindung mit einer ArrayList
Wenn wir jetzt die wiederholte Datenbindung für Steuerelemente anwenden möchten, die keine Templates unterstützen, muss man ein paar Eigenschaften der Controls mit entsprechenden Werten initialisieren, die anzeigen, von welchen Spalten die Werte für die Datenbindung kommen. Die beiden wichtigsten Eigenschaften sind die DataTextField- und dieDataValueField-Eigenschaft. Das folgende Beispiel definiert eine ListBox und bindet diese an eine HashTable, die in der Funktion Page_Load mit ein paar Werten initialisiert wird. void Page_Load(Object sender, EventArgs e) { Hashtable myValues = new Hashtable(); myValues.Add("Hello", "First"); myValues.Add("World", "Second"); myValues.Add("!", "Third"); MyListBox.DataSource = myValues; MyListBox.DataTextField = "Key"; MyListBox.DataValueField = "Value"; MyListBox.DataBind(); } Datenbindung eines ListBox-Controls
Sandini Bib Datenbindung
307
Listing 4.31: Datenbindung mit einem ListBox-Control
Das Beispiel erzeugt die folgende Ausgabe:
Abbildung 4.22: Datenbindung mit einem ListBox-Control
Wenn in einer Zeile einer Datenquelle mehr als ein Wert vorhanden ist, können wir die Spalte für die Datenbindung mit der Methode Eval auswählen. Optional kann man dann noch die Spalte für die Ausgabe formatieren. Nehmen wir als Beispiel an, dass wir ein DataView-Objekt haben, das mehrere Spalten beinhaltet. Mit der folgenden Zeile wird die Spalte Buchtitel an das entsprechende Steuerelement gebunden:
Formatierung der Ausgabe Die Eval-Funktion nimmt drei Parameter entgegen, wovon der dritte Parameter optional ist. Es wird für die Formatierung der Ausgabe verwendet. Ihm übergibt man einen Formatstring, der das Format der Ausgabe definiert. Wenn wir ein DataView-Objekt haben, das z.B. ein Datum beinhaltet, kann man mit der folgenden Ausgabe das Datum mit den lokalen Einstellungen des Rechners ausgeben:
Das erste Zeichen nach der geschweiften Klammer ist ein Platzhalter für die Variable, die den Wert enthält, der formatiert werden soll. Der Buchstabe nach dem Doppel-
Sandini Bib 308
4
WebForm-Controls
punkt gibt das genaue Format an, in dem die Ausgabe formatiert werden soll. Dabei sind folgende Einstellungen möglich: Format
Beschreibung
C oder c
Währungsformat
D oder d
Dezimalformat
E oder e
Wissenschaftliches (exponentielles) Format
F oder f
Fließkommaformat
G oder g
Generelles Format
N oder n
Nummern-Format
P oder p
Prozent-Format
X oder x
Hexadezimales Format
d
Kurzes Datum
D
Langes Datum
f
Langes Datum und kurze Zeit
F
Langes Datum und lange Zeit
g
Kurzes Datum und kurze Zeit
G
Kurzes Datum und lange Zeit
M oder m
Monat und Tag
R oder r
RFC1123-Format (lokale Zeit)
s
ISO-8601-Zeit
t
Kurze Zeit
T
Lange Zeit
u
ISO-8601-Zeit (universale Zeit)
U
Universale(s) Datum / Zeit
Y oder y
Jahr und Monat
Tabelle 4.18: Formatierungsmöglichkeiten
Beispiele der Datenbindung Das einfachste Beispiel für die Datenbindung ist, wenn man ein ArrayList-Objekt an ein Steuerelement bindet. Die ArrayList wird in Page_Load initialisiert und dann an verschiedene Controls gebunden. void Page_Load(Object sender, EventArgs e)
Listing 4.32: Datenbindung an verschiedene Steuerelemente
Das Beispiel erzeugt die folgende Ausgabe:
309
Sandini Bib 310
4
WebForm-Controls
Abbildung 4.23: Datenbindung an verschiedene Steuerelemente
Um genau zu erklären, wie die Datenbindung bei den verschiedenen Controls funktioniert, möchte ich jetzt noch zeigen, welchen Output die verschiedenen Steuerelemente aus dem letzten Beispiel produziert haben. Die DropDownList hat folgenden HtmlCode produziert: Microsoft IBM Sun Compaq Oracle
Die ListBox wurde wie folgt in HTML erzeugt: Microsoft IBM Sun Compaq Oracle
Sandini Bib Datenbindung
311
Das DataGrid-Control wird immer als Html-Tabelle erstellt. Für das Datenbindungsbeispiel wurde folgende Tabelle erzeugt:
Item
Microsoft
IBM
Sun
Compaq
Oracle
Das Repeater-Control erzeugt den einfachsten Output, nämlich folgenden: Microsoft IBM Sun Compaq Oracle
Die DataList wurde ebenfalls wieder als Html-Tabelle erzeugt:
Item
Microsoft
IBM
Sun
Sandini Bib 312
4
WebForm-Controls
Compaq
Oracle
Die CheckBoxList ist eine Html-Tabelle, die mehrere -Elemente besitzt:
Microsoft
IBM
Sun
Compaq
Oracle
name="MyCheckBoxList:0" /
name="MyCheckBoxList:1" /
name="MyCheckBoxList:2" /
name="MyCheckBoxList:3" /
name="MyCheckBoxList:4" /
Die RadioButtonList ist eine Html-Tabelle, die mehrere Elemente besitzt:
Sandini Bib Datenbindung
313
Microsoft
IBM
Sun
Compaq
Oracle
Das nächste Beispiel demonstriert, wie man eine HashTable an WebForm-Controls binden kann. Eine HashTable beinhaltet ein Key- und ein Value-Feld. Dabei wird wiederum wie im vorherigen Beispiel die HashTable in Page_Load initialisiert. void Page_Load(Object sender, EventArgs e) { Hashtable myValues = new Hashtable(); myValues.Add("Microsoft", 49.56); myValues.Add("Sun", 28.33); myValues.Add("IBM", 55);
Für das Erscheinungsdatum wird eine einfache verwendet. Für den Editiermodus wird dann eine normale TextBox angezeigt. Für den Titel des Buches habe ich ein verwendet, da man hier genau angeben kann, welches Steuerelement für den Editiermodus angezeigt werden soll. Wenn man eine verwendet hätte, wäre die TextBox für die Eingabe des Titels zu klein gewesen. Darum habe ich ein definiert, welches die Länge der TextBox auf 60 Zeichen vergrößert. Natürlich gibt es auch die Möglichkeit, dass man innerhalb eines andere Steuerelemente wie TextBoxen definiert. Für ein Boolean-Feld einer Datenquelle könnte man z.B. ein -Control verwenden. An diesem Beispiel sieht man, dass man in ASP.NET nicht eingeschränkt ist, da man das Standardverhalten beliebig verändern kann. Zum Schluss wird dann noch die definiert. Dabei werden die Eigenschaften mit den Texten initialisiert, die für die Bearbeitung des DataGrids angezeigt werden sollen. In der serverseitigen Funktion cmdEditComand wird das DataGrid in den Editiermodus versetzt. Es wird der Eigenschaft EditItemIndex des DataGrids einfach der Index des zu bearbeitenden Eintrags zugewiesen. Diesen Index erhält man aus dem zweiten Übergabeparameter vom Typ DataGridCommandEventArgs. void cmdEditCommand(Object sender, DataGridCommandEventArgs e) { lblOutput.Text = ""; MyDataGrid.EditItemIndex = e.Item.ItemIndex; BindDataGrid(); }
Sandini Bib Stile und Vorlagen
339
In der Ereignisbehandlungsfunktion cmdCancelCommand wird das DataGrid wieder in den Normalmodus versetzt. Es wird einfach die Eigenschaft EditItemIndex auf –1 gesetzt. void cmdCancelCommand(Object Sender, DataGridCommandEventArgs e) { lblOutput.Text = ""; MyDataGrid.EditItemIndex = -1; BindDataGrid(); }
Die wichtigste Funktion ist die Funktion cmdUpdateCommand. Sie ist für die Aktualisierung der Datenquelle zuständig. In diesem Beispiel gebe ich einfach die SQLAbfrage aus, die das gewünschte Buch in der Datenbank mit den eingegebenen neuen Werten aktualisieren würde. Die eingegebenen Werte werden durch eine Referenz auf die entsprechenden TextBoxen ermittelt. Dabei gibt es zwei Möglichkeiten, wie man auf die TextBoxen zugreifen kann. void cmdUpdateCommand(Object sender, DataGridCommandEventArgs e) { String strSql; TextBox txtTitleCtrl = (TextBox)e.Item.FindControl("txtTitle"); TextBox txtPublicationDateCtrl = (TextBox)e.Item.Cells[2].Controls[0]; strSql = "UPDATE Books SET Title = ‚" + txtTitleCtrl.Text + "', PublicationDate = ‚" + txtPublicationDateCtrl.Text + "' WHERE ISBN = ‚" + MyDataGrid.DataKeys[e.Item.ItemIndex] + "'"; ExecuteSql(strSql); MyDataGrid.EditItemIndex = -1; BindDataGrid(); }
Im letzten Beispiel dieses Kapitels möchte ich noch zeigen, wie man Daten mit dem DataList-Control bequem und einfach bearbeiten kann. Das DataList-Control ist das zweite Control, das dieses Inline-Feature für die Bearbeitung einer Datenquelle beinhaltet. Mit dem DataList-Control kann man sogar noch mehr machen, als mit dem DataGrid Steuerelement, aber man muss dafür auch ein wenig serverseitigen Code schreiben. Das DataList-Control hat folgende Ereignisse, die im serverseitigen Code durch den Programmierer gesteuert werden können:
Sandini Bib 340
4
WebForm-Controls
Ereignis
Beschreibung
OnItemCommand
Gibt die Ereignisbehandlungsfunktion an, die aufgerufen werden soll, wenn irgendein Button im DataList-Control geklickt wird
OnUpdateCommand
Gibt die Ereignisbehandlungsfunktion an, die aufgerufen werden soll, wenn das Update-Command ausgelöst wird
OnEditCommand
Gibt die Ereignisbehandlungsfunktion an, die aufgerufen werden soll, wenn das Edit-Command ausgelöst wird
OnDeleteCommand
Gibt die Ereignisbehandlungsfunktion an, die aufgerufen werden soll, wenn das Delete-Command ausgelöst wird
OnCancelCommand
Gibt die Ereignisbehandlungsfunktion an, die aufgerufen werden soll, wenn das Cancel-Command ausgelöst wird
Tabelle 4.23: Ereignisse des DataList-Controls
Im folgenden Beispiel werden wieder mehrere Buchtitel angezeigt, die über das DataList-Control bearbeitet werden können. Wenn man auf den Info-Button klickt, wird der entsprechende Eintrag in der DataList ausgewählt und es werden nähere Informationen darüber angezeigt. Außerdem erscheint dann ein Edit-Button, mit dem man den ausgewählten Eintrag bearbeiten kann. Man kann dann den Eintrag aktualisieren oder löschen. void Page_Load(Object sender, EventArgs e) { if (!Page.IsPostBack) { BindDataGrid(); } } void BindDataGrid() { DataTable myTable = new DataTable("Books"); DataRow myRow; myTable.Columns.Add("ISBN", System.Type.GetType("System.String")); myTable.Columns.Add("Title", System.Type.GetType("System.String")); myTable.Columns.Add(PublicationDate", System.Type.GetType("System.DateTime")); myRow = myTable.NewRow(); myRow["ISBN"] = "123456789"; myRow["Title"] = "Web-Anwendungen mit Visual C#";
Empfänger:"; html += "" + FirstName.Text + " " + LastName.Text + "\n" + Address.Text + ""; html += "
Rechnungsadresse:"; if (BillShipping.Checked == true) { html += "(Gleich wie Empfangsadresse)"; } else { html += "" + BillFirstName.Text + " " + BillLastName.Text + "\n" + BillAddress.Text + ""; } ReceiptPane.InnerHtml = html; }
Sandini Bib 368
5
Internet Explorer WebControls
Internet-Bestellung Einkaufswagen
Das ist ein Beispiels-Assistent, der das MultiPage WebControl demonstriert.
Web-Anwendungen mit Visual C# ATS 950,00 Menge: 1
Bitte geben Sie die Versandinformationen ein:
Vorname:
Nachname:
Adresse:
" OnClick="NextClick" /> Listing 5.3: Verwendung des MultiPage-WebControls
Das Beispiel erzeugt die in Abbildung 5.2 gezeigte Ausgabe. Ich möchte jetzt nochmals den Quelltext Schritt für Schritt durchgehen, damit man sieht, wie das MultiPage-WebControl funktioniert. In der Funktion SetupButtons werden die beiden Schaltflächen für die Navigation initialisiert, je nachdem auf welcher Seite man sich befindet. Die Ereignisbehandlungsfunktion BackClick wird aufgerufen, wenn der Button mit der Aufschrift < Zurück geklickt wird. In ihr wird der SelectedIndex des MultiPage-WebControls um eins vermindert, was bedeutet, dass die vorherige Seite angezeigt wird.
Sandini Bib 370
5
Internet Explorer WebControls
Abbildung 5.2: Verwendung des MultiPage-WebControls void BackClick(Object sender, EventArgs e) { if (Wizard.SelectedIndex > 0) { Wizard.SelectedIndex--; SetupButtons(); } }
Die Ereignisbehandlungsfunktion NextClick macht das gleiche wie ihr Vorgänger, nur dass die nächste Seite des MultiPage-WebControls angezeigt wird. Die benutzerdefinierte Funktion CreateReceipt wird aufgerufen, wenn der letzte Schritt des Assistenten angezeigt wird. In dieser Funktion wird dann die Rechnung für die Bestellung erstellt und angezeigt. In dieser Funktion sieht man, wie man auf alle Steuerelemente innerhalb aller PageViews zugreifen kann. Im Html-Teil der ASP.NET-Seite wird als erster Schritt das MultiPage-Control definiert.
Nachdem das MultiPage-WebControl definiert ist, werden die verschiedenen PageViewSteuerelemente mit ihrem Inhalt definiert, wobei jede PageView einen Schritt des Assistenten darstellt.
Sandini Bib Das MultiPage-WebControl
371
...
Das nächste Beispiel zeigt, wie man dynamisch im serverseitigen Code PageView-Steuerelemente zu einem MultiPage-Control hinzufügen kann. void cmdAdd_Click(Object sender, EventArgs e) { PageView myView = new PageView(); MyMultiPage.Controls.Add(myView); myView.Controls.Add(new LiteralControl(txtPageView.Text)); } Dynamische PageView-WebControls Bitte geben Sie den Namen des neuen PageView-WebControls ein: Listing 5.4: Dynamische Erzeugung von PageView-Steuerelementen
Sandini Bib 372
5
Internet Explorer WebControls
5.3 Das TabStrip WebControl Mit dem TabStrip WebControl ist es möglich, leistungsfähige Registerkarten auf einer ASP.NET-Seite anzuzeigen. Meistens wird das TabStrip-WebControl in Verbindung mit dem MultiPage-Control verwendet, wobei das TabStrip-Steuerelement die Registerkarten definiert und das MultiPage-Steuerelement den Inhalt der verschiedenen Registerkarten definiert und dementsprechend anzeigt. Es gibt die folgenden TabStrip-Objekte, mit denen im serverseitigen Code programmiert werden kann: Objekt
Beschreibung
TabStrip
Dient als Container für alle anderen Objekte.
Tab
Definiert eine Registerkarte innerhalb des TabStrip-Containers.
TabSeparator
Definiert ein Trennelement, dass zwischen zwei Tab-Elementen angezeigt wird.
Tabelle 5.2: Die verschiedenen TabStrip-Objekte
Features Das TabStrip-WebControl führt eine Vielzahl von Features ein, die in der folgenden Tabelle näher beschrieben werden. Feature
Beschreibung
Textbasierte Registerkarten
Erstellt Registerkarten, die einen Text enthalten.
Bildbasierte Registerkarten
Erstellt Registerkarten, die Bilder enthalten.
Ausrichtung
Die Registerkarten können entweder horizontal oder vertikal ausgerichtet werden.
Tabelle 5.3: Die verschiedenen Features des TabStrip-Controls
Wie auch all den anderen WebControls, gibt es eine Möglichkeit, dass man dem Steuerelement verschiedene Stile zuweisen kann. Dabei unterstützt das TabStrip-Control zur Zeit die folgenden Stile: Stil
Beschreibung
Default
Definiert das Aussehen, wenn die Registerkarte nicht ausgewählt ist und wenn sich die Maus nicht über der Registerkarte befindet.
Selected
Definiert das Aussehen, wenn die Registerkarte durch den Benutzer ausgewählt wurde.
Hover
Definiert das Aussehen, wenn der Benutzer die Maus über die Registerkarte bewegt.
Tabelle 5.4: Die verschiedenen Stile des TabStrip-Controls
Sandini Bib Das TabStrip WebControl
373
Das Aussehen der Registerkarten kann man bequem über CSS-Eigenschaften steuern. Das folgende Codefragment verwendet verschiedene CSS-Eigenschaften für die Definition des TabStrip-WebControls.
Sandini Bib 378
5
Internet Explorer WebControls
Listing 5.6: Verwendung von CSS-Eigenschaften für die Toolbar
Das Beispiel erzeugt die folgende Ausgabe:
Abbildung 5.4: Verwendung von CSS-Eigenschaften für die Toolbar
Außerdem können auch die Stile einer Toolbar im serverseitigen Code angesprochen werden. Dies wird im folgenden Beispiel demonstriert. 0) { e.Row.RowError += "VB.NET darf kein Buchtitel sein."; } } } Verwendung von Ereignissen
Listing 6.14: Verwendung von Ereignissen in einer DataTable
Das Beispiel erzeugt die in Abbildung 6.14 gezeigte Ausgabe. Zuerst wird wieder eine DataTable mit zwei Datensätzen angelegt. Anschließend wird für das Ereignis RowChanged ein Eventhandler angelegt. Dies geschieht mit der folgenden Anweisung: myDataTable.RowChanged += new DataRowChangeEventHandler(OnRowChanged);
Anschließend wird die DataTable an das DataGrid gebunden, und die Datensätze werden angezeigt. Danach werden noch alle Fehlermeldungen ausgegeben, die sich im Laufe der Änderungen ergeben haben.
Sandini Bib Arbeiten mit relationalen Daten
443
Abbildung 6.14: Verwendung von Ereignissen in einer DataTable if (myDataTable.HasErrors) { foreach (DataRow myRow in myDataTable.Rows) { strResult += myRow.RowError + "