Cách thích hợp để gửi phản hồi HTTP 404 từ hành động ASP.NET MVC là gì?


92

Nếu đưa ra lộ trình:

{FeedName} / {ItemPermalink}

ví dụ: / Blog / Hello-World

Nếu mục không tồn tại, tôi muốn trả về 404. Cách phù hợp để thực hiện việc này trong ASP.NET MVC là gì?


Cảm ơn vì đã hỏi câu hỏi này btw. Điều này sẽ bổ sung trong dự án tiêu chuẩn của tôi: D
Erik van Brakel

Câu trả lời:


69

Chụp từ hông (mã hóa cao bồi ;-)), tôi muốn đề xuất một cái gì đó như thế này:

Bộ điều khiển:

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

Sử dụng cách tiếp cận này, bạn tuân thủ các tiêu chuẩn khung. Đã có HttpUnauthorizedResult ở đó, vì vậy điều này chỉ đơn giản là mở rộng khuôn khổ dưới con mắt của một nhà phát triển khác đang duy trì mã của bạn sau này (bạn biết đấy, người tâm lý biết bạn sống ở đâu).

Bạn có thể sử dụng gương phản xạ để nhìn vào tổ hợp để xem HttpUnauthorizedResult đạt được như thế nào, bởi vì tôi không biết liệu cách tiếp cận này có bỏ sót điều gì không (nó có vẻ quá đơn giản).


Tôi đã sử dụng gương phản xạ để xem HttpUnauthorizedResult vừa rồi. Có vẻ như họ đang đặt Mã trạng thái trên phản hồi với 0x191 (401). Mặc dù điều này hoạt động cho 401, sử dụng 404 làm giá trị mới, tôi dường như chỉ nhận được một trang trống trong Firefox. Internet Explorer hiển thị mặc định 404 (không phải phiên bản ASP.NET). Sử dụng thanh công cụ của trình phát triển web, tôi đã kiểm tra các tiêu đề trong FF, tiêu đề này sẽ hiển thị phản hồi 404 Not Found. Có thể chỉ đơn giản là một cái gì đó tôi đã định cấu hình sai trong FF.


Điều này đang được nói, tôi nghĩ cách tiếp cận của Jeff là một ví dụ điển hình về KISS. Nếu bạn không thực sự cần sự chi tiết trong mẫu này, phương pháp của anh ấy cũng hoạt động tốt.


Vâng, tôi cũng để ý đến Enum. Như tôi đã nói, đó chỉ là một ví dụ thô thiển, vui lòng cải thiện nó. Đây được coi là một sở kiến thức sau khi tất cả ;-)
Erik van Brakel

Tôi nghĩ rằng tôi đã đi một quá nhiệt tình chút ... thưởng thức: D
Daniel Schaffer

FWIW, ví dụ của Jeff cũng yêu cầu bạn phải có trang 404 tùy chỉnh.
Daniel Schaffer

2
Một vấn đề với việc ném HttpException thay vì chỉ đặt HttpContext.Response.StatusCode = 404 là nếu bạn sử dụng trình xử lý OnException Controller (như tôi làm), nó cũng sẽ bắt HttpExceptions. Vì vậy, tôi nghĩ chỉ cần đặt Mã trạng thái là một cách tiếp cận tốt hơn.
Igor Brejc

4
HttpException hoặc HttpNotFoundResult trong MVC3 hữu ích theo nhiều cách. Trong trường hợp của @Igor Brejc, chỉ cần sử dụng nếu tuyên bố trong OnException để lọc ra các lỗi không tìm thấy.
CallMeLaNN

46

Chúng tôi làm như vậy; mã này được tìm thấy trongBaseController

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

được gọi như vậy

public ActionResult ShowUserDetails(int? id)
{        
    // make sure we have a valid ID
    if (!id.HasValue) return PageNotFound();

hành động này sau đó có được kết nối với một tuyến đường mặc định không? Không thể thấy nó được thực thi như thế nào.
Christian Dalager

2
Có thể chạy nó như thế này: protected override void HandleUnknownAction (string actionName) {PageNotFound (). ExecuteResult (this.ControllerContext); }
Tristan Warner-Smith

Tôi đã từng làm theo cách đó, nhưng nhận thấy rằng tách kết quả và chế độ xem được hiển thị là một cách tiếp cận tốt hơn. Kiểm tra câu trả lời của tôi dưới đây.
Brian Vallelunga

19
throw new HttpException(404, "Are you sure you're in the right place?");

Tôi thích điều này vì nó tuân theo các trang lỗi tùy chỉnh được thiết lập trong web.config.
Mike Cole

7

HttpNotFoundResult là bước đầu tiên tuyệt vời cho những gì tôi đang sử dụng. Trả lại một HttpNotFoundResult là tốt. Sau đó, câu hỏi là, tiếp theo là gì?

Tôi đã tạo một bộ lọc hành động có tên HandleNotFoundAttribute sau đó hiển thị trang lỗi 404. Vì nó trả về một dạng xem, bạn có thể tạo một dạng xem 404 đặc biệt cho mỗi bộ điều khiển hoặc sử dụng dạng xem 404 được chia sẻ mặc định. Điều này thậm chí sẽ được gọi khi một bộ điều khiển không có hành động được chỉ định, bởi vì khung công tác ném một HttpException với mã trạng thái là 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

Lưu ý rằng kể từ MVC3, bạn chỉ có thể sử dụng HttpStatusCodeResult.


8
Hoặc, thậm chí dễ dàng hơn,HttpNotFoundResult
Matt Enright

6

Sử dụng ActionFilterkhó để duy trì bởi vì bất cứ khi nào chúng ta ném ra một lỗi cần lọc được thiết lập trong các thuộc tính. Điều gì sẽ xảy ra nếu chúng ta quên thiết lập nó? Một cách là bắt nguồn OnExceptiontừ bộ điều khiển cơ sở. Bạn cần xác định một BaseControllernguồn gốc Controllervà tất cả các bộ điều khiển của bạn phải bắt nguồn từ đó BaseController. Cách tốt nhất là có một bộ điều khiển cơ sở.

Lưu ý nếu sử dụng Exceptionmã trạng thái phản hồi là 500, vì vậy chúng ta cần thay đổi nó thành 404 cho Không tìm thấy và 401 cho Không được phép. Giống như tôi đã đề cập ở trên, hãy sử dụng OnExceptionghi đè trên BaseControllerđể tránh sử dụng thuộc tính bộ lọc.

MVC 3 mới cũng gây ra nhiều rắc rối hơn bằng cách trả lại một chế độ xem trống cho trình duyệt. Giải pháp tốt nhất sau một số nghiên cứu dựa trên câu trả lời của tôi ở đây Làm thế nào để trả lại chế độ xem cho HttpNotFound () trong ASP.Net MVC 3?

Để thuận tiện hơn, tôi dán nó vào đây:


Sau một số nghiên cứu. Cách giải quyết cho MVC 3 ở đây là để lấy được tất cả HttpNotFoundResult, HttpUnauthorizedResult, HttpStatusCodeResultlớp học và thực hiện mới (trọng nó) HttpNotFound() phương pháp trong BaseController.

Cách tốt nhất là sử dụng Bộ điều khiển cơ sở để bạn có 'quyền kiểm soát' đối với tất cả Bộ điều khiển có nguồn gốc.

Tôi tạo HttpStatusCodeResultlớp mới , không phải để bắt nguồn ActionResultmà từ đó ViewResultđể hiển thị dạng xem hoặc bất kỳ lớp nào Viewbạn muốn bằng cách chỉ định thuộc ViewNametính. Tôi làm theo bản gốc HttpStatusCodeResultđể thiết lập HttpContext.Response.StatusCodeHttpContext.Response.StatusDescriptionnhưng sau đó base.ExecuteResult(context)sẽ hiển thị chế độ xem phù hợp bởi vì một lần nữa tôi bắt nguồn từ ViewResult. Đủ đơn giản phải không? Hy vọng điều này sẽ được thực hiện trong lõi MVC.

Xem BaseControllerdưới đây của tôi :

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

Để sử dụng trong hành động của bạn như thế này:

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

Và trong _Layout.cshtml (như trang chính)

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

Ngoài ra, bạn có thể sử dụng chế độ xem tùy chỉnh giống như Error.shtmlhoặc tạo mới NotFound.cshtmlnhư tôi đã nhận xét trong mã và bạn có thể xác định mô hình chế độ xem cho mô tả trạng thái và các giải thích khác.


Bạn luôn có thể đăng ký bộ lọc chung đánh bại bộ điều khiển cơ sở bởi vì bạn phải NHỚ sử dụng bộ điều khiển cơ sở của mình!
John Culviner

:) Không chắc chắn đây vẫn là một vấn đề trong MVC4. Ý tôi lúc đó là bộ lọc HandleNotFoundAttribute được trả lời bởi người khác. Nó không cần thiết phải được áp dụng cho mỗi hành động. Ví dụ: nó chỉ phù hợp với hành động có id param nhưng không phù hợp với hành động Index (). Tôi đã đồng ý về bộ lọc toàn cầu, không phải cho HandleNotFoundAttribute mà là HandleErrorAttribute tùy chỉnh.
CallMeLaNN

Tôi nghĩ MVC3 cũng có nó, không chắc. Thảo luận tốt bất kể cho những người khác mà có thể đi qua các câu trả lời
John Culviner
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.