Javascript-Fehler in einer ASP .NET Anwendung bei falschem User-Agent im Zuammenhang mit einer Benutzererkennung von Nocturne zum Thema Code To Joy - Di, 06.01.2009

Vor gut einem Jahr hatte ich ein Problem, welches ich stilvoll "Postbacks ohne Wiederkehr und CSS-Alzheimer" nannte. Damals konnte ich in dem von mir bevorzugt verwendeten Browser Firefox einige JavaScripts nicht ausführen und gleichsam wurden einige CSS-Angaben nicht angezeigt. Damals ging ich davon aus, dass die Ursache der Browser selbst war und fand die Lösung in einem falsch eingestelltem User-Agent.
In der Tat habe ich die wahre Ursache gerade eben ehrer durch Zufall entdeckt. Halten wir nochnal kurz die Symptome fest:

  • einige CSS-Angaben werden nicht angezeigt
  • Postbacks werden nicht ausgelöst
  • einige Javascripts werden nicht ausgeführt
  • selbst geschriebene JavaScripte funktionierten

Schuld daran ist, wie damals auch bereits kurz in Erwägung gezogen, nicht der Browser sondern der Webserver, konkret der IIS-Webserver.

Die Überprüfung, ob ein Browser JavaScript unterstützt oder nicht, erfolgt anhand der Request.Browser.JavaScript-Eigenschaft. Diese Eigenschaft wird auf Basis der vom Browser an den Webserver gesendeten User-Agent-Zeichenfolge festgelegt. [...]

Und genau dort liegt der Hund begraben: der Firefox hat einen falschen User-Agent gesendet. Da der IIS den Browser nicht "erkennen" kann, geht er prinzipiell davon aus, dass JavaScript nicht unterstützt wird. Mittels

Request.Browser.JavaScript

kann man dies in einer ASP .NET Anwendung einfach abfragen. Im Falle des falschen User-Agents wird diese Eigenschaft mit False belegt. Geht der IIS davon aus, dass der Client - also der Browser - kein JavaScript unterstützt, so generiert der IIS auch einfach keines.
Der ASP .NET Postbackmechanismus kann jedoch auf 2 Arten ausgeführt werden: standardmäßig macht sich ASP .NET die Eigenschaft zu nutze, dass eine vom IIS generierte Webseite im Grunde ein einziges HTML-Formular ist. Das heißt, dass direkt nach dem Body-Tag der Form-Tag gerendert wird, der alle weiteren Steuerelemente und HTML-Tags umschließt. Beim Klick auf einen Button, wird dann das Formular abgeschickt, was Microsoft als "Postback" bezeichnet, da im action-Attribut des Form-Tags die selbe Seite steht, die die Daten sendet. Sender und Empfänger sind demnach das selbe Formular, was den Begriff verdeutlicht.
Statt den Sendemechanismus des Clientbrowsers zu verwenden, gibt es noch eine andere Möglichkeit einen Postback auszulösen: Der IIS kann ein JavaScript generieren, welches den Postback initiiert und das Formular an den Server sendet. Dies kann man einfach testen, indem man die Eigenschaft UseSubmitBehavior eines ASP .NET Button Steuerelements auf False festlegt. Beim Laden der Seite generiert der IIS dann folgendes Javascript in der Webseite:

JScript
1 <script type="text/javascript"> 2 //<![CDATA[ 3 var theForm = document.forms['form1']; 4 if (!theForm) { 5 theForm = document.form1; 6 } 7 function __doPostBack(eventTarget, eventArgument) { 8 if (!theForm.onsubmit || (theForm.onsubmit() != false)) { 9 theForm.__EVENTTARGET.value = eventTarget; 10 theForm.__EVENTARGUMENT.value = eventArgument; 11 theForm.submit(); 12 } 13 } 14 //]]> 15 </script>

Sinn macht das vor allem wenn man Steuerelemente entwickelt die vor dem Absenden des Formulars noch clientseitiges Script ausführen sollen, etwa die Validierung einer Eingabe. Unabdingbar ist die Verwendung des ASP .NET Postbackmechanismus' jedoch dann, wenn man auf seiner Webseite ein Link-Button Steuerelement verwendet. Dieses wird im Browser nämlich als einfacher Textlink gerendert. Um ein HTML-Formular absenden zu können, bedarf es jedoch eines Submit-Buttons. Da in diesem Fall jedoch keiner vorhanden ist, wird clientseitig das onClick-Ereignis des Textlinks abgefangen und das JavaScript aufgerufen, welches den Postback schließlich initiiert.

Weiterhin notwendig ist der ASP .NET Postbackmechanismus, wenn in Steuerelementen wie Textbox, Checkbox oder DropdownBox die Eigenschaft AutoPostBack auf True festgelegt wird. Dann wird sobald die Textbox den Fokus verliert, die Checkbox ihren Status (gesetzt/nicht gesetzt) ändert oder nachdem in der DropdownBox ein anderer Wert ausgewählt wurde, ein Postback ausgelöst.

Um zum ursprünglichen Problem zurück zu kommen: all das funktioniert nicht, wenn der IIS ein JavaScript rendern muss, um die Funktionalität der Webanwendung bereit stellen zu können, der User-Agent jedoch (fälschlicherweise) meldet, dass er kein JavaScript verarbeiten kann. Dann funktionieren alle Postbacks, die vom ASP .NET anstatt vom Browser gesteuert werden, nicht - kurz gesagt: nur normale Submit-Buttons funktionieren noch. Genau das erklärt das Verhalten, welches ich damals hatte, denn da funktionierten ja auch nur einige Postbacks, andere wiederum nicht. Die fehlerhafte CSS-Ausgabe kann ich mir jedoch nicht 100%ig erklären. Vermutlich ist es ein Seiteneffekt, da durch die fehlenden JavaScripts Fehler in der Konsole generiert werden. Möglicherweise verhindert das das Laden einiger Styles.

Da die Ursache nun vollends geklärt ist, möchte man meinen, dass man gut daran ist, den User-Agent einfach nicht zu manipulieren und schon hat man keine Probleme. Dabei sollte ich jedoch erwähnen, dass ich damals die Einstellung

general.useragent.override

zuvor niemals manipuliert hatte. Ich hatte jedoch kurzzeitig ein Plugin im Firefox installiert, welches es mir ermöglichte schnell und komfortabel den User-Agent des Browsers zu verändern. Nach der Deinstallation des Plugins, muss dieses jedoch eine fehlerhafte User-Agent Angabe in der Config des Firefox' hinterlassen haben.
Doch auch damit ist die Sache noch nicht ganz vom Tisch: meine Idee, weshalb ich den User-Agent überhaupt manipulieren möchte, war dass ich bei meinen Webseiten diverse Auswertungen mache und ich dabei gern unterscheiden möchte ob ich oder jemand aus meiner Firma eine Seite des Projekts aufruft, oder ob es ein "normaler" User ist. So könnte man die Zugriffszahlen auf bestimmte Dokumente nur von externen Benutzern zählen, statt auch die eigenen Aufrufe stets mit zu zählen. Auch wäre es in einem Webshop sinnvoll einen Suchbegriff nur dann zu speichern, wenn ein Kunde diesen eingegeben hat und nicht wenn es ein Mitarbeiter war. So wäre gewährleistet dass die eingegebenen Suchbegriffe direkt das Kundeninteresse widerspiegeln, was eine Optimierung des Warenangebots ermöglicht. Es gibt also durchaus genügend Gründe weshalb man Benutzer beim Aufruf einer Webseite unterscheiden sollte. Da selbst die meisten Mittelständischen Unternehmen und Privatpersonen ohnehin dynamische IP-Adressen verwenden, ist es schwierig es daran fest zu machen. Möglich ist es aber, indem man sich bei einem dynamischen DNS-Dienst anmeldet, der bei einem ping auf diese dynamische Adresse dann die aktuelle IP-Adresse zurückliefert, die man dann lediglich gegen die des anfragenden Clients prüfen muss. Etwas umständlich aber durchaus machbar :)

Besser wäre es jedoch wenn man den User-Agent des Browsers zur Identifikation eines Benutzers nutzen könnte. Dann könnte man sich irgend einen x-beliebigen User-Agent ausdenken, einen den sonst keiner verwendet und das Verhalten der Webseite davon abhängig machen. Leider macht nun ASP .NET dem Vorhaben ein Strich durch die Rechnung, da der IIS eben bei fehlerhaften User-Agents unter Umständen nicht funktionierende Webseiten generiert. Bliebe noch die Möglichkeit einen User-Agent zu verwenden, der heutzutage von niemandem mehr verwendet wird, der jedoch schon JavaScript unterstützt. Alternativ kann man einen gültigen User-Agent auch nur leicht verändern. So habe ich z.B. festgestallt, dass man folgenden User-Agent-String

Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4

in der Art verändern kann, sodass dieser trotzdem noch vom IIS als gültig anerkannt wird:

Mozilla/1.0 (Win; U; 6.0; de;sd) Gecko Firefox/3.0.4

Lässt man jedoch das "o" bei Gecko weg, so wird dieser bereits schon nicht mehr als gültig erkannt und JavaScript steht in der Webanwendung somit schon wieder nicht zur Verfügung.

Hilfreiche 404 Fehlerseiten dank Google Widget