วิธีที่เหมาะสมในการส่งการตอบสนอง HTTP 404 จากการดำเนินการ ASP.NET MVC คืออะไร?


92

หากระบุเส้นทาง:

{FeedName} / {ItemPermalink}

เช่น / Blog / Hello-World

หากไม่มีรายการอยู่ฉันต้องการส่งคืน 404 วิธีที่ถูกต้องในการดำเนินการนี้ใน ASP.NET MVC คืออะไร?


ขอบคุณที่ถามคำถามนี้ btw สิ่งนี้จะเพิ่มขึ้นในโครงการมาตรฐานของฉัน: D
Erik van Brakel

คำตอบ:


69

ถ่ายจากสะโพก (การเข้ารหัสคาวบอย ;-)) ฉันขอแนะนำสิ่งนี้:

ตัวควบคุม:

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

การใช้แนวทางนี้แสดงว่าคุณปฏิบัติตามมาตรฐานกรอบงาน มี HttpUnauthorizedResult อยู่แล้วดังนั้นนี่จะเป็นการขยายกรอบในสายตาของนักพัฒนารายอื่นที่ดูแลรหัสของคุณในภายหลัง (คุณรู้ไหมว่าโรคจิตที่รู้ว่าคุณอาศัยอยู่ที่ไหน)

คุณสามารถใช้ตัวสะท้อนแสงเพื่อตรวจสอบชุดประกอบเพื่อดูว่า HttpUnauthorizedResult บรรลุผลได้อย่างไรเพราะฉันไม่รู้ว่าแนวทางนี้พลาดอะไรไป (ดูเหมือนง่ายเกินไป)


ฉันใช้ตัวสะท้อนแสงเพื่อดู HttpUnauthorizedResult ในตอนนี้ ดูเหมือนว่าพวกเขากำลังตั้งค่า StatusCode ในการตอบสนองเป็น 0x191 (401) แม้ว่าจะใช้งานได้กับ 401 แต่การใช้ 404 เป็นค่าใหม่ดูเหมือนว่าฉันจะได้รับเพียงหน้าเปล่าใน Firefox Internet Explorer แสดง 404 เริ่มต้นแม้ว่า (ไม่ใช่เวอร์ชัน ASP.NET) การใช้แถบเครื่องมือนักพัฒนาเว็บฉันตรวจสอบส่วนหัวใน FF ซึ่ง DO แสดงการตอบสนอง 404 Not Found อาจเป็นเพียงสิ่งที่ฉันกำหนดค่าผิดใน FF


ฉันคิดว่าแนวทางของ Jeff เป็นตัวอย่างที่ดีของ KISS หากคุณไม่ต้องการความฟุ่มเฟื่อยในตัวอย่างนี้วิธีการของเขาก็ใช้ได้ดีเช่นกัน


ใช่ฉันสังเกตเห็น Enum เช่นกัน อย่างที่ฉันพูดไปมันเป็นเพียงแค่ตัวอย่างคร่าวๆเท่านั้นอย่าลังเลที่จะปรับปรุงมัน นี่ควรจะเป็นฐานความรู้หลังจากนั้น ;-)
Erik van Brakel

ฉันคิดว่าฉันไปลงน้ำนิดหน่อย ... สนุก: D
Daniel Schaffer

FWIW ตัวอย่างของ Jeff ยังกำหนดให้คุณมีหน้า 404 ที่กำหนดเอง
Daniel Schaffer

2
ปัญหาอย่างหนึ่งในการโยน HttpException แทนที่จะตั้งค่า HttpContext.Response.StatusCode = 404 คือถ้าคุณใช้ตัวจัดการ OnException Controller (เหมือนที่ฉันทำ) มันจะจับ HttpExceptions ด้วย ดังนั้นฉันคิดว่าการตั้งค่า StatusCode เป็นแนวทางที่ดีกว่า
Igor Brejc

4
HttpException หรือ HttpNotFoundResult ใน MVC3 มีประโยชน์หลายประการ ในกรณีของ @Igor Brejc ให้ใช้คำสั่งifใน OnException เพื่อกรองข้อผิดพลาดที่ไม่พบออกไป
CallMeLaNN

46

เราทำเช่นนั้น พบรหัสนี้ในBaseController

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

เรียกอย่างนั้น

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

การดำเนินการนี้เชื่อมต่อกับเส้นทางเริ่มต้นหรือไม่ ไม่เห็นว่ามันจะถูกดำเนินการอย่างไร
Christian Dalager

2
สามารถเรียกใช้งานได้ดังนี้: protected override void HandleUnknownAction (string actionName) {PageNotFound (). ExecuteResult (this.ControllerContext); }
Tristan Warner-Smith

ฉันเคยทำแบบนั้น แต่พบว่าการแยกผลลัพธ์และมุมมองที่แสดงเป็นแนวทางที่ดีกว่า ตรวจสอบคำตอบของฉันด้านล่าง
Brian Vallelunga

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

ฉันชอบสิ่งนี้เพราะเป็นไปตามหน้าข้อผิดพลาดที่กำหนดเองที่ตั้งค่าไว้ในweb.config.
Mike Cole

7

HttpNotFoundResult เป็นขั้นตอนแรกที่ยอดเยี่ยมสำหรับสิ่งที่ฉันใช้ การส่งคืน HttpNotFoundResult เป็นสิ่งที่ดี คำถามคืออะไรต่อไป?

ฉันสร้างตัวกรองการดำเนินการที่เรียกว่า HandleNotFoundAttribute ซึ่งจะแสดงหน้าข้อผิดพลาด 404 เนื่องจากจะส่งคืนมุมมองคุณสามารถสร้างมุมมอง 404 พิเศษต่อคอนโทรลเลอร์หรือให้ใช้มุมมอง 404 ที่แชร์เริ่มต้น สิ่งนี้จะถูกเรียกเมื่อคอนโทรลเลอร์ไม่มีการดำเนินการที่ระบุไว้เนื่องจากเฟรมเวิร์กแสดง HttpException ด้วยรหัสสถานะเป็น 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
                                        };
        }
    }
}


6

การใช้ActionFilterนั้นยากที่จะรักษาเพราะเมื่อใดก็ตามที่เราเกิดข้อผิดพลาดจะต้องตั้งค่าตัวกรองในแอตทริบิวต์ ถ้าเราลืมตั้งค่าล่ะ? วิธีหนึ่งคือได้มาOnExceptionจากตัวควบคุมฐาน คุณจำเป็นต้องกำหนดBaseControllerมาจากและตัวควบคุมของคุณทุกคนจะต้องมาจากController BaseControllerเป็นแนวทางปฏิบัติที่ดีที่สุดในการมีตัวควบคุมฐาน

โปรดทราบว่าหากใช้Exceptionรหัสสถานะการตอบกลับคือ 500 ดังนั้นเราจำเป็นต้องเปลี่ยนเป็น 404 สำหรับ Not Found และ 401 สำหรับ Unauthorized เช่นเดียวกับที่ฉันกล่าวถึงข้างต้นให้ใช้การOnExceptionแทนที่BaseControllerเพื่อหลีกเลี่ยงการใช้แอตทริบิวต์ตัวกรอง

MVC 3 ใหม่ยังทำให้เกิดปัญหามากขึ้นด้วยการคืนมุมมองที่ว่างเปล่าไปยังเบราว์เซอร์ ทางออกที่ดีที่สุดหลังจากการวิจัยมาจากคำตอบของฉันที่นี่จะคืนมุมมองสำหรับ HttpNotFound () ใน ASP.Net MVC 3 ได้อย่างไร

เพื่อให้สะดวกสบายมากขึ้นฉันวางไว้ที่นี่:


หลังจากการศึกษาบางส่วน วิธีแก้ปัญหาสำหรับMVC 3ที่นี่คือการได้รับมาทั้งหมดHttpNotFoundResult, HttpUnauthorizedResult, HttpStatusCodeResultการเรียนและการดำเนินการใหม่ (เอาชนะมัน) HttpNotFound() BaseControllerวิธีการใน

เป็นแนวทางปฏิบัติที่ดีที่สุดในการใช้ตัวควบคุมพื้นฐานเพื่อให้คุณมี 'การควบคุม' เหนือคอนโทรลเลอร์ที่ได้รับทั้งหมด

ฉันจะสร้างใหม่HttpStatusCodeResultชั้นไม่ได้ที่จะมาจากActionResultแต่จากViewResultที่จะทำให้มุมมองหรือViewคุณต้องการโดยการระบุViewNameคุณสมบัติ ฉันทำตามเดิมHttpStatusCodeResultการตั้งค่าHttpContext.Response.StatusCodeและHttpContext.Response.StatusDescriptionแต่แล้วจะทำให้มุมมองที่เหมาะสมอีกครั้งเพราะผมมาจากbase.ExecuteResult(context) ViewResultง่ายพอหรือยัง หวังว่าสิ่งนี้จะถูกนำไปใช้ในแกน MVC

ดูการBaseControllerร้องของฉัน:

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

เพื่อใช้ในการกระทำของคุณเช่นนี้:

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

และใน _Layout.cshtml (เช่นหน้าต้นแบบ)

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

นอกจากนี้คุณสามารถใช้มุมมองแบบกำหนดเองเช่นError.shtmlหรือสร้างใหม่NotFound.cshtmlเหมือนที่ฉันแสดงความคิดเห็นในโค้ดและคุณอาจกำหนดรูปแบบมุมมองสำหรับคำอธิบายสถานะและคำอธิบายอื่น ๆ


คุณสามารถลงทะเบียนตัวกรองส่วนกลางที่เอาชนะตัวควบคุมพื้นฐานได้ตลอดเวลาเพราะคุณต้องจำไว้ว่าใช้ตัวควบคุมฐานของคุณ!
John Culviner

:) ไม่แน่ใจว่านี่ยังคงเป็นปัญหาใน MVC4 สิ่งที่ฉันหมายถึงในตอนนั้นคือตัวกรอง HandleNotFoundAttribute ตอบโดยคนอื่น ไม่จำเป็นต้องใช้กับการกระทำแต่ละอย่าง เช่นเหมาะสำหรับการดำเนินการที่มี id param แต่ไม่ใช่การกระทำ Index () ฉันเห็นด้วยกับตัวกรองส่วนกลางไม่ใช่สำหรับ HandleNotFoundAttribute แต่เป็น HandleErrorAttribute ที่กำหนดเอง
CallMeLaNN

ฉันคิดว่า MVC3 ก็มีเหมือนกันไม่แน่ใจ การอภิปรายที่ดีโดยไม่คำนึงถึงคนอื่น ๆ ที่อาจเจอคำตอบ
John Culviner
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.