Beim Veröffentlichen einer größeren ASP.NET Webseite konnte ich vor kurzem direkt die (normalerweise nicht gerade auffälligen - aber falls doch, dann sehr deutlich spürbaren) Unterschiede zwischen dem temporären IIS des Visual Studios (2008) zum echten IIS (7) feststellen. Die Webseite lief auf dem Entwicklungsrechner ohne Probleme und hatte keine sichtbaren Fehler.
1 | Bilder speichern oder laden
Ein Teil der Webseite nutzt die Möglichkeit sog. Captchas zum Generieren von Eingabetexten zu nutzen. Diese Captchas dienen dazu (Spam-) Robots vom erfolgreichen Absenden von Formularen (z.B. Benutzerregistrierung) abzuhalten. Mein Programm generiert dazu einen Code, welcher auf der Seite gespeichert wird und durch das Captcha-Modul lesbar ist. Dieses sorgt für die Abfrage ob der Code richtig eingegeben wurde und dafür, dass ein Bild aus dem Code generiert wird.
Für das Bild nutzte ich die Möglichkeiten von GDI+ (dank .NET) und bastelte mir einen Outputstream, welchen ich als Content-Type image/png vermerkte. Als ImageFormat nutzte ich daher auch Png. Der IIS gab mir in diesem Fall die Fehlermeldung "A generic error occured in GDI+" mit einem merkwürdigen StackTrace zurück.
Ich stellte einige Nachforschungen an, nach denen ich als Probelauf versucht habe das ImageFormat auf Jpeg umzustellen. Dazu habe ich nur in zwei Codezeilen Änderungen vorgenommen:
- Response.ContentType = "image/jpeg";
- System.Drawing.Imaging.ImageFormat.Jpeg
Anschließend ging das Captcha ohne Probleme, weshalb ich die Ursache des Fehlers im Png Format fand. Das Problem liegt hier darin, dass GDI+ für Png einen durchsuchbaren Stream benötigt (Seek). Wieso sich dieser Fehler nicht auf dem temporären IIS zeigt ist mir nicht bekannt.
Mit Hilfe eines MemoryStreams kann man jeden Stream in einen durchsuchbaren Stream verwandeln. Der folgende Code zeigt ein Beispiel.
Response.Clear();//Ausgabe entfernen
Response.ContentType = "image/png";//neu setzen
//Memorystream starten
using(MemoryStream stream = new MemoryStream())
{//in Memorystream schreiben und dann
img.Save(stream, ImageFormat.Png);
stream.WriteTo(Response.OutputStream);
}//von Memorystream in die Ausgabe schreiben
2 | Pfadangaben und Schreiben von Daten
Ein weiterer Teil der Webseite nutzte aus, dass man Dank der großartigen File.IO Bibliothek sehr viele Dateioperationen ausführen kann. So holte ich mir über Server.Mappath("...") einen Pfad und schrieb mit den entsprechenden Streams und Funktionen Daten in entsprechende Verzeichnisse. Dies funktionierte auf meinem Entwicklungsrechner ausgezeichnet.
Beim realen IIS hat man (in diesem Fall nicht leider - da dies zum Sicherheitskonzept gehört und daher unbedingt enthalten sein sollte) mit Rechtevergeben zu kämpfen. Der entsprechende IIS Benutzer für ASP.NET (normalerweise ein Name der sowas wie IUSR enthält) benötigt das Schreiben-Recht in den entsprechenden Verzeichnissen. Doch selbst nachdem ich diesem Benutzer die entsprechenden Rechte gab, konnte ich meine Dateioperationen nicht wie gewohnt ausführen.
Hier kommt einem wieder mal die doch sehr eigene Struktur des temporären IIS in die Quere. Dieser agiert zwar teilweise wie in Server, ist aber in echt keiner und interpretiert somit auch "normale" Pfadangaben (wie sie auf dem NTFS Dateisystem in Windows gängig sind) wie gewohnt. Der reale IIS dagegen benötigt virtuelle / URL oder UNC Pfade für den korrekten Ablauf.
Wir können dies jedoch sehr einfach ändern, indem wir die URL-Konventionen "~/..." verwenden. Dabei steht die Tilde für den Root, also das Wurzelverzeichnis der aktuellen Applikation. Somit wird
//Ursprünglicher Wert
Server.Mappath("App_Data");
//Neuer Wert
Server.Mappath("~/App_Data/");
Diese URL Konvention funktioniert nun sowohl auf dem temporären als auch auf dem realen IIS ausgezeichnet.
3 | E-Mails nach außerhalb verschicken
Dieses Problem hängt weniger am IIS als an den Mailservereinstellungen und der nicht gerade transparenten Vorgehensweise von .NET um die Anmeldedaten beim SMTP Server zu ändern. Verschicke ich E-Mails - egal von welchem Rechner - an eine Adresse innerhalb der Domäne des Mailservers, so läuft alles einwandfrei. Beim Versuch Mails auch an externe Mail Adressen zu verschicken erhalte ich allerdings eine Fehlermeldung, welche mir sagt, dass ich zu wenig Rechte besitze um dies zu tun.
Eine Lösung ist sehr schnell gefunden - die SmtpClient-Klasse enthält eine Eigenschaft Credentials, mit welcher man offensichtlich in der Lage sein sollte diese Daten zu setzen. Allerdings enthält weder diese Klasse solche Eigenschaften, noch kann man eine neue Instanz dieser Klasse direkt erstellen...
Folgender Code schafft Abhilfe:
SmtpClient smtp = new SmtpClient();
smtp.Host = "smtp.server.com";
smtp.Credentials = new System.Net.NetworkCredential("User", "Pass");
smtp.Send("from@address.net", "to@address.net", "Subject", "Body");