Apa cara yang tepat untuk mengirim respons HTTP 404 dari tindakan ASP.NET MVC?

92

Jika diberikan rutenya:

{FeedName} / {ItemPermalink}

mis .: / Blog / Hello-World

Jika item tidak ada, saya ingin mengembalikan 404. Apa cara yang benar untuk melakukan ini di ASP.NET MVC?

Daniel Schaffer
sumber
Terima kasih telah mengajukan pertanyaan ini btw. Ini terjadi dalam penambahan proyek standar saya: D
Erik van Brakel

Jawaban:

69

Memotret dari pinggul (pengkodean koboi ;-)), saya menyarankan sesuatu seperti ini:

Pengontrol:

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

Dengan menggunakan pendekatan ini, Anda mematuhi standar kerangka kerja. Sudah ada HttpUnauthorizedResult di sana, jadi ini hanya akan memperluas kerangka kerja di mata pengembang lain yang memelihara kode Anda nanti (Anda tahu, psiko yang tahu di mana Anda tinggal).

Anda dapat menggunakan reflektor untuk melihat ke dalam rakitan untuk melihat bagaimana HttpUnauthorizedResult dicapai, karena saya tidak tahu apakah pendekatan ini melewatkan sesuatu (tampaknya hampir terlalu sederhana).


Saya memang menggunakan reflektor untuk melihat HttpUnauthorizedResult sekarang. Sepertinya mereka menyetel StatusCode pada respons ke 0x191 (401). Meskipun ini berfungsi untuk 401, menggunakan 404 sebagai nilai baru saya tampaknya hanya mendapatkan halaman kosong di Firefox. Internet Explorer menunjukkan 404 default (bukan versi ASP.NET). Menggunakan toolbar pengembang web saya memeriksa header di FF, yang menunjukkan respons 404 Not Found. Bisa jadi hanya sesuatu yang saya salah konfigurasi di FF.


Ini dikatakan, saya pikir pendekatan Jeff adalah contoh bagus dari KISS. Jika Anda tidak terlalu membutuhkan verbositas dalam sampel ini, metodenya juga berfungsi dengan baik.

Erik van Brakel
sumber
Ya, saya perhatikan Enum juga. Seperti yang saya katakan, ini hanya contoh kasar, silakan perbaiki. Ini seharusnya menjadi basis pengetahuan ;-)
Erik van Brakel
Saya pikir saya sedikit berlebihan ... nikmati: D
Daniel Schaffer
FWIW, contoh Jeff juga mengharuskan Anda memiliki halaman 404 kustom.
Daniel Schaffer
2
Satu masalah dengan melempar HttpException alih-alih hanya menyetel HttpContext.Response.StatusCode = 404 adalah jika Anda menggunakan pengendali OnException Controller (seperti yang saya lakukan), itu akan menangkap HttpExceptions juga. Jadi saya pikir hanya mengatur StatusCode adalah pendekatan yang lebih baik.
Igor Brejc
4
HttpException atau HttpNotFoundResult di MVC3 berguna dalam banyak hal. Dalam kasus @Igor Brejc, cukup gunakan pernyataan if di OnException untuk menyaring kesalahan tidak ditemukan.
CallMeLaNN
46

Kami melakukannya seperti itu; kode ini ditemukan diBaseController

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

disebut seperti itu

public ActionResult ShowUserDetails(int? id)
{        
    // make sure we have a valid ID
    if (!id.HasValue) return PageNotFound();
Jeff Atwood
sumber
apakah tindakan ini kemudian dihubungkan ke rute default? Tidak bisa melihat bagaimana itu akan dieksekusi.
Christian Dalager
2
Bisa menjalankannya seperti ini: protected override void HandleUnknownAction (string actionName) {PageNotFound (). ExecuteResult (this.ControllerContext); }
Tristan Warner-Smith
Saya biasa melakukannya dengan cara itu, tetapi menemukan bahwa membagi hasil dan tampilan yang ditampilkan adalah pendekatan yang lebih baik. Simak jawaban saya di bawah ini.
Brian Vallelunga
19
throw new HttpException(404, "Are you sure you're in the right place?");
yfeldblum
sumber
Saya suka ini karena mengikuti halaman kesalahan kustom yang disiapkan web.config.
Mike Cole
7

HttpNotFoundResult adalah langkah pertama yang bagus untuk apa yang saya gunakan. Mengembalikan HttpNotFoundResult itu bagus. Lalu pertanyaannya adalah, selanjutnya apa?

Saya membuat filter tindakan bernama HandleNotFoundAttribute yang kemudian menampilkan halaman kesalahan 404. Karena mengembalikan tampilan, Anda dapat membuat tampilan 404 khusus per pengontrol, atau biarkan menggunakan tampilan 404 bersama default. Ini bahkan akan dipanggil ketika pengontrol tidak memiliki aksi yang ditentukan, karena kerangka kerja melempar HttpException dengan kode status 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
                                        };
        }
    }
}
Brian Vallelunga
sumber
7

Perhatikan bahwa pada MVC3, Anda hanya dapat menggunakan HttpStatusCodeResult.

enashnash
sumber
8
Atau, bahkan lebih mudah,HttpNotFoundResult
Matt Enright
6

Menggunakan ActionFilter adalah sulit untuk mempertahankan karena setiap kali kita melempar kesalahan kebutuhan filter yang harus ditetapkan dalam atribut. Bagaimana jika kita lupa mengaturnya? Salah satu caranya adalah dengan menurunkan OnExceptionbasis pengontrol. Anda perlu mendefinisikan BaseControllerturunan dari Controllerdan semua pengontrol Anda harus berasal dari BaseController. Ini adalah praktik terbaik untuk memiliki pengontrol dasar.

Catatan jika menggunakan Exceptionkode status respon adalah 500, maka kita perlu mengubahnya menjadi 404 untuk Not Found dan 401 untuk Unauthorized. Seperti yang saya sebutkan di atas, gunakan OnExceptionoverrides BaseControlleruntuk menghindari penggunaan atribut filter.

MVC 3 baru juga membuat lebih merepotkan dengan mengembalikan tampilan kosong ke browser. Solusi terbaik setelah beberapa penelitian didasarkan pada jawaban saya di sini Bagaimana mengembalikan tampilan untuk HttpNotFound () di ASP.Net MVC 3?

Agar lebih nyaman saya tempel di sini:


Setelah belajar. Solusi untuk MVC 3 di sini adalah untuk mendapatkan semua HttpNotFoundResult, HttpUnauthorizedResult, HttpStatusCodeResultkelas dan mengimplementasikan baru (utama itu) HttpNotFound() metode dalam BaseController.

Ini adalah praktik terbaik untuk menggunakan Pengontrol dasar sehingga Anda memiliki 'kendali' atas semua Pengontrol turunan.

Saya membuat HttpStatusCodeResultkelas baru , bukan untuk diturunkan dari ActionResulttetapi ViewResultuntuk membuat tampilan atau apa pun yang ViewAnda inginkan dengan menentukan ViewNameproperti. Saya mengikuti yang asli HttpStatusCodeResultuntuk mengatur HttpContext.Response.StatusCodedan HttpContext.Response.StatusDescriptiontetapi kemudian base.ExecuteResult(context)akan membuat tampilan yang sesuai karena lagi-lagi saya berasal ViewResult. Cukup sederhana bukan? Semoga ini akan diterapkan di inti MVC.

Lihat di BaseControllerbawah saya :

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

Untuk digunakan dalam tindakan Anda seperti ini:

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

Dan di _Layout.cshtml (seperti halaman master)

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

Selain itu Anda dapat menggunakan tampilan kustom seperti Error.shtmlatau membuat baru NotFound.cshtmlseperti yang saya komentari dalam kode dan Anda dapat menentukan model tampilan untuk deskripsi status dan penjelasan lainnya.

CallMeLaNN
sumber
Anda selalu dapat mendaftarkan filter global yang mengalahkan pengontrol dasar karena Anda harus INGAT untuk menggunakan pengontrol dasar!
John Culviner
:) Tidak yakin apakah ini masih menjadi masalah di MVC4. Yang saya maksud saat itu adalah filter HandleNotFoundAttribute yang dijawab oleh orang lain. Tidak perlu diterapkan untuk setiap tindakan. Misalnya, hanya cocok untuk tindakan yang memiliki parameter id tetapi tidak tindakan Indeks (). Saya menyetujui filter global, bukan untuk HandleNotFoundAttribute tetapi HandleErrorAttribute kustom.
CallMeLaNN
Saya pikir MVC3 juga memilikinya, tidak yakin. Diskusi yang bagus terlepas dari orang lain yang mungkin menemukan jawabannya
John Culviner