Einer der Gründe für den Erfolg von ASP.NET besteht darin, dass es die Eintrittsbarriere für Webentwickler senkt. Sie müssen keinen Doktortitel in Informatik haben, um ASP.NET-Code zu schreiben. Viele ASP.NET-Entwickler, die ich bei der Arbeit treffe, sind Autodidakten und haben Microsoft® Excel®-Tabellen geschrieben, bevor sie C# oder Visual Basic® geschrieben haben. Jetzt schreiben sie Webanwendungen und insgesamt verdienen sie Lob für die Arbeit, die sie leisten.
Aber mit der Macht geht auch Verantwortung einher, und selbst erfahrene ASP.NET-Entwickler können Fehler machen. In meiner langjährigen Beratung zu ASP.NET-Projekten habe ich festgestellt, dass bestimmte Fehler besonders häufig zu wiederkehrenden Defekten führen. Einige dieser Fehler können die Leistung beeinträchtigen. Andere Fehler können die Skalierbarkeit beeinträchtigen. Einige Fehler können Entwicklungsteams auch wertvolle Zeit kosten, Fehler und unerwartetes Verhalten aufzuspüren.
Hier sind 10 Fallstricke, die bei der Veröffentlichung von ASP.NET-Produktionsanwendungen zu Problemen führen können, und Möglichkeiten, diese zu vermeiden. Alle Beispiele stammen aus meiner eigenen Erfahrung beim Erstellen echter Webanwendungen in echten Unternehmen, und in einigen Fällen stelle ich Kontext bereit, indem ich einige der Probleme beschreibe, auf die das ASP.NET-Entwicklungsteam während des Entwicklungsprozesses gestoßen ist.
LoadControl und Ausgabecaching Es gibt nur sehr wenige ASP.NET-Anwendungen, die keine Benutzersteuerelemente verwenden. Vor Masterseiten verwendeten Entwickler Benutzersteuerelemente, um allgemeine Inhalte wie Kopf- und Fußzeilen zu extrahieren. Selbst in ASP.NET 2.0 bieten Benutzersteuerelemente eine effiziente Möglichkeit, Inhalte und Verhalten zu kapseln und die Seite in Bereiche zu unterteilen, deren Cachefähigkeit unabhängig von der Seite als Ganzes gesteuert werden kann (ein Prozess, der als Segmente bezeichnet wird). ).
Benutzersteuerelemente können deklarativ oder erzwungen geladen werden. Das erzwungene Laden basiert auf Page.LoadControl, das ein Benutzersteuerelement instanziiert und eine Steuerelementreferenz zurückgibt. Wenn das Benutzersteuerelement Mitglieder eines benutzerdefinierten Typs enthält (z. B. eine öffentliche Eigenschaft), können Sie die Referenz umwandeln und über Ihren Code auf das benutzerdefinierte Mitglied zugreifen. Das Benutzersteuerelement in Abbildung 1 implementiert eine Eigenschaft namens BackColor. Der folgende Code lädt das Benutzersteuerelement und weist BackColor einen Wert zu:
protected void Page_Load(object sender, EventArgs e){// Laden Sie das Benutzersteuerelement und fügen Sie es der Seite hinzu. Control control = LoadControl("~/MyUserControl.ascx") ;PlaceHolder1 .Controls.Add(control);//Legen Sie die Hintergrundfarbe fest ((MyUserControl)control).BackColor = Color.Yellow;}
Der obige Code ist eigentlich sehr einfach, aber er ist eine Falle, in die der unvorsichtige Entwickler tappen kann. Können Sie den Fehler finden?
Wenn Sie vermutet haben, dass das Problem mit dem Ausgabe-Caching zusammenhängt, liegen Sie richtig. Wie Sie sehen können, lässt sich das obige Codebeispiel gut kompilieren und ausführen. Wenn Sie jedoch versuchen, die folgende Anweisung (die völlig zulässig ist) zu MyUserControl.ascx hinzuzufügen:
<%@ OutputCache Duration="5" VaryByParam="None" %>
Wenn Sie dann die Seite das nächste Mal ausführen, werden Sie eine InvalidCastException (oh Freude!) und die folgende Fehlermeldung sehen:
„Ein Objekt vom Typ ‚System.Web.UI.PartialCachingControl‘ kann nicht in den Typ ‚MyUserControl‘ umgewandelt werden.“
Daher läuft dieser Code ohne die OutputCache-Direktive einwandfrei, schlägt jedoch fehl, wenn die OutputCache-Direktive hinzugefügt wird. ASP.NET sollte sich nicht so verhalten. Seiten (und Steuerelemente) sollten unabhängig vom Ausgabe-Caching sein. Was bedeutet das also?
Das Problem besteht darin, dass LoadControl bei aktiviertem Ausgabe-Caching für ein Benutzersteuerelement keinen Verweis mehr auf die Steuerelementinstanz zurückgibt, sondern stattdessen einen Verweis auf eine PartialCachingControl-Instanz, die die Steuerelementinstanz möglicherweise umschließt oder nicht, je nachdem, ob die Steuerelementinstanz aktiviert ist Die Ausgabe des Steuerelements ist Cache. Wenn ein Entwickler daher LoadControl aufruft, um ein Benutzersteuerelement dynamisch zu laden, und die Steuerelementreferenz konvertiert, um auf steuerelementspezifische Methoden und Eigenschaften zuzugreifen, muss er darauf achten, wie er dies tut, damit der Code unabhängig davon ausgeführt wird, ob ein Steuerelement vorhanden ist OutputCache-Direktive.
Abbildung 2 zeigt die korrekte Methode zum dynamischen Laden eines Benutzersteuerelements und zum Konvertieren der zurückgegebenen Steuerelementreferenz. Hier ist eine Zusammenfassung der Funktionsweise:
• Wenn in der ASCX-Datei eine OutputCache-Direktive fehlt, gibt LoadControl eine MyUserControl-Referenz zurück. Page_Load konvertiert den Verweis in MyUserControl und legt die BackColor-Eigenschaft des Steuerelements fest.
• Wenn die ASCX-Datei eine OutputCache-Direktive enthält und die Ausgabe des Steuerelements nicht zwischengespeichert wird, gibt LoadControl einen Verweis auf das PartialCachingControl zurück, dessen CachedControl-Eigenschaft einen Verweis auf das zugrunde liegende MyUserControl enthält. Page_Load konvertiert PartialCachingControl.CachedControl in MyUserControl und legt die BackColor-Eigenschaft des Steuerelements fest.
• Wenn die ASCX-Datei eine OutputCache-Direktive enthält und die Ausgabe des Steuerelements zwischengespeichert wird, gibt LoadControl einen Verweis auf das PartialCachingControl zurück, dessen CachedControl-Eigenschaft leer ist. Beachten Sie, dass Page_Load nicht mehr fortgesetzt wird. Die BackColor-Eigenschaft des Steuerelements kann nicht festgelegt werden, da die Ausgabe des Steuerelements aus dem Ausgabecache stammt. Mit anderen Worten: Es gibt überhaupt kein MyUserControl, für das Eigenschaften festgelegt werden könnten.
Der Code in Abbildung 2 wird unabhängig davon ausgeführt, ob die .ascx-Datei eine OutputCache-Direktive enthält. Auch wenn es etwas komplizierter aussieht, vermeidet es ärgerliche Fehler. Einfach bedeutet nicht immer auch leicht zu warten.
Zurück zum Anfang Sitzungen und Ausgabe-Caching Apropos Ausgabe-Caching: Sowohl ASP.NET 1.1 als auch ASP.NET 2.0 weisen ein potenzielles Problem auf, das sich auf Ausgabe-Cache-Seiten auf Servern auswirkt, auf denen Windows Server™ 2003 und IIS 6.0 ausgeführt werden. Ich habe persönlich gesehen, dass dieses Problem zweimal auf einem ASP.NET-Produktionsserver auftrat und beide Male durch Deaktivieren der Ausgabepufferung behoben wurde. Später erfuhr ich, dass es eine bessere Lösung gibt, als das Ausgabe-Caching zu deaktivieren. So sah es aus, als ich zum ersten Mal auf dieses Problem stieß.
Was passierte, war, dass eine Website (nennen wir sie hier Contoso.com, die eine öffentliche E-Commerce-Anwendung in einem kleinen ASP.NET-Webbereich ausführt) mein Team kontaktierte und sich darüber beschwerte, dass ein „Cross-Threading“-Fehler aufgetreten sei. Kunden, die die Website Contoso.com nutzen, verlieren oft plötzlich die von ihnen eingegebenen Daten, sehen aber stattdessen Daten, die sich auf einen anderen Benutzer beziehen. Nach einer kleinen Analyse haben wir festgestellt, dass die Beschreibung des Cross-Threading nicht zutreffend ist; Es scheint, dass Contoso.com Daten im Sitzungsstatus speichert und Benutzer aus irgendeinem Grund gelegentlich und zufällig eine Verbindung zu den Sitzungen anderer Benutzer herstellen.
Einer meiner Teammitglieder hat ein Diagnosetool geschrieben, das Schlüsselelemente jeder HTTP-Anfrage und -Antwort protokolliert, einschließlich des Cookie-Headers. Anschließend installierte er das Tool auf dem Webserver von Contoso.com und ließ es einige Tage lang laufen. Die Ergebnisse sind sehr offensichtlich. Etwa einmal pro 100.000 Anfragen weist ASP.NET einer völlig neuen Sitzung korrekt eine Sitzungs-ID zu und gibt die Sitzungs-ID im Set-Cookie-Header zurück. Anschließend wird bei der nächsten unmittelbar benachbarten Anforderung dieselbe Sitzungs-ID (d. h. derselbe Set-Cookie-Header) zurückgegeben, obwohl die Anforderung bereits mit einer gültigen Sitzung verknüpft war und die Sitzungs-ID im Cookie korrekt übermittelt wurde. Tatsächlich schaltet ASP.NET Benutzer nach dem Zufallsprinzip aus ihren eigenen Sitzungen aus und verbindet sie mit anderen Sitzungen.
Wir waren überrascht und machten uns auf die Suche nach dem Grund. Wir haben zunächst den Quellcode von Contoso.com überprüft und zu unserer Erleichterung festgestellt, dass das Problem nicht vorhanden war. Um sicherzustellen, dass das Problem nicht mit dem Anwendungshost im Webbereich zusammenhängt, haben wir als Nächstes nur einen Server laufen lassen und alle anderen heruntergefahren. Das Problem besteht weiterhin, was nicht verwunderlich ist, da unsere Protokolle zeigen, dass übereinstimmende Set-Cookie-Header niemals von zwei verschiedenen Servern stammen. ASP.NET generiert versehentlich doppelte Sitzungs-IDs, was unglaublich ist, da es die .NET Framework RNGCryptoServiceProvider-Klasse zum Generieren dieser IDs verwendet und die Sitzungs-IDs lang genug sind, um sicherzustellen, dass dieselbe ID nie zweimal generiert wird (zumindest beim nächsten Mal). wird in Billionen von Jahren nicht zweimal erzeugt). Darüber hinaus erklärt dies nicht, selbst wenn der RNGCryptoServiceProvider versehentlich wiederholte Zufallszahlen generiert, warum ASP.NET auf mysteriöse Weise die gültige Sitzungs-ID durch eine neue (die nicht eindeutig ist) ersetzt.
Aus einer Ahnung heraus beschlossen wir, einen Blick auf das Ausgabe-Caching zu werfen. Wenn das OutputCacheModule eine HTTP-Antwort zwischenspeichert, muss darauf geachtet werden, dass der Set-Cookie-Header nicht zwischengespeichert wird. Andernfalls verbindet eine zwischengespeicherte Antwort mit einer neuen Sitzungs-ID alle Empfänger der zwischengespeicherten Antwort (und den Benutzer, dessen Anfrage die zwischengespeicherte Antwort generiert hat). zur gleichen Sitzung. Wir haben den Quellcode überprüft. Contoso.com hat auf beiden Seiten das Ausgabe-Caching aktiviert. Wir haben das Ausgabe-Caching deaktiviert. Infolgedessen lief die Anwendung mehrere Tage lang ohne ein einziges sitzungsübergreifendes Problem. Danach lief es über zwei Jahre fehlerfrei. In einem anderen Unternehmen mit einer anderen Anwendung und einem anderen Satz an Webservern sahen wir, dass genau das gleiche Problem verschwand. Genau wie bei Contoso.com wird das Problem durch Entfernen des Ausgabecaches gelöst.
Microsoft bestätigte später, dass dieses Verhalten auf ein Problem im OutputCacheModule zurückzuführen ist. (Möglicherweise wurde zum Zeitpunkt des Lesens dieses Artikels bereits ein Update veröffentlicht.) Wenn ASP.NET mit IIS 6.0 verwendet wird und das Kernelmodus-Caching aktiviert ist, entfernt das OutputCacheModule manchmal den Set-Cookie-Header nicht aus den zwischengespeicherten Antworten, die es weiterleitet zu Http.sys . Das Folgende ist die spezifische Abfolge von Ereignissen, die zu dem Fehler führt:
• Ein Benutzer, der die Site nicht kürzlich besucht hat (und daher keine entsprechende Sitzung hat), fordert eine Seite an, für die das Ausgabe-Caching aktiviert ist, deren Ausgabe jedoch derzeit nicht verfügbar ist im Cache.
• Die Anfrage führt Code aus, der auf die zuletzt erstellte Sitzung des Benutzers zugreift, wodurch das Sitzungs-ID-Cookie im Set-Cookie-Header der Antwort zurückgegeben wird.
• OutputCacheModule stellt eine Ausgabe für Http.sys bereit, kann jedoch den Set-Cookie-Header nicht aus der Antwort entfernen.
• Http.sys gibt bei nachfolgenden Anfragen zwischengespeicherte Antworten zurück und verbindet fälschlicherweise andere Benutzer mit der Sitzung.
Die Moral der Geschichte? Sitzungsstatus und Ausgabe-Caching im Kernel-Modus passen nicht zusammen. Wenn Sie den Sitzungsstatus auf einer Seite mit aktiviertem Ausgabe-Caching verwenden und die Anwendung unter IIS 6.0 ausgeführt wird, müssen Sie das Ausgabe-Caching im Kernelmodus deaktivieren. Sie profitieren weiterhin vom Ausgabe-Caching, aber da das Ausgabe-Caching im Kernel-Modus viel schneller ist als das normale Ausgabe-Caching, ist das Caching nicht so effizient. Weitere Informationen zu diesem Problem finden Sie unter support.microsoft.com/kb/917072.
Sie können das Ausgabe-Caching im Kernelmodus für eine einzelne Seite deaktivieren, indem Sie das Attribut VaryByParam="*" in die OutputCache-Direktive der Seite aufnehmen. Dies kann jedoch zu einem plötzlichen Anstieg des Speicherbedarfs führen. Ein anderer, sichererer Ansatz besteht darin, das Kernelmodus-Caching für die gesamte Anwendung zu deaktivieren, indem das folgende Element in web.config eingefügt wird:
<httpRuntime enableKernelOutputCache="false" />
Sie können auch eine Registrierungseinstellung verwenden, um das Ausgabe-Caching im Kernel-Modus global zu deaktivieren, d. h. das Ausgabe-Caching im Kernel-Modus für alle Server zu deaktivieren. Weitere Informationen finden Sie unter support.microsoft.com/kb/820129.
Jedes Mal, wenn ein Kunde rätselhafte Sitzungsprobleme meldet, frage ich ihn, ob er auf irgendwelchen Seiten Ausgabe-Caching verwendet. Wenn sie Ausgabe-Caching verwenden und das Host-Betriebssystem Windows Server 2003 ist, würde ich empfehlen, das Ausgabe-Caching im Kernel-Modus zu deaktivieren. Das Problem ist in der Regel gelöst. Wenn das Problem nicht behoben wird, liegt der Fehler im Code vor. Seien Sie wachsam!
Zurück nach oben
Formularauthentifizierungsticket-Lebensdauer Können Sie das Problem mit dem folgenden Code identifizieren?
FormsAuthentication.RedirectFromLoginPage(username, true);
Dieser Code scheint in Ordnung zu sein, sollte jedoch niemals in einer ASP.NET 1.x-Anwendung verwendet werden, es sei denn, Code an anderer Stelle in der Anwendung gleicht die negativen Auswirkungen dieser Anweisung aus. Wenn Sie nicht sicher sind, warum, lesen Sie weiter.
FormsAuthentication.RedirectFromLoginPage führt zwei Aufgaben aus. Wenn FormsAuthenticationModule den Benutzer zunächst auf die Anmeldeseite umleitet, leitet FormsAuthentication.RedirectFromLoginPage den Benutzer zunächst auf die Seite weiter, die er ursprünglich angefordert hat. Zweitens wird ein Authentifizierungsticket ausgestellt (normalerweise in einem Cookie und in ASP.NET 1.x immer in einem Cookie enthalten), das es dem Benutzer ermöglicht, für einen vorgegebenen Zeitraum authentifiziert zu bleiben.
Das Problem liegt in diesem Zeitraum. In ASP.NET 1.x wird durch die Übergabe eines anderen Parameters, der „false“ ist, an „RedirectFromLoginPage“ ein temporäres Authentifizierungsticket ausgegeben, das standardmäßig nach 30 Minuten abläuft. (Sie können den Timeout-Zeitraum mithilfe des Timeout-Attributs im Element von web.config ändern.) Wenn Sie jedoch einen anderen Parameter von „true“ übergeben, wird ein dauerhaftes Authentifizierungsticket ausgestellt, das 50 Jahre lang gültig ist. Dies führt zu einem Problem, denn wenn jemand diese Authentifizierung stiehlt Ticket können sie für die Dauer des Tickets die Identität des Opfers verwenden, um auf die Website zuzugreifen. Es gibt viele Möglichkeiten, Authentifizierungstickets zu stehlen – das Untersuchen des unverschlüsselten Datenverkehrs auf öffentlichen WLAN-Zugangspunkten, das Erstellen von Skripten über Websites hinweg, das Erhalten physischen Zugriffs auf den Computer des Opfers usw. – daher ist die Weitergabe von „true“ an „RedirectFromLoginPage“ sicherer als die Deaktivierung Ihrer Website. Nicht viel besser. Glücklicherweise wurde dieses Problem in ASP.NET 2.0 behoben. RedirectFromLoginPage akzeptiert jetzt die in web.config angegebenen Zeitüberschreitungen für temporäre und permanente Authentifizierungstickets auf die gleiche Weise.
Eine Lösung besteht darin, im zweiten Parameter von RedirectFromLoginPage in ASP.NET 1.x-Anwendungen niemals true zu übergeben. Dies ist jedoch unpraktisch, da auf Anmeldeseiten häufig ein Kontrollkästchen „Angemeldet bleiben“ vorhanden ist, das der Benutzer aktivieren kann, um ein permanentes und kein temporäres Authentifizierungs-Cookie zu erhalten. Eine andere Lösung besteht darin, das Code-Snippet in Global.asax (oder das HTTP-Modul, wenn Sie es vorziehen) zu verwenden, das das Cookie mit dem permanenten Authentifizierungsticket ändert, bevor es an den Browser zurückgegeben wird.
Abbildung 3 enthält einen solchen Codeausschnitt. Wenn sich dieser Codeausschnitt in Global.asax befindet, wird die Expires-Eigenschaft des ausgehenden permanenten Forms-Authentifizierungscookies so geändert, dass das Cookie nach 24 Stunden abläuft. Sie können das Zeitlimit auf ein beliebiges Datum festlegen, indem Sie die Zeile mit dem Kommentar „Neues Ablaufdatum“ ändern.
Möglicherweise finden Sie es seltsam, dass die Methode „Application_EndRequest“ eine lokale Hilfsmethode (GetCookieFromResponse) aufruft, um das Authentifizierungscookie auf die ausgehende Antwort zu überprüfen. Die Helper-Methode ist eine Problemumgehung für einen weiteren Fehler in ASP.NET 1.1, der dazu führte, dass der Antwort falsche Cookies hinzugefügt wurden, wenn Sie den Zeichenfolgenindexgenerator der HttpCookieCollection zur Prüfung auf nicht vorhandene Cookies verwendet haben. Die Verwendung eines ganzzahligen Indexgenerators als GetCookieFromResponse löst das Problem.
Zurück zum Anfang Ansichtsstatus: Der stille Leistungskiller In gewisser Weise ist der Ansichtsstatus das Größte, was es je gab. Schließlich ermöglicht der Ansichtsstatus Seiten und Steuerelementen, den Status zwischen Postbacks beizubehalten. Daher müssen Sie keinen Code schreiben, um zu verhindern, dass der Text in einem Textfeld verschwindet, wenn auf eine Schaltfläche geklickt wird, oder um die Datenbank erneut abzufragen und das DataGrid nach einem Postback erneut zu binden, wie Sie es bei herkömmlichem ASP tun würden.
Der Ansichtszustand hat jedoch eine Kehrseite: Wenn er zu groß wird, wird er zu einem stillen Leistungskiller. Einige Steuerelemente, z. B. Textfelder, treffen Entscheidungen basierend auf dem Ansichtsstatus. Andere Steuerelemente (insbesondere DataGrid und GridView) bestimmen ihren Ansichtsstatus basierend auf der Menge der angezeigten Informationen. Ich wäre entmutigt, wenn ein GridView 200 oder 300 Datenzeilen anzeigen würde. Auch wenn der Ansichtsstatus von ASP.NET 2.0 etwa halb so groß ist wie der Ansichtsstatus von ASP.NET 1.x, kann eine fehlerhafte GridView die effektive Bandbreite der Verbindung zwischen dem Browser und dem Webserver leicht um 50 % oder mehr reduzieren.
Sie können den Ansichtsstatus für einzelne Steuerelemente deaktivieren, indem Sie EnableViewState auf „false“ festlegen. Einige Steuerelemente (insbesondere das DataGrid) verlieren jedoch einige Funktionen, wenn sie den Ansichtsstatus nicht verwenden können. Eine bessere Lösung zur Steuerung des Ansichtsstatus besteht darin, ihn auf dem Server zu belassen. In ASP.NET 1.x konnten Sie die Methoden „LoadPageStateFromPersistenceMedium“ und „SavePageStateToPersistenceMedium“ der Seite überschreiben und den Ansichtszustand nach Ihren Wünschen behandeln. Der Code in Abbildung 4 zeigt eine Außerkraftsetzung, die verhindert, dass der Ansichtsstatus in ausgeblendeten Feldern beibehalten wird, und ihn stattdessen im Sitzungsstatus beibehält. Das Speichern des Ansichtszustands im Sitzungszustand ist besonders effektiv, wenn es mit dem standardmäßigen Sitzungszustandsprozessmodell verwendet wird (d. h. wenn der Sitzungszustand in einem ASP.NET-Arbeitsprozess im Speicher gespeichert wird). Wenn der Sitzungsstatus dagegen in der Datenbank gespeichert wird, kann nur durch Tests gezeigt werden, ob die Beibehaltung des Ansichtsstatus im Sitzungsstatus die Leistung verbessert oder verringert.
Der gleiche Ansatz wird in ASP.NET 2.0 verwendet, aber ASP.NET 2.0 bietet eine einfachere Möglichkeit, den Ansichtsstatus im Sitzungsstatus beizubehalten. Definieren Sie zunächst einen benutzerdefinierten Seitenadapter, dessen GetStatePersister-Methode eine Instanz der .NET Framework-Klasse SessionPageStatePersister zurückgibt:
public class SessionPageStateAdapter :System.Web.UI.Adapters.PageAdapter{public override PageStatePersister GetStatePersister () {return new SessionPageStatePersister(this.Page ) ; }}
Registrieren Sie dann den benutzerdefinierten Seitenadapter als Standardseitenadapter, indem Sie die App.browsers-Datei wie folgt im App_Browsers-Ordner Ihrer Anwendung ablegen:
<browsers><browser refID="Default"><controlAdapters><adapter controlType=" System.Web. UI.Page"adapterType="SessionPageStateAdapter" /></controlAdapters></browser></browsers>
(Sie können der Datei einen beliebigen Namen geben, solange sie die Erweiterung „.browsers“ hat.) Anschließend lädt ASP.NET den Seitenadapter und verwendet den zurückgegebenen SessionPageStatePersister, um den gesamten Seitenstatus, einschließlich des Ansichtsstatus, beizubehalten.
Ein Nachteil der Verwendung eines benutzerdefinierten Seitenadapters besteht darin, dass er global auf jede Seite in der Anwendung angewendet wird. Wenn Sie den Ansichtsstatus einiger Seiten lieber im Sitzungsstatus belassen möchten, andere jedoch nicht, verwenden Sie die in Abbildung 4 dargestellte Methode. Außerdem können bei dieser Methode Probleme auftreten, wenn der Benutzer in derselben Sitzung mehrere Browserfenster erstellt.
Zurück nach oben
SQL Server-Sitzungsstatus: Ein weiterer Leistungskiller
ASP.NET erleichtert das Speichern des Sitzungsstatus in der Datenbank: Sie müssen lediglich einen Schalter in web.config umlegen, und der Sitzungsstatus wird problemlos in die Backend-Datenbank verschoben. Dies ist eine wichtige Funktion für Anwendungen, die im Webbereich ausgeführt werden, da sie jedem Server im Bereich die gemeinsame Nutzung eines gemeinsamen Sitzungsstatus-Repositorys ermöglicht. Die zusätzliche Datenbankaktivität reduziert die Leistung einzelner Anfragen, aber die erhöhte Skalierbarkeit gleicht den Leistungsverlust aus.
Das klingt alles gut, aber die Dinge ändern sich, wenn Sie ein paar Punkte berücksichtigen:
• Selbst in Anwendungen, die den Sitzungsstatus verwenden, verwenden die meisten Seiten keinen Sitzungsstatus.
• Standardmäßig führt der ASP.NET-Sitzungsstatusmanager bei jeder Anforderung zwei Zugriffe (einen Lesezugriff und einen Schreibzugriff) auf den Sitzungsdatenspeicher durch, unabhängig davon, ob die angeforderte Seite den Sitzungsstatus verwendet.
Mit anderen Worten: Wenn Sie die SQL Server™-Sitzungsstatusoption verwenden, zahlen Sie für jede Anfrage einen Preis (zwei Datenbankzugriffe) – selbst für Anfragen nach Seiten, die nichts mit dem Sitzungsstatus zu tun haben. Dies wirkt sich direkt negativ auf den Durchsatz der gesamten Website aus.
Abbildung 5 Eliminieren Sie unnötigen Zugriff auf die Sitzungsstatusdatenbank.
Was sollten Sie also tun? Es ist ganz einfach: Deaktivieren Sie den Sitzungsstatus auf Seiten, die keinen Sitzungsstatus verwenden. Das ist immer eine gute Idee, aber besonders wichtig, wenn der Sitzungsstatus in einer Datenbank gespeichert wird. Abbildung 5 zeigt, wie der Sitzungsstatus deaktiviert wird. Wenn die Seite überhaupt keinen Sitzungsstatus verwendet, fügen Sie EnableSessionState="false" in ihre Seitenanweisung ein, etwa so:
<%@ Page EnableSessionState="false" ... %>
Diese Anweisung verhindert, dass der Sitzungsstatusmanager bei jeder Anfrage Lese- und Schreibvorgänge in der Sitzungsstatusdatenbank durchführt. Wenn die Seite Daten aus dem Sitzungsstatus liest, aber keine Daten schreibt (d. h. den Inhalt der Sitzung des Benutzers nicht ändert), legen Sie EnableSessionState wie folgt auf ReadOnly fest:
<%@ Page EnableSessionState="ReadOnly" ... %>
Wenn die Seite schließlich Lese-/Schreibzugriff auf den Sitzungsstatus erfordert, lassen Sie die Eigenschaft „EnableSessionState“ weg oder setzen Sie sie auf „true“:
<%@ Page EnableSessionState="true" ... %>
Indem Sie den Sitzungsstatus auf diese Weise steuern, stellen Sie sicher, dass ASP.NET nur dann auf die Sitzungsstatusdatenbank zugreift, wenn dies wirklich erforderlich ist. Die Eliminierung unnötiger Datenbankzugriffe ist der erste Schritt beim Aufbau leistungsstarker Anwendungen.
Die Eigenschaft „EnableSessionState“ ist übrigens öffentlich. Diese Eigenschaft ist seit ASP.NET 1.0 dokumentiert, aber ich sehe immer noch selten, dass Entwickler sie nutzen. Vielleicht, weil es für das Standardsitzungsstatusmodell im Speicher nicht sehr wichtig ist. Aber es ist wichtig für das SQL Server-Modell.
Zurück zum Anfang Nicht zwischengespeicherte Rollen Die folgende Anweisung erscheint häufig in der web.config-Datei einer ASP.NET 2.0-Anwendung und in den Beispielen, die den ASP.NET 2.0-Rollenmanager vorstellen:
<roleManager activate="true" />
Wie oben gezeigt, hat diese Aussage jedoch erhebliche negative Auswirkungen auf die Leistung. Wissen Sie warum?
Standardmäßig speichert der ASP.NET 2.0-Rollenmanager keine Rollendaten zwischen. Stattdessen konsultiert es jedes Mal den Rollendatenspeicher, um festzustellen, welcher Rolle der Benutzer ggf. angehört. Dies bedeutet, dass nach der Authentifizierung eines Benutzers alle Seiten, die Rollendaten nutzen (z. B. Seiten, die Sitemaps mit aktivierten Sicherheitsbeschneidungseinstellungen verwenden, und Seiten, deren Zugriff mithilfe rollenbasierter URL-Anweisungen in web.config eingeschränkt ist), die Rolle verursachen Manager, um den Rollendatenspeicher abzufragen. Wenn Rollen in einer Datenbank gespeichert sind, kann auf den Zugriff auf mehrere Datenbanken pro Anfrage problemlos verzichtet werden. Die Lösung besteht darin, den Rollenmanager so zu konfigurieren, dass Rollendaten in Cookies zwischengespeichert werden:
<roleManager activate="true" cacheRolesInCookie="true" />
Sie können andere <roleManager>-Attribute verwenden, um die Eigenschaften des Rollencookies zu steuern – zum Beispiel, wie lange das Cookie gültig bleiben soll (und daher wie oft der Rollenmanager zur Rollendatenbank zurückkehrt). Rollencookies werden standardmäßig signiert und verschlüsselt, sodass das Sicherheitsrisiko zwar nicht Null ist, aber gemindert wird.
Zurück zum AnfangSerialisierung der Eigenschaften der Konfigurationsdatei
Der ASP.NET 2.0-Profildienst bietet eine vorgefertigte Lösung für das Problem der Aufrechterhaltung des Status pro Benutzer, z. B. Personalisierungseinstellungen und Spracheinstellungen. Um den Profildienst zu verwenden, definieren Sie ein XML-Profil, das die Attribute enthält, die Sie im Namen eines einzelnen Benutzers beibehalten möchten. ASP.NET kompiliert dann eine Klasse, die dieselben Eigenschaften enthält und über der Seite hinzugefügte Konfigurationsdateieigenschaften einen stark typisierten Zugriff auf Klasseninstanzen bietet.
Die Profilflexibilität ist so groß, dass sogar benutzerdefinierte Datentypen als Profileigenschaften verwendet werden können. Es gibt jedoch ein Problem, das meiner Erfahrung nach dazu führt, dass Entwickler Fehler machen. Abbildung 6 enthält eine einfache Klasse namens „Posts“ und eine Profildefinition, die „Posts“ als Profilattribut verwendet. Allerdings führen diese Klasse und diese Konfigurationsdatei zur Laufzeit zu unerwartetem Verhalten. Können Sie herausfinden, warum?
Das Problem besteht darin, dass Posts ein privates Feld namens _count enthält, das serialisiert und deserialisiert werden muss, um die Klasseninstanz vollständig einzufrieren und wieder einzufrieren. Allerdings wird _count nicht serialisiert und deserialisiert, da es privat ist und der ASP.NET-Profilmanager standardmäßig XML-Serialisierung verwendet, um benutzerdefinierte Typen zu serialisieren und zu deserialisieren. Der XML-Serialisierer ignoriert nicht öffentliche Mitglieder. Daher werden Instanzen von Posts serialisiert und deserialisiert, aber jedes Mal, wenn eine Klasseninstanz deserialisiert wird, wird _count auf 0 zurückgesetzt.
Eine Lösung besteht darin, _count zu einem öffentlichen Feld statt zu einem privaten Feld zu machen. Eine andere Lösung besteht darin, _count mit einer öffentlichen Lese-/Schreibeigenschaft zu kapseln. Die beste Lösung besteht darin, Beiträge als serialisierbar zu markieren (mithilfe des SerializableAttribute) und den Profilmanager so zu konfigurieren, dass er den binären Serializer von .NET Framework zum Serialisieren und Deserialisieren von Klasseninstanzen verwendet. Diese Lösung behält das Design der Klasse selbst bei. Im Gegensatz zu XML-Serialisierern serialisieren binäre Serialisierer Felder unabhängig davon, ob auf sie zugegriffen werden kann. Abbildung 7 zeigt die korrigierte Version der Posts-Klasse und hebt die geänderte zugehörige Profildefinition hervor.
Beachten Sie Folgendes: Wenn Sie einen benutzerdefinierten Datentyp als Profileigenschaft verwenden und dieser Datentyp über nicht öffentliche Datenelemente verfügt, die serialisiert werden müssen, um eine Instanz des Typs vollständig zu serialisieren, verwenden Sie serializeAs=" Binär“ in den Eigenschaften der Eigenschaftsdeklaration und stellen Sie sicher, dass der Typ selbst serialisierbar ist. Andernfalls findet keine vollständige Serialisierung statt und Sie verschwenden Zeit damit, herauszufinden, warum das Profil nicht funktioniert.
Zurück zum Anfang Thread-Pool-Sättigung Ich bin oft sehr überrascht über die tatsächliche Anzahl der ASP.NET-Seiten, die ich sehe, wenn ich eine Datenbankabfrage ausführe und 15 Sekunden oder länger auf die Rückgabe von Abfrageergebnissen warte. (Ich habe auch 15 Minuten gewartet, bevor ich die Ergebnisse meiner Abfrage gesehen habe!) Manchmal ist die Verzögerung eine unvermeidbare Folge der großen zurückgegebenen Datenmenge; ein anderes Mal ist die Verzögerung auf ein schlechtes Datenbankdesign zurückzuführen. Aber unabhängig vom Grund führen lange Datenbankabfragen oder lange E/A-Vorgänge jeglicher Art dazu, dass der Durchsatz in ASP.NET-Anwendungen abnimmt.
Ich habe dieses Problem bereits ausführlich beschrieben, daher werde ich hier nicht zu sehr ins Detail gehen. Es genügt zu sagen, dass ASP.NET auf einen begrenzten Thread-Pool angewiesen ist, um Anforderungen zu verarbeiten. Wenn alle Threads damit beschäftigt sind, auf den Abschluss einer Datenbankabfrage, eines Webdienstaufrufs oder eines anderen E/A-Vorgangs zu warten, werden sie freigegeben, wenn ein Vorgang abgeschlossen ist Bevor ein Thread ausgegeben wird, müssen andere Anforderungen in die Warteschlange gestellt werden. Wenn Anfragen in die Warteschlange gestellt werden, sinkt die Leistung dramatisch. Wenn die Warteschlange voll ist, führt ASP.NET dazu, dass nachfolgende Anforderungen mit einem HTTP-503-Fehler fehlschlagen. Dies ist keine Situation, die wir bei einer Produktionsanwendung auf einem Produktions-Webserver sehen möchten.
Die Lösung sind asynchrone Seiten, eine der besten, aber wenig bekannten Funktionen von ASP.NET 2.0. Eine Anforderung für eine asynchrone Seite beginnt in einem Thread, aber wenn sie einen E/A-Vorgang startet, kehrt sie zu diesem Thread und der IAsyncResult-Schnittstelle von ASP.NET zurück. Wenn der Vorgang abgeschlossen ist, benachrichtigt die Anforderung ASP.NET über IAsyncResult, und ASP.NET ruft einen weiteren Thread aus dem Pool ab und schließt die Verarbeitung der Anforderung ab. Es ist zu beachten, dass bei E/A-Vorgängen keine Thread-Pool-Threads belegt sind. Dies kann den Durchsatz erheblich verbessern, indem verhindert wird, dass Anfragen für andere Seiten (Seiten, die keine langwierigen E/A-Vorgänge ausführen) in der Warteschlange warten.
Alles über asynchrone Seiten können Sie in der Oktoberausgabe 2005 des MSDN® Magazine lesen. Jede Seite, die E/A-gebunden und nicht maschinengebunden ist und deren Ausführung lange dauert, hat gute Chancen, eine asynchrone Seite zu werden.
Wenn ich Entwicklern von asynchronen Seiten erzähle, antworten sie oft mit „Das ist großartig, aber ich brauche sie nicht in meiner Anwendung.“ Darauf antworte ich mit „Müssen irgendwelche Ihrer Seiten die Datenbank abfragen?“
Webdienste? Haben Sie die ASP.NET-
Leistungsindikatoren auf Statistiken zu in der Warteschlange befindlichen Anforderungen und durchschnittlichen Wartezeiten überprüft? Auch wenn Ihre Anwendung bisher einwandfrei läuft, kann die Last mit zunehmender Clientgröße zunehmen
der realen ASP.NET-Anwendungen erfordern asynchrone Seiten. Bitte denken Sie daran!
Zurück zum Anfang Identitätswechsel und ACL-Autorisierung Das Folgende ist eine einfache Konfigurationsanweisung, aber sie lässt meine Augen jedes Mal leuchten, wenn ich sie in web.config sehe:
<identity impersonate="true" />
Diese Direktive ermöglicht den clientseitigen Identitätswechsel in ASP.NET-Anwendungen. Es hängt ein Zugriffstoken an, das den Client darstellt, an den Thread, der die Anforderung verarbeitet, sodass die vom Betriebssystem durchgeführten Sicherheitsüberprüfungen anhand der Client-Identität und nicht anhand der Arbeitsprozessidentität erfolgen. ASP.NET-Anwendungen erfordern selten eine Verspottung; meiner Erfahrung nach aktivieren Entwickler die Verspottung oft aus den falschen Gründen. Hier erfahren Sie, warum.
Entwickler aktivieren häufig den Identitätswechsel in ASP.NET-Anwendungen, sodass Dateisystemberechtigungen verwendet werden können, um den Zugriff auf Seiten einzuschränken. Wenn Bob keine Berechtigung zum Anzeigen von Salaries.aspx hat, aktiviert der Entwickler den Identitätswechsel, sodass Bob daran gehindert werden kann, Salaries.aspx anzuzeigen, indem er die Zugriffskontrollliste (ACL) so einstellt, dass Bob die Leseberechtigung verweigert wird. Es besteht jedoch die folgende versteckte Gefahr: Für die ACL-Autorisierung ist kein Identitätswechsel erforderlich. Wenn Sie die Windows-Authentifizierung in einer ASP.NET-Anwendung aktivieren, überprüft ASP.NET automatisch die ACL für jede angeforderte ASPX-Seite und lehnt Anfragen von Aufrufern ab, die keine Berechtigung zum Lesen der Datei haben. Es verhält sich auch dann noch so, wenn die Simulation deaktiviert ist.
Manchmal ist es notwendig, die Simulation zu rechtfertigen. Aber mit gutem Design lässt sich das meist vermeiden. Angenommen, Salaries.aspx fragt eine Datenbank nach Gehaltsinformationen ab, die nur Managern bekannt sind. Mit Identitätsdiebstahl können Sie Datenbankberechtigungen nutzen, um nicht leitendem Personal die Möglichkeit zu verweigern, Gehaltsabrechnungsdaten abzufragen. Oder Sie können den Identitätswechsel ignorieren und den Zugriff auf die Gehaltsabrechnungsdaten einschränken, indem Sie eine ACL für Salaries.aspx festlegen, sodass Nicht-Administratoren keinen Lesezugriff haben. Der letztere Ansatz bietet eine bessere Leistung, da er das Verspotten vollständig vermeidet. Außerdem werden unnötige Datenbankzugriffe vermieden. Warum wird die Abfrage der Datenbank nur aus Sicherheitsgründen verweigert?
Übrigens habe ich einmal bei der Fehlerbehebung einer älteren ASP-Anwendung geholfen, die aufgrund eines unbegrenzten Speicherbedarfs regelmäßig neu gestartet wurde. Ein unerfahrener Entwickler hat die Ziel-SELECT-Anweisung in SELECT * konvertiert, ohne zu berücksichtigen, dass die abgefragte Tabelle große und zahlreiche Bilder enthielt. Das Problem wird durch einen unerkannten Speicherverlust verschärft. (Mein Bereich des verwalteten Codes!) Eine Anwendung, die jahrelang einwandfrei funktioniert hatte, funktionierte plötzlich nicht mehr, weil SELECT-Anweisungen, die früher ein oder zwei Kilobyte an Daten zurückgaben, jetzt mehrere Megabyte zurückgaben. Hinzu kommt das Problem der unzureichenden Versionskontrolle, und das Leben eines Entwicklungsteams muss „hyperaktiv“ sein – und mit „hyperaktiv“ ist es so, als müssten Sie Ihren Kindern dabei zusehen, wie sie ein nerviges Spiel spielen Nachts im Bett. Langweiliges Fußballspiel.
Theoretisch können herkömmliche Speicherlecks in ASP.NET-Anwendungen, die vollständig aus verwaltetem Code bestehen, nicht auftreten. Eine unzureichende Speichernutzung kann sich jedoch negativ auf die Leistung auswirken, da die Speicherbereinigung häufiger durchgeführt werden muss. Seien Sie auch in ASP.NET-Anwendungen vorsichtig mit SELECT *!
Verlassen Sie sich nicht ausschließlich darauf – richten Sie die Konfigurationsdatei der Datenbank ein!
Als Berater werde ich oft gefragt, warum Anwendungen nicht wie erwartet funktionieren. Kürzlich fragte jemand mein Team, warum eine ASP.NET-Anwendung nur etwa 1/100 des Durchsatzes (Anfragen pro Sekunde) abwickelt, der zum Anfordern eines Dokuments erforderlich ist. Die Probleme, die wir zuvor entdeckt haben, beziehen sich ausschließlich auf Probleme, die wir bei Webanwendungen gesehen haben, die nicht richtig funktionierten – und sind Lektionen, die wir alle ernst nehmen sollten.
Wir führen SQL Server Profiler aus und überwachen die Interaktion zwischen dieser Anwendung und der Back-End-Datenbank. Im extremeren Fall verursachte nur ein einziger Tastendruck das Auftreten von mehr als 1.500 Fehlern in der Datenbank. Auf diese Weise können Sie keine Hochleistungsanwendungen erstellen. Eine gute Architektur beginnt immer mit einem guten Datenbankdesign. Egal wie effizient Ihr Code ist, er funktioniert nicht, wenn er von einer schlecht geschriebenen Datenbank abgewogen wird.
Schlechte Datenzugriffsarchitektur resultiert normalerweise aus einem oder mehreren der folgenden:
• Schlechtes Datenbankdesign (normalerweise von Entwicklern entwickelt, nicht von Datenbankadministratoren).
• Verwendung von Datensätzen und Datenadaptern - insbesondere Dataadapter.Update, das für Windows Forms -Anwendungen und andere reichhaltige Clients gut geeignet ist, aber im Allgemeinen nicht ideal für Webanwendungen ist.
• Eine schlecht gestaltete Datenzugriffsschicht (DAL), die schlecht programmierte Berechnungen hat und viele CPU -Zyklen verbraucht, um relativ einfache Operationen auszuführen.
Das Problem muss identifiziert werden, bevor es behandelt werden kann. Die Möglichkeit, Datenzugriffsprobleme zu identifizieren, besteht darin, den SQL Server -Profiler oder ein gleichwertiges Tool auszuführen, um zu sehen, was hinter den Kulissen geschieht. Die Leistungsabstimmung wird nach Überprüfung der Kommunikation zwischen der Anwendung und der Datenbank abgeschlossen. Probieren Sie es aus - Sie könnten überrascht sein, was Sie finden.
Zurück zum Top -Schluss, jetzt kennen Sie einige der Probleme und ihre Lösungen, auf die Sie beim Erstellen einer ASP.NET -Produktionsanwendung begegnen könnten. Der nächste Schritt ist, sich Ihren eigenen Code genauer anzusehen und zu versuchen, einige der Probleme zu vermeiden, die ich hier beschrieben habe. ASP.NET hat möglicherweise die Eintrittsbarriere für Webentwickler gesenkt, Ihre Anwendungen haben jedoch allen Grund, flexibel, stabil und effizient zu sein. Bitte überlegen Sie dies sorgfältig, um Anfängerfehler zu vermeiden.
Abbildung 8 enthält eine kurze Checkliste, mit der Sie die in diesem Artikel beschriebenen Fallstricke vermeiden können. Sie können eine ähnliche Checkliste für Sicherheitsdefekte erstellen. Zum Beispiel:
• Haben Sie Konfigurationsabschnitte, die sensible Daten enthalten, verschlüsselt?
• Überprüfen und validieren Sie die in Datenbankoperationen verwendeten Eingaben und verwenden Sie HTML -codierte Eingabe als Ausgabe?
• Enthält Ihr virtuelles Verzeichnis Dateien mit ungeschützten Erweiterungen?
Diese Fragen sind wichtig, wenn Sie die Integrität Ihrer Website, die Server, die sie hosten, und die Backend -Ressourcen, auf die sie angewiesen sind, schätzen.
Jeff Prosise ist ein Redakteur des MSDN Magazine und Autor mehrerer Bücher, darunter das Programmieren von Microsoft .NET (Microsoft Press, 2002). Er ist auch Mitbegründer von Wintellect, einem Software-Beratungs- und Bildungsunternehmen.
Aus der Juli 2006 Ausgabe des MSDN Magazine.