Im Artikel Blogengine.NET und 404 Fehler habe ich bereits auf das seltsame Verhalten von BlogEngine.NET im Umgang mit 404 Fehlern hingewiesen. Ich dachte ich hätte eine Lösung gefunden, bis mich ein aufmerksamer Leser auf ein neues Problem in meiner Lösung hingewiesen hat. Der Kommentar kann im oben genannten Artikel nachgelesen werden.

Ich konnte zunächst nicht glauben, dass sich ein Robot, wie etwa der Google-Bot, tatsächlich so verhält und den Statuscode der error404.aspx ignoriert. Also habe ich im Google Webmaster Forum nachgefragt. Tatsächlich wurde mir dort dieses Verhalten, zumindest annähernd, bestätigt.

Nur so viel vorne weg: Wie man in folgendem Screenshot sehen kann, ist eine direkte Ausgabe der 404-Fehlerseite mit Statuscode 404 ohne 302 Weiterleitung durchaus möglich. So wird der Statuscode korrekt an den jeweiligen User Agent übertragen und ein Benutzer vor einem Browser bekommt eine benutzerdefinierte Fehlerseite präsentiert.

direkte Ausgabe der 404-Fehlerseite

Als erstes habe ich mich von der ASP.NET Fehlerbehandlung in der web.config verabschiedet, in dem ich customErrors Mode=”Off” gesetzt habe.
BlogEngine.NET bringt offiziell seit der Version 1.6.0.0 eine eigene Fehlerbehandlung in der Global.asax mit. Also musste ich mich nur in diese Fehlerbehandlung einklinken.
Standardmäßig wird in BlogEngine.NET in der Methode Application_Error  ein 404 Fehler ignoriert und die Fehlerbehandlung an dieser Stelle beendet.
Die Fehlerbehandlung muss somit um die Fähigkeit zum behandeln eines 404 Fehlers erweitert werden.

Da die error404.aspx.cs den URL-Parameter aspxerrorpath sowie den UrlReferrer der aktuellen Anforderung verwendet, müssen hier Vorbereitungen getroffen werden, dass diese Informationen auch zur Verfügung stehen. Da keine clientseite Weiterleitung stattfindet, ist eine Übergabe der Informationen via URL nicht möglich. Auch ist der UrlReferrer der verweisenden Seite nicht vorhanden, da ja eine serverseitige Umleitung vorgenommen wird.
Als einfachste Lösung habe ich die Eigenschaft HttpContext.Items des aktuellen HttpContext in Betracht gezogen. Es wird sowohl der Pfad der nicht gefundenen Ressource sowie der verweisende UrlReferrer in das Dictionary des aktuellen HttpContext geschrieben.
Anschließend wird der anstehende Fehler gelöscht und serverseitig auf die error404.aspx umgeleitet. In folgendem Listing ein Auszug der relevanten Passage aus der Methode Application_Error der Global.asax.

var currentPage = context.CurrentHandler as System.Web.UI.Page;

if ((ex as HttpException).GetHttpCode() == 404)
{
    context.Response.StatusCode = 404;
    context.Items["aspxerrorpath"] = context.Request.RawUrl;
    context.Items["urlReferrer"] = context.Request.UrlReferrer;
    this.Server.ClearError();

    if (currentPage != null && currentPage.IsCallback)
    {
        return;
    }

    context.Server.Transfer("~/error404.aspx");

    return;
}

Jetzt muss noch die error404.aspx.cs angepasst werden, damit sie die benötigten Informationen aus dem Dictionary Items des aktuellen HttpContext verwendet.
Als erstes habe ich zwei klassenweite Felder, errorPath und urlReferrer, erzeugt die beim Laden der Seite mit Werten belegt werden.
Das Feld errorPath wird anstatt des Url-Parameter aspxerrorpath verwendet und urlReferrer ersetzt den Zugriff auf den bisher verwendetet UrlReferrer.
Damit die vorgenommenen Änderungen leichter ersichtlich sind, habe ich in folgendem Listing den bisherigen Code lediglich auskommentiert.

private string errorPath = null;
private Uri urlReferrer = null;

protected void Page_Load(object sender, EventArgs e)
{
    if (this.Request.QueryString["aspxerrorpath"] == null)
    {
        this.errorPath = (string)HttpContext.Current.Items["aspxerrorpath"];
        this.urlReferrer = (Uri)HttpContext.Current.Items["urlReferrer"];
    }
    else
    {
        this.errorPath = (string)this.Request.QueryString["aspxerrorpath"];
        this.urlReferrer = this.Request.UrlReferrer;
    }

    //if (Request.QueryString["aspxerrorpath"] != null && Request.QueryString["aspxerrorpath"].Contains("/post/"))
    if (!string.IsNullOrEmpty(errorPath) && errorPath.Contains("/post/"))
    {
        DirectHitSearch();
        divDirectHit.Visible = true;
    }
    //else if (Request.UrlReferrer == null)
    else if (this.urlReferrer != null)
    {
        divDirectHit.Visible = true;
    }
    //else if (Request.UrlReferrer.Host == Request.Url.Host)
    else if (this.urlReferrer != null && this.urlReferrer.Host == this.Request.Url.Host)
    {
        divInternalReferrer.Visible = true;
    }
    else if (GetSearchKey() != string.Empty)
    {
        SearchTerm = GetSearchTerm(GetSearchKey());
        BindSearchResult();
        divSearchEngine.Visible = true;
    }
    // else if (Request.UrlReferrer != null)
    else if (this.urlReferrer != null)
    {
        divExternalReferrer.Visible = true;
    }

    Page.Title += Server.HtmlEncode(" - " + "Page not found");
}

Neben der Methode Page_Load muss auch die private Methode GetSearchKey angepasst werden, da sie ursprünglich den UrlReferrer verwendet.

private string GetSearchKey()
{
    if (this.urlReferrer == null)
    {
        return string.Empty;
    }

    //string referrer = Request.UrlReferrer.Host.ToLowerInvariant();
    var referrer = this.urlReferrer.Host.ToLowerInvariant();

    if (referrer.Contains("google.") && referrer.Contains("q="))
        return "q=";

    if (referrer.Contains("yahoo.") && referrer.Contains("p="))
        return "p=";

    if (referrer.Contains("q="))
        return "q=";

    return string.Empty;
}

Somit sind die Anpassungen in BlogEngine.NET abgeschlossen und einer besserer Behandlung von 404-Fehlern, vor allem ohne 302-Weiterleitung, steht nichts mehr im Weg.

Fazit:

Auf den ersten Blick, vor allem für den Benutzer, mag die 404 Fehlersteuerung in der web.config ja ganz angenehm sein. Will man aber eine, in allen Lagen und ohne 302, funktionierende Fehlerbehandlung, muss man selbst Hand anlegen.

Technorati-Tags:  |  | 
Wenn ihnen der Artikel gefallen hat oder er für sie hilfreich war, bitte "kicken" sie ihn.
kick it on dotnet-kicks.de