wake-up-neo.com

Wie kann eine HTTP 404-Antwort von einer ASP.NET MVC-Aktion ordnungsgemäß gesendet werden?

Wenn die Route angegeben ist:

{FeedName}/{ItemPermalink}

zB:/Blog/Hallo-Welt

Wenn das Element nicht vorhanden ist, möchte ich einen 404 zurückgeben. Wie wird dies in ASP.NET MVC richtig ausgeführt?

90
Daniel Schaffer

Wenn ich aus der Hüfte fotografiere (Cowboy-Codierung ;-)), würde ich Folgendes vorschlagen:

Controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return new HttpNotFoundResult("This doesn't exist");
    }
}

HttpNotFoundResult:

using System;
using System.Net;
using System.Web;
using System.Web.Mvc;

namespace YourNamespaceHere
{
    /// <summary>An implementation of <see cref="ActionResult" /> that throws an <see cref="HttpException" />.</summary>
    public class HttpNotFoundResult : ActionResult
    {
        /// <summary>Initializes a new instance of <see cref="HttpNotFoundResult" /> with the specified <paramref name="message"/>.</summary>
        /// <param name="message"></param>
        public HttpNotFoundResult(String message)
        {
            this.Message = message;
        }

        /// <summary>Initializes a new instance of <see cref="HttpNotFoundResult" /> with an empty message.</summary>
        public HttpNotFoundResult()
            : this(String.Empty) { }

        /// <summary>Gets or sets the message that will be passed to the thrown <see cref="HttpException" />.</summary>
        public String Message { get; set; }

        /// <summary>Overrides the base <see cref="ActionResult.ExecuteResult" /> functionality to throw an <see cref="HttpException" />.</summary>
        public override void ExecuteResult(ControllerContext context)
        {
            throw new HttpException((Int32)HttpStatusCode.NotFound, this.Message);
        }
    }
}
// By Erik van Brakel, with edits from Daniel Schaffer :)

Mit diesem Ansatz halten Sie sich an die Rahmenstandards. Es gibt dort bereits ein HttpUnauthorizedResult, so dass dies einfach das Framework für einen anderen Entwickler erweitern würde, der später Ihren Code pflegt (Sie wissen, der Psycho, der weiß, wo Sie leben).

Sie könnten Reflektor verwenden, um einen Blick in die Assembly zu werfen, um zu sehen, wie das HttpUnauthorizedResult erzielt wird, da ich nicht weiß, ob dieser Ansatz etwas verfehlt (es scheint fast zu einfach).


Ich habe gerade Reflektor verwendet, um einen Blick auf das HttpUnauthorizedResult zu werfen. Sie setzen anscheinend den StatusCode für die Antwort auf 0x191 (401). Obwohl dies für 401 funktioniert und 404 als neuen Wert verwendet wird, erhalte ich in Firefox scheinbar nur eine leere Seite. Internet Explorer zeigt jedoch standardmäßig 404 an (nicht die ASP.NET-Version). Mit der Webdeveloper-Symbolleiste habe ich die Header in FF überprüft, die eine 404 Not Found-Antwort enthalten. Könnte einfach etwas sein, was ich in FF falsch konfiguriert habe.


Angesichts dessen denke ich, dass Jeffs Ansatz ein gutes Beispiel für KISS ist. Wenn Sie die Ausführlichkeit in diesem Beispiel nicht wirklich benötigen, funktioniert seine Methode ebenfalls einwandfrei.

68
Erik van Brakel

Wir machen es so; Dieser Code befindet sich in BaseController

/// <summary>
/// returns our standard page not found view
/// </summary>
protected ViewResult PageNotFound()
{
    Response.StatusCode = 404;
    return View("PageNotFound");
}

wie so genannt

public ActionResult ShowUserDetails(int? id)
{        
    // make sure we have a valid ID
    if (!id.HasValue) return PageNotFound();
46
Jeff Atwood
throw new HttpException(404, "Are you sure you're in the right place?");
19
yfeldblum

Das HttpNotFoundResult ist ein großartiger erster Schritt zu dem, was ich verwende. Die Rückgabe eines HttpNotFoundResult ist gut. Dann ist die Frage, was kommt als nächstes?

Ich habe einen Aktionsfilter mit dem Namen HandleNotFoundAttribute erstellt, der dann eine 404-Fehlerseite anzeigt. Da eine Ansicht zurückgegeben wird, können Sie eine spezielle 404-Ansicht pro Controller erstellen oder eine freigegebene 404-Standardansicht verwenden. Dies wird auch dann aufgerufen, wenn auf einem Controller die angegebene Aktion nicht vorhanden ist, da das Framework eine HttpException mit dem Statuscode 404 auslöst.

public class HandleNotFoundAttribute : ActionFilterAttribute, IExceptionFilter
{
    public void OnException(ExceptionContext filterContext)
    {
        var httpException = filterContext.Exception.GetBaseException() as HttpException;
        if (httpException != null && httpException.GetHttpCode() == (int)HttpStatusCode.NotFound)
        {
            filterContext.HttpContext.Response.TrySkipIisCustomErrors = true; // Prevents IIS from intercepting the error and displaying its own content.
            filterContext.ExceptionHandled = true;
            filterContext.HttpContext.Response.StatusCode = (int) HttpStatusCode.NotFound;
            filterContext.Result = new ViewResult
                                        {
                                            ViewName = "404",
                                            ViewData = filterContext.Controller.ViewData,
                                            TempData = filterContext.Controller.TempData
                                        };
        }
    }
}
7

Beachten Sie, dass Sie ab MVC3 nur noch HttpStatusCodeResult verwenden können.

7
enashnash

Die Verwendung von ActionFilter ist schwierig zu warten , da der Filter immer dann, wenn wir einen Fehler auslösen müssen im Attribut gesetzt werden. Was ist, wenn wir vergessen, es einzustellen? Eine Möglichkeit besteht darin, OnException auf dem Basiscontroller abzuleiten. Sie müssen ein BaseController definieren, das von Controller abgeleitet ist, und alle Ihre Controller müssen von BaseController abgeleitet sein. Es wird empfohlen, einen Basiscontroller zu haben.

Hinweis: Wenn Sie Exception verwenden, lautet der Antwortstatuscode 500, daher müssen Sie ihn für Nicht gefunden in 404 und für Nicht autorisiert in 401 ändern. Verwenden Sie, wie oben erwähnt, die OnException - Überschreibungen für BaseController, um die Verwendung von Filterattributen zu vermeiden.

Die neue MVC 3 macht auch Probleme, indem eine leere Ansicht an den Browser zurückgegeben wird. Die beste Lösung nach einigen Recherchen basiert auf meiner Antwort hier Wie kann ich eine Ansicht für HttpNotFound () in ASP.Net MVC 3 zurückgeben?

Um es einfacher zu machen, füge ich es hier ein:


Nach einigem Studium. Die Problemumgehung für MVC 3 besteht darin, alle Klassen HttpNotFoundResult, HttpUnauthorizedResult, HttpStatusCodeResult und abzuleiten implementiere eine neue (überschreibe sie) HttpNotFound () Methode in BaseController.

Es wird empfohlen, den Basis-Controller zu verwenden, damit Sie die Kontrolle über alle abgeleiteten Controller haben.

Ich erstelle eine neue HttpStatusCodeResult - Klasse, die nicht von ActionResult, sondern von ViewResult abgeleitet wird, um die Ansicht oder eine beliebige View zu rendern, indem Sie die ViewName Eigentum. Ich folge dem Original HttpStatusCodeResult, um HttpContext.Response.StatusCode Und HttpContext.Response.StatusDescription Festzulegen, aber dann rendere base.ExecuteResult(context) die passende Ansicht, da ich wieder von ViewResult. Ist es einfach genug? Ich hoffe, dies wird im MVC-Kern implementiert.

Siehe mein BaseController unten:

using System.Web;
using System.Web.Mvc;

namespace YourNamespace.Controllers
{
    public class BaseController : Controller
    {
        public BaseController()
        {
            ViewBag.MetaDescription = Settings.metaDescription;
            ViewBag.MetaKeywords = Settings.metaKeywords;
        }

        protected new HttpNotFoundResult HttpNotFound(string statusDescription = null)
        {
            return new HttpNotFoundResult(statusDescription);
        }

        protected HttpUnauthorizedResult HttpUnauthorized(string statusDescription = null)
        {
            return new HttpUnauthorizedResult(statusDescription);
        }

        protected class HttpNotFoundResult : HttpStatusCodeResult
        {
            public HttpNotFoundResult() : this(null) { }

            public HttpNotFoundResult(string statusDescription) : base(404, statusDescription) { }

        }

        protected class HttpUnauthorizedResult : HttpStatusCodeResult
        {
            public HttpUnauthorizedResult(string statusDescription) : base(401, statusDescription) { }
        }

        protected class HttpStatusCodeResult : ViewResult
        {
            public int StatusCode { get; private set; }
            public string StatusDescription { get; private set; }

            public HttpStatusCodeResult(int statusCode) : this(statusCode, null) { }

            public HttpStatusCodeResult(int statusCode, string statusDescription)
            {
                this.StatusCode = statusCode;
                this.StatusDescription = statusDescription;
            }

            public override void ExecuteResult(ControllerContext context)
            {
                if (context == null)
                {
                    throw new ArgumentNullException("context");
                }

                context.HttpContext.Response.StatusCode = this.StatusCode;
                if (this.StatusDescription != null)
                {
                    context.HttpContext.Response.StatusDescription = this.StatusDescription;
                }
                // 1. Uncomment this to use the existing Error.ascx / Error.cshtml to view as an error or
                // 2. Uncomment this and change to any custom view and set the name here or simply
                // 3. (Recommended) Let it commented and the ViewName will be the current controller view action and on your view (or layout view even better) show the @ViewBag.Message to produce an inline message that tell the Not Found or Unauthorized
                //this.ViewName = "Error";
                this.ViewBag.Message = context.HttpContext.Response.StatusDescription;
                base.ExecuteResult(context);
            }
        }
    }
}

So verwenden Sie in Ihrer Aktion:

public ActionResult Index()
{
    // Some processing
    if (...)
        return HttpNotFound();
    // Other processing
}

Und in _Layout.cshtml (wie Masterseite)

<div class="content">
    @if (ViewBag.Message != null)
    {
        <div class="inlineMsg"><p>@ViewBag.Message</p></div>
    }
    @RenderBody()
</div>

Zusätzlich können Sie eine benutzerdefinierte Ansicht wie Error.shtml Verwenden oder eine neue NotFound.cshtml Erstellen, wie ich im Code kommentiert habe, und Sie können ein Ansichtsmodell für die Statusbeschreibung und andere Erklärungen definieren.

5
CallMeLaNN