1 4. .NET • Kombination von Frameworks, Sprachen, Werkzeugen, Webservices, . . . zur einfachen Entwicklung von Windows-Applikationen (inkl. Web-Appl.) • gemeinsames Laufzeitsystem CLR (Common Language Runtime): abstrakte/virtuelle Maschine für alle .NET-Srachen (VB, C#, J#, Managed C++, Java, Fortran, Java, Haskell, Perl, . . . ) • basiert auf gemeinsamer Zwischensprache CIL (Common Intermediate Language) • gemeinsames Typsystem CTS (Common Type System) • (sofortige) Just-in-time-Compilierung (→ Sicherheit) • umfassende OO-Klassenbibliothek (Kollektionen, Threads, Reflection, XMLVerarbeitung, Windows Forms, Web Forms, ADO.NET, ASP.NET) • Versionierung (“Rettung aus der DLL-Hölle”) • sehr gute Unterstützung von Webservices 2 4.1 C# • sehr Java-ähnliche OO-Programmiersprache mit ? (Einfach-)Vererbung, Interfaces ? generische Typen (besser als in Java, da in CLR) ? Garbage-Collection ? Code-Verifikation (z.B. int nicht als Referenz) ? Exceptions (müssen nicht abgefangen oder deklariert werden) ? Reflection ? Iteratoren, foreach-Schleife ? Assembly: (vgl. .jar-Datei in Java) ∗ Sammlung von Klassen mit Manifest (Inhaltsverzeichnis) und Metadaten über Schnittstellen ∗ als EXE oder DLL bereitgestellt (in Ordner oder Global Assembly Cache) ∗ kein Eintrag in Windows Registry erforderlich 3 Unterschiede zu Java • auch Basistypen als Objekte verwendbar (Auto-Boxing und -Unboxing) • (neben Referenztypen) Werttypen (structs, enum); auf Stack statt Heap • neben call-by-value auch call-by-reference (T m(ref T2 p){...}, o.m(ref a)) • mehrdimenionale Arrays als Speicherblock (statt Arrays von Arrays) z.B. int[,] a = new int[4,5]; ... x = a[2,0]; • Property = ˆ Attribut mit getter und setter, z.B.: class C{ private in anzahl; public int Anzahl{set{anzahl = value;} get{return anzahl}} } C o; ... o.Anzahl = o.Anzahl + 1; 4 Unterschiede zu Java (2) • Namensraum (= ˆ package) auf mehrere Verzeichnisse aufteilbar • eine Datei umfasst ggf. mehrere Namensräume mit ggf. mehreren Klassen • in unsafe-Programmteilen können Typregeln ignoriert werden • keine anonymen Klassen (dafür (ggf. anonyme) Methoden (delegates)) zur Ereignisbehandlung • Objekte ggf. auf dem Stack (statt Heap) • (eingeschränktes) goto • überschreibbare Methoden als virtual deklariert (überschreibende als override, verdeckende als new) • sealed-Klassen haben keine Unterklassen (vgl. final in Java) 5 4.2 ASP.NET • zur Gestaltung dynamischer Webseiten mit Interaktion über Formulare • Webform: HTML-Seite mit integrierten Steuerelementen • wird transformiert in reines HTML mit versteckten Formularfeldern und serverseitigem Code zur Ereignisbehandlung • nicht nur das Abschicken eines Formulars, sondern die Bedienung jedes Steuerelements können ein Ereignis auslösen, das serverseitig bearbeitet wird • das Ausfüllen eines Formulars und seine Bearbeitung erfolgen in mehreren httpRoundtrips zwischen Server und Browser 6 ASP.NET (Fortsetzung) • Code behind: im Webformular kann auf Code zurückgegriffen werden, der in einer Oberklasse der Klasse zum Webform bereitgestellt wurde • dadurch saubere Trennung von HTML (Webform) und C#-Code • Unterschied zu JSP: ein Webform beschreibt die in Roundtrips befindliche aktuelle Seite (nicht die nächste) • zum Wechsel der Seite muss ein Link oder eine Umleitung genutzt werden • Masterseiten erlauben ein einheitliches Layout eines Webauftritts • Sitzungsattribut speichert Informationen Sitzungs-übergreifend (wie JSP) • Cookies können gesetzt und ausgelesen werden • Validatoren für Benutzereingaben (werden in JavaScript übersetzt) 7 Beispiel: Webform <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="BiblLogin.aspx.cs" Inherits="Bibliothek.BiblLogin" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Willkommen in der Bibliothek</title> </head> <body> <h1>Willkommen in der Bibliothek</h1> <form id="form1" runat="server"> <div> <asp:Label ID="PasswortLabel" runat="server" Text="Passwort"></asp:Label> <asp:TextBox ID="Passwort" TextMode="Password" runat="server"></asp:TextBox></br> <asp:RequiredFieldValidator ID="val" ControlToValidate="Passwort" ErrorMessage="Passwort angeben!" runat="server"/> <asp:Button ID="Ok" runat="server" Text="Ok" OnClick="Login"/> </div> </form> </body> </html> 8 Beispiel: An Browser übertragene HTML-Seite (ohne Validator) <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head><title>Willkommen in der Bibliothek</title></head> <body> <h1>Willkommen in der Bibliothek</h1> <form name="form1" method="post" action="BiblLogin.aspx" id="form1"> <div> <input type="hidden" name=" VIEWSTATE" id=" VIEWSTATE" value="/wEPDwUKMTIxNDIyOTM0MmRkBODT+ziyM71eMrjNc9ap6hwWoCg=" /> </div> <div> <span id="PasswortLabel">Passwort</span> <input name="Passwort" type="text" id="Passwort" /></br> <input type="submit" name="Ok" value="Ok" id="Ok" /> </div> <div> <input type="hidden" name=" EVENTVALIDATION" id=" EVENTVALIDATION" value="/wEWAwKclIP9BgLSxaDECgLh777vDG/iFwXGlUrZVFnVqg/27x7y75wb" /> </div> </form> </body> </html> 9 Beispiel: Zugehöriger Hintergrundcode using using ... using using System; System.Data; System.Web.UI.HtmlControls; Bibliothek.Web; namespace Bibliothek{ public partial class BiblLogin : System.Web.UI.Page { public void Login(object sender, EventArgs ev){ string passw = Passwort.Text; if (passw != "geheim123"){ Context.Session.Add("Login",false); Passwort.Text = "Fehlerhafte Eingabe";} else {Context.Session.Add("Login",true); Response.Redirect("Web/Bibliothek.aspx");}} } } • hinzu kommt eine automatisch generierte partielle Klasse BiblLogin • hierin werden Variablen zum Zugriff auf Formularwerte wie Passwort deklariert 10 Beispiel 2: Webform “Benutzer Anlegen” <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="BenutzerAnlegenWeb.aspx.cs" Inherits="Bibliothek.Web.BenutzerAnlegenWeb" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ...> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"><title>Benutzer Anlegen</title></head> <body> <h1>Benutzer Anlegen</h1> <form id="form1" runat="server"> <div> <asp:Label ID="NameLabel" runat="server" Text="Name"></asp:Label> <asp:TextBox ID="Bname" runat="server"></asp:TextBox><br/> <asp:Label ID="Adresslabel" runat="server" Text="Adresse"></asp:Label> <asp:TextBox ID="Badresse" runat="server"></asp:TextBox><br/> <asp:Button ID="Ok" runat="server" Text="Ok" OnClick="Anlegen"/> </div> </form> <a href="BenutzerverwaltungWeb.aspx">zur&uuml;ck</a> </body> </html> 11 Beispiel: Hintergrundcode zu “Benutzer Anlegen” using Bibliothek.Geschäftslogik; ... namespace Bibliothek.Web { public partial class BenutzerAnlegenWeb : System.Web.UI.Page { protected void Page Load(object sender, EventArgs e) { if ((Context.Session["Login"] == null) || (Convert.ToBoolean(Context.Session["Login"]) == false)) Response.Redirect("BiblLogin.aspx");} public void Anlegen(object sender, EventArgs ev) { string name = Bname.Text; string adresse = Badresse.Text; if ((name == null) || (name == "")) Response.Redirect("Fehler.htm"); try{ Benutzerverwaltung bv = new Benutzerverwaltung(); bv.BenutzerAnlegen(name,adresse); Response.Write("Benutzer "+name+" gespeichert!"); Bname.Text = ""; Badresse.Text = "";} catch(Exception){Response.Redirect("Fehler.htm");}} } } 12 4.3 ADO.NET • Framework zum Zugriff auf Datenbanken und andere Datenquellen • verbindsorientierter oder verbindungsloser Zugriff • Schritte beim verbindungsorientierten Zugriff: ? Verbindung zur DB aufbauen ? wiederhole: ∗ SQL-Anweisung mit Platzhaltern (@platzhaltername) erstellen ∗ Platzhalter mit Werten befüllen, z.B. mit AddWithValue("@platzhaltername",wert) ∗ SQL-Anweisung ausführen (ExecuteScalar, ExecuteNonQuery, ExecuteReader) ∗ Ergebnisse verarbeiten (ggf. in Schleife) ? Verbindung abbauen 13 Beispiel: Geschäftslogik mit ADO.NET using System.Data.SqlClient; ... namespace Bibliothek.Geschäftslogik{ public class Benutzerverwaltung{ public void BenutzerAnlegen(string name, string adresse){ SqlConnection verbindung = new SqlConnection(ConfigurationManager.ConnectionStrings["Bibliothek"].ConnectionString); verbindung.Open(); string sqltext = "SELECT COUNT(*) FROM Benutzer WHERE Name = @name"; SqlCommand sql = new SqlCommand(sqltext, verbindung); sql.Parameters.AddWithValue("@name", name); int anzahl = (int) sql.ExecuteScalar(); if (anzahl > 0) throw new Exception("Name schon vergeben"); else{ sqltext = "INSERT INTO Benutzer (Name, Adresse)" + "VALUES (@name, @adresse);" + "SELECT CAST(SCOPE IDENTITY() AS INT);"; sql = new SqlCommand(sqltext, verbindung); sql.Parameters.AddWithValue("@name", name); sql.Parameters.AddWithValue("@adresse", adresse); int bid = (int) sql.ExecuteScalar();} verbindung.Close();}} } 14 Beispiel 2: Mengenwertige Anfragen verarbeiten (1) public string AusleiheAnzeigen(int kundennr) { SqlConnection verbindung = new SqlConnection(ConfigurationManager.ConnectionStrings["Bibliothek"].ConnectionString); try { verbindung.Open(); } catch (Exception e) { Console.Write(e); } string sqltext = "SELECT Name, Bid, Bezeichnung, Inventarnr, Datum " + "FROM Benutzer B, Ausleihe A, Exemplar E, Medium M " + "WHERE B.Bid = @bid AND B.Bid = A.Benutzer AND A.Exemplar = E.Inventarnr " + " AND E.Medium = M.Id AND M.Medientyp = 0; " + "SELECT Name, Bid, Bezeichnung, Inventarnr, Datum " + "FROM Benutzer B, Ausleihe A, Exemplar E, Medium M " + "WHERE B.Bid = @bid AND B.Bid = A.Benutzer AND A.Exemplar = E.Inventarnr " + " AND E.Medium = M.Id AND Medientyp = 1"; SqlCommand sql = new SqlCommand(sqltext, verbindung); sql.Parameters.AddWithValue("@bid", kundennr); SqlDataReader erg = sql.ExecuteReader(); ... (b.w.) ... 15 Beispiel 2: Mengenwertige Anfragen verarbeiten (2) ... (s.o.) ... string[] medientext = { "Bücher", "CDs" }; string ausgabe = ""; int i = 0; do { ausgabe += "<h1>Ausgeliehene " + medientext[i] + "</h1>"; ausgabe += "<Table border="1">"; ausgabe += "<tr><th>Name</th><th>Benutzernr</th><th>Bezeichnung</th>"; ausgabe += "<th>Inventarnr</th><th>Datum</th></tr>"; while (erg.Read()) { object[] werte = new object[erg.FieldCount]; erg.GetSqlValues(werte); ausgabe += "<tr>"; foreach (object wert in werte) ausgabe += "<td>"+wert.ToString()+"</td>"; ausgabe += "</tr>";} ausgabe += "</Table>"; i++;} while (erg.NextResult()); verbindung.Close(); return ausgabe; } 16 Anmerkungen zu den Beispielen • statt SQL direkt aus der Geschäftslogik aufzurufen, kann man eine OR-Mapping-Schicht zwischenschalten • Beispiel 2 vermischt der Kürze halber Präsentationslogik und Geschäftslogik • deutlich besser wäre: ? eine Datenstruktur mit Ergebnissen (DTO) an Präsentationsschicht liefern ? (nur) die Präsentationschicht HTML-Code generieren lassen • die Transaktionsverarbeitung fehlt noch (s.u.) 17 Transaktionsverarbeitung • Schritte: BeginTransaction, jedes SqlCommand der Tr. zuordnen, Commit bzw. Rollback • lokale oder verteilte Transaktionen mit vorgegebenem Isolationslevel • Isolationslevel: ? ReadUncommitted: kein Schutz, Sperren werden ignoriert ? ReadCommitted: ∗ Tupel können nur nach commit gelesen werden ∗ die zugehörigen Tabellen können sich während der Transaktion ändern ∗ wiederholtes Lesen einer Tabelle liefert ggf. andere Ergebnisse ? ReadRepeatable: wiederholte Abfrage liefern die gleichen Ergebnisse ? Serializable ? Trade-off zwischen Effizienz und unerwünschten Effekten 18 Beispiel: Transaktionsverarbeitung mit ADO.NET public void BenutzerAnlegen(string name, string adresse){ SqlConnection verbindung = new SqlConnection(ConfigurationManager.ConnectionStrings["Bibliothek"].ConnectionString); SqlTransaction trans = null; try { verbindung.Open(); trans = verbindung.BeginTransaction(IsolationLevel.ReadCommitted); string sqltext = "SELECT COUNT(*) FROM Benutzer WHERE Name = @name"; SqlCommand sql = new SqlCommand(sqltext, verbindung); sql.Parameters.AddWithValue("@name", name); sql.Transaction = trans; int anzahl = (int) sql.ExecuteScalar(); if (anzahl > 0) throw new Exception("Name schon vergeben"); else{ sqltext = "INSERT INTO Benutzer (Name, Adresse)" + "VALUES (@name, @adresse);" + "SELECT CAST(SCOPE IDENTITY() AS INT);"; sql = new SqlCommand(sqltext, verbindung); sql.Parameters.AddWithValue("@name", name); sql.Parameters.AddWithValue("@adresse", adresse); sql.Transaction = trans; int bid = (int) sql.ExecuteScalar(); trans.Commit();}} catch (Exception e) {if (trans != null) trans.Rollback(); Console.WriteLine(e);} finally {verbindung.Close();} }