Jaki jest właściwy sposób na wysłanie odpowiedzi HTTP 404 z ASP.NET akcja MVC?

Jeśli podano trasę:

{FeedName} / {ItemPermalink}

Ex: / Blog / Hello-World

Jeśli element nie istnieje, chcę zwrócić 404. Jaki jest właściwy sposób, aby to zrobić w ASP.NET MVC?

Author: Daniel Schaffer, 2009-02-01

6 answers

Strzelanie z biodra (cowboy coding ; -)), proponuję coś takiego:

Kontroler:

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 :)

Stosując to podejście, spełniasz standardy ramowe. Tam już jest HttpUnauthorizedResult, więc to po prostu rozszerzyłoby framework w oczach innego dewelopera utrzymującego Twój kod później (wiesz, psychol, który wie, gdzie mieszkasz).

Możesz użyć reflektora, aby wziąć zajrzyj do assembly, aby zobaczyć, jak HttpUnauthorizedResult jest osiągany, ponieważ Nie wiem, czy to podejście niczego nie pomija(wydaje się zbyt proste prawie).


Użyłem reflektora, aby spojrzeć na HttpUnauthorizedResult właśnie teraz. Wygląda na to, że ustawiają Kod statusowy odpowiedzi na 0x191 (401). Chociaż działa to dla 401, używając 404 jako nowej wartości, wydaje mi się, że otrzymuję tylko pustą stronę w Firefoksie. Internet Explorer pokazuje jednak domyślną 404 (Nie ASP.NET wersja). Używając paska narzędzi webdeveloper sprawdzałem nagłówki w FF, które pokazują odpowiedź 404 Not Found. To może być po prostu coś, co źle skonfigurowałem w FF.


To powiedziawszy, myślę, że podejście Jeffa jest dobrym przykładem pocałunku. Jeśli naprawdę nie potrzebujesz szczegółowości w tej próbce, jego metoda działa dobrze, jak również.
 68
Author: Erik van Brakel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-01 01:09:03

Robimy to tak; ten kod znajduje się w BaseController

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

Nazywa się tak

public ActionResult ShowUserDetails(int? id)
{        
    // make sure we have a valid ID
    if (!id.HasValue) return PageNotFound();
 46
Author: Jeff Atwood,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-01 09:18:48
throw new HttpException(404, "Are you sure you're in the right place?");
 19
Author: yfeldblum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-01 00:04:27

HttpNotFoundResult to świetny pierwszy krok do tego, czego używam. Zwracanie HttpNotFoundResult jest dobre. Więc pytanie brzmi, co dalej?

Utworzyłem filtr akcji o nazwie HandleNotFoundAttribute, który następnie wyświetla stronę błędu 404. Ponieważ zwraca widok, można utworzyć specjalny widok 404 dla każdego kontrolera, lub pozwolić używać domyślnego współdzielonego widoku 404. Zostanie ona wywołana nawet wtedy, gdy kontroler nie ma podanej akcji, ponieważ Framework wyrzuca HttpException o kodzie statusowym 404.

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
Author: Brian Vallelunga,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-22 17:56:10

Zauważ, że począwszy od MVC3, możesz po prostu użyć HttpStatusCodeResult.

 7
Author: enashnash,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-04 10:08:52

Użycie ActionFilter jest trudne do utrzymania , ponieważ ilekroć wyrzucimy błąd, filtr musi być ustawiony w atrybucie. A jeśli zapomnimy go ustawić? Jednym ze sposobów jest wyprowadzenie OnException na kontrolerze bazowym. Musisz zdefiniować BaseController pochodzące z Controller i wszystkie kontrolery muszą pochodzić z BaseController. Najlepszym rozwiązaniem jest posiadanie kontrolera bazowego.

Uwaga jeśli używamy Exception kod statusu odpowiedzi to 500, więc musimy zmienić go na 404 dla Not Found I 401 dla Nieautoryzowano. Tak jak wspomniałem powyżej, użyj OnException nadpisuje BaseController, Aby uniknąć używania atrybutu filter.

Nowy MVC 3 również bardziej kłopotliwy, zwracając pusty widok do przeglądarki. Najlepszym rozwiązaniem po kilku badaniach jest moja odpowiedź tutaj Jak zwrócić widok dla HttpNotFound () w ASP.Net MVC 3?

Dla większej wygody wklejam go tutaj:


Po kilku badaniach. Obejście dla MVC 3 oto wyprowadzenie wszystkich HttpNotFoundResult, HttpUnauthorizedResult, HttpStatusCodeResult klasy i zaimplementować nową metodę (nadrzędną) HttpNotFound() W BaseController.

Najlepiej jest używać kontrolera bazowego, aby mieć "kontrolę" nad wszystkimi kontrolerami pochodnymi.

Tworzę nową klasę HttpStatusCodeResult, nie po to, aby wywodzić się z ActionResult, ale z ViewResult, aby renderować widok lub dowolne View, które chcesz, podając właściwość ViewName. Podążam za oryginalnym HttpStatusCodeResult, aby ustawić HttpContext.Response.StatusCode i HttpContext.Response.StatusDescription, ale wtedy {[23] } wyrenderuje odpowiedni Widok, ponieważ ponownie wywodzę się z ViewResult. Dość proste, prawda? Mam nadzieję, że zostanie to zaimplementowane w rdzeniu MVC.

Zobacz mój BaseController poniżej:

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);
            }
        }
    }
}

Aby użyć w swojej akcji tak:

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

I w _Layout.cshtml (jak strona wzorcowa)

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

Dodatkowo możesz użyć widoku niestandardowego jak Error.shtml lub utworzyć nowy NotFound.cshtml Jak skomentowałem w kodzie i możesz zdefiniować model widoku dla opisu stanu i innych wyjaśnień.

 5
Author: CallMeLaNN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:34