ASP.NET MVC 작업에서 HTTP 404 응답을 보내는 적절한 방법은 무엇입니까?


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를 살펴 보았습니다. 0x191 (401)에 대한 응답에 StatusCode를 설정하는 것 같습니다. 이것은 401에서 작동하지만 404를 새 값으로 사용하면 Firefox에서 빈 페이지가 나타나는 것 같습니다. Internet Explorer는 기본 404를 표시합니다 (ASP.NET 버전이 아님). 웹 개발자 도구 모음을 사용하여 FF의 헤더를 검사했는데 404 Not Found 응답이 표시됩니다. FF에서 잘못 구성한 것일 수 있습니다.


이 말은 Jeff의 접근 방식이 KISS의 좋은 예라고 생각합니다. 이 샘플의 자세한 정보가 실제로 필요하지 않은 경우 그의 방법도 잘 작동합니다.


네, Enum도 알아 챘습니다. 내가 말했듯이, 그것은 단지 조잡한 예일 뿐이므로 자유롭게 개선 할 수 있습니다. 이것은 결국 지식 기반이어야합니다 ;-)
Erik van Brakel

I 즐길 ... 나는 조금 배 밖으로 갔다 생각 : D
다니엘 셰퍼

FWIW, Jeff의 예제에서는 사용자 지정 404 페이지가 있어야합니다.
Daniel Schaffer

2
HttpContext.Response.StatusCode = 404를 설정하는 대신 HttpException을 던지는 한 가지 문제는 OnException 컨트롤러 처리기를 사용하는 경우 (내가하는 것처럼) HttpExceptions도 catch한다는 것입니다. 따라서 StatusCode를 설정하는 것이 더 나은 접근 방식이라고 생각합니다.
Igor Brejc 2009

4
MVC3의 HttpException 또는 HttpNotFoundResult는 여러면에서 유용합니다. @Igor Brejc의 경우 OnException의 if 문을 사용 하여 찾을 수 없음 오류를 필터링하십시오.
CallMeLaNN 2011 년

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를 반환하는 것이 좋습니다. 그럼 문제는 다음은 무엇입니까?

404 오류 페이지를 표시하는 HandleNotFoundAttribute라는 작업 필터를 만들었습니다. 보기를 반환하기 때문에 컨트롤러 당 특별한 404보기를 만들거나 기본 공유 404보기를 사용하도록 할 수 있습니다. 프레임 워크가 상태 코드가 404 인 HttpException을 발생시키기 때문에 컨트롤러에 지정된 작업이없는 경우에도 호출됩니다.

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로, 권한이없는 경우 401로 변경해야합니다. 위에서 언급했듯이 필터 속성을 사용하지 않으 려면 OnException재정의를 BaseController사용하십시오.

새로운 MVC 3는 또한 빈 뷰를 브라우저에 반환함으로써 더 문제가됩니다. 일부 연구 후 최고의 솔루션은 여기 내 대답을 기반으로합니다 .ASP.Net MVC 3에서 HttpNotFound ()에 대한 뷰를 반환하는 방법은 무엇입니까?

더 편리하게 여기에 붙여 넣습니다.


약간의 연구 후에. 대한 해결 MVC 3는 여기에 모든 유도하는 것입니다 HttpNotFoundResult, HttpUnauthorizedResult, HttpStatusCodeResult클래스를 구현할 새로운 (그것을 무시) HttpNotFound() 메서드를 BaseController.

파생 된 모든 컨트롤러를 '제어'할 수 있도록 기본 컨트롤러를 사용하는 것이 가장 좋습니다.

나는 새로운 생성 HttpStatusCodeResult되지에서 파생 클래스를 ActionResult만에서 ViewResult보기 또는 렌더링하는 View지정하여 원하는 ViewName속성을. 나는 원래에 따라 HttpStatusCodeResult세트에 HttpContext.Response.StatusCodeHttpContext.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 2013-08-22

:) 이것이 MVC4에서 여전히 문제인지 확실하지 않습니다. 그 당시 내가 의미하는 것은 다른 누군가가 응답 한 HandleNotFoundAttribute 필터입니다. 각 액션에 적용 할 필요는 없습니다. 예를 들어, ID 매개 변수가 있지만 Index () 작업이 아닌 작업에만 적합합니다. HandleNotFoundAttribute가 아니라 사용자 지정 HandleErrorAttribute에 대한 전역 필터에 동의했습니다.
CallMeLaNN

나는 MVC3도 그것을 가지고 있다고 생각했다. 답 건너 올 수 있습니다 다른 사람에 관계없이 좋은 토론
존 Culviner
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.