[asp.net-mvc] ASP.NET MVC에서 404를 올바르게 처리하려면 어떻게해야합니까?

RC2를 사용하고 있습니다

URL 라우팅 사용 :

routes.MapRoute(
    "Error",
     "{*url}",
     new { controller = "Errors", action = "NotFound" }  // 404s
);

위의 요청은 다음과 같은 요청을 처리하는 것으로 보입니다 (초기 MVC 프로젝트에서 기본 라우팅 테이블 설정을 가정) : “/ blah / blah / blah / blah”

컨트롤러 자체에서 HandleUnknownAction () 재정의 :

// 404s - handle here (bad action requested
protected override void HandleUnknownAction(string actionName) {
    ViewData["actionName"] = actionName;
    View("NotFound").ExecuteResult(this.ControllerContext);
}  

그러나 이전 전략은 Bad / Unknown 컨트롤러에 대한 요청을 처리하지 않습니다. 예를 들어, 나는 “/ IDoNotExist”를 가지고 있지 않다. 이것을 요청하면 웹 서버에서 일반 404 페이지를 얻습니다. 라우팅 + 재정의를 사용하면 404 페이지가 아닙니다.

마지막으로, 내 질문은 : MVC 프레임 워크 자체에서 경로 또는 다른 것을 사용하여 이러한 유형의 요청을 잡을 수있는 방법이 있습니까?

또는 기본적으로 Web.Config customErrors를 404 처리기로 사용 하고이 모든 것을 잊어 버려야합니까? customErrors를 사용하면 직접 액세스에 대한 Web.Config 제한으로 인해 / Views 외부에 일반 404 페이지를 저장해야한다고 가정합니다.



답변

코드는 http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx 에서 가져와 작동합니다 ASP.net MVC 1.0에서도

http 예외를 처리하는 방법은 다음과 같습니다.

protected void Application_Error(object sender, EventArgs e)
{
   Exception exception = Server.GetLastError();
   // Log the exception.

   ILogger logger = Container.Resolve<ILogger>();
   logger.Error(exception);

   Response.Clear();

   HttpException httpException = exception as HttpException;

   RouteData routeData = new RouteData();
   routeData.Values.Add("controller", "Error");

   if (httpException == null)
   {
       routeData.Values.Add("action", "Index");
   }
   else //It's an Http Exception, Let's handle it.
   {
       switch (httpException.GetHttpCode())
       {
          case 404:
              // Page not found.
              routeData.Values.Add("action", "HttpError404");
              break;
          case 500:
              // Server error.
              routeData.Values.Add("action", "HttpError500");
              break;

           // Here you can handle Views to other error codes.
           // I choose a General error template  
           default:
              routeData.Values.Add("action", "General");
              break;
      }
  }

  // Pass exception details to the target error View.
  routeData.Values.Add("error", exception);

  // Clear the error on server.
  Server.ClearError();

  // Avoid IIS7 getting in the middle
  Response.TrySkipIisCustomErrors = true;

  // Call target Controller and pass the routeData.
  IController errorController = new ErrorController();
  errorController.Execute(new RequestContext(
       new HttpContextWrapper(Context), routeData));
}


답변

404 요구 사항

다음은 404 솔루션에 대한 요구 사항이며 아래에서 구현 방법을 보여줍니다.

  • 나쁜 행동으로 일치하는 경로를 처리하고 싶습니다.
  • 불량 컨트롤러로 일치하는 경로를 처리하고 싶습니다.
  • 일치하지 않는 경로 (앱이 이해할 수없는 임의의 URL)를 처리하고 싶습니다 .Global.asax 또는 IIS까지 버블 링을 원하지 않습니다. 그러면 MVC 앱으로 올바르게 리디렉션 할 수 없기 때문입니다
  • 존재하지 않는 객체에 대해 ID를 제출할 때와 같이 위와 같은 방식으로 사용자 정의 404를 처리하는 방법을 원합니다.
  • 난 내가 이상 (필요한 경우 더 많은 데이터를 펌핑 할 수있는 할 수있는 MVC 뷰 (안 정적 페이지)를 돌려 내 모든 404을 원하는 좋은 404 개 디자인 ) 그들이 있어야 는 HTTP 404 상태 코드를 반환

해결책

Application_Error처리되지 않은 예외 및 로깅 ( Shay Jacoby의 답변 쇼 와 같은)과 같은 더 높은 것들을 위해 Global.asax에 저장해야 하지만 404 처리는하지 않아야한다고 생각합니다 . 이것이 내 제안이 Global.asax 파일에서 404 항목을 유지하는 이유입니다.

1 단계 : 404 오류 논리의 공통 위치

이것은 유지 관리에 좋은 아이디어입니다. 잘 설계된 404 페이지의 향후 개선 사항을 쉽게 적용 할 수 있도록 ErrorController를 사용하십시오 . 또한 응답에 404 코드가 있는지 확인하십시오 !

public class ErrorController : MyController
{
    #region Http404

    public ActionResult Http404(string url)
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        var model = new NotFoundViewModel();
        // If the url is relative ('NotFound' route) then replace with Requested path
        model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ?
            Request.Url.OriginalString : url;
        // Dont get the user stuck in a 'retry loop' by
        // allowing the Referrer to be the same as the Request
        model.ReferrerUrl = Request.UrlReferrer != null &&
            Request.UrlReferrer.OriginalString != model.RequestedUrl ?
            Request.UrlReferrer.OriginalString : null;

        // TODO: insert ILogger here

        return View("NotFound", model);
    }
    public class NotFoundViewModel
    {
        public string RequestedUrl { get; set; }
        public string ReferrerUrl { get; set; }
    }

    #endregion
}

2 단계 : 기본 Controller 클래스를 사용하여 사용자 정의 404 조치를 쉽게 호출하고 연결하십시오. HandleUnknownAction

ASP.NET MVC의 404는 여러 곳에서 포착해야합니다. 첫 번째는 HandleUnknownAction입니다.

InvokeHttp404방법은 ErrorController새로운 Http404작업으로 다시 라우팅 할 수있는 일반적인 장소를 만듭니다 . 건조를 생각하십시오 !

public abstract class MyController : Controller
{
    #region Http404 handling

    protected override void HandleUnknownAction(string actionName)
    {
        // If controller is ErrorController dont 'nest' exceptions
        if (this.GetType() != typeof(ErrorController))
            this.InvokeHttp404(HttpContext);
    }

    public ActionResult InvokeHttp404(HttpContextBase httpContext)
    {
        IController errorController = ObjectFactory.GetInstance<ErrorController>();
        var errorRoute = new RouteData();
        errorRoute.Values.Add("controller", "Error");
        errorRoute.Values.Add("action", "Http404");
        errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
        errorController.Execute(new RequestContext(
             httpContext, errorRoute));

        return new EmptyResult();
    }

    #endregion
}

3 단계 : Controller Factory에서 Dependency Injection을 사용하고 404 HttpExceptions 연결

이와 같이 (StructureMap 일 필요는 없음) :

MVC1.0 예 :

public class StructureMapControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(RequestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }
}

MVC2.0 예 :

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(requestContext, controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == 404)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(requestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }

오류가 발생한 위치에 더 가깝게 오류를 잡는 것이 좋습니다. 이것이 내가 Application_Error핸들러 보다 위의 것을 선호하는 이유 입니다.

이것은 404를 잡는 두 번째 장소입니다.

4 단계 : 앱으로 구문 분석되지 않은 URL에 대해 NotFound 경로를 Global.asax에 추가

이 경로는 우리의 Http404행동을 가리켜 야합니다 . 통지 url라우팅 엔진이 여기에 도메인 부분을 제거하기 때문에 PARAM 상대 URL 될 것인가? 이것이 1 단계에서 모든 조건부 URL 논리를 갖는 이유입니다.

        routes.MapRoute("NotFound", "{*url}",
            new { controller = "Error", action = "Http404" });

MVC 앱에서 404를 잡을 수있는 세 번째이자 마지막 장소입니다. 여기서 일치하지 않는 경로를 찾지 못하면 MVC가 문제를 ASP.NET (Global.asax)으로 전달하므로이 상황에서는 실제로 원하지 않습니다.

5 단계 : 마지막으로 앱에서 무언가를 찾을 수 없을 때 404를 호출합니다.

잘못된 ID가 대출 컨트롤러에 제출 될 때와 같이 (에서 파생 됨 MyController) :

    //
    // GET: /Detail/ID

    public ActionResult Detail(int ID)
    {
        Loan loan = this._svc.GetLoans().WithID(ID);
        if (loan == null)
            return this.InvokeHttp404(HttpContext);
        else
            return View(loan);
    }

이 모든 것이 적은 코드로 적은 수의 장소에 연결될 수 있다면 좋을 것입니다.하지만이 솔루션은 유지 관리가 쉽고 테스트 가능하며 실용적이라고 생각합니다.

지금까지 의견을 보내 주셔서 감사합니다. 더 많은 것을 얻고 싶습니다.

참고 : 이것은 원래의 답변에서 크게 편집되었지만 목적 / 요구 사항은 동일합니다-이것이 새로운 답변을 추가하지 않은 이유입니다


답변

ASP.NET MVC는 사용자 지정 404 페이지를 잘 지원하지 않습니다. 맞춤형 컨트롤러 공장, 포괄 루트, 기본 컨트롤러 클래스 HandleUnknownAction-argh!

지금까지 IIS 사용자 정의 오류 페이지가 더 나은 대안입니다.

web.config

<system.webServer>
  <httpErrors errorMode="Custom" existingResponse="Replace">
    <remove statusCode="404" />
    <error statusCode="404" responseMode="ExecuteURL" path="/Error/PageNotFound" />
  </httpErrors>
</system.webServer>

ErrorController

public class ErrorController : Controller
{
    public ActionResult PageNotFound()
    {
        Response.StatusCode = 404;
        return View();
    }
}

샘플 프로젝트


답변

빠른 답변 / TL; DR

여기에 이미지 설명을 입력하십시오

게으른 사람들을 위해 :

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

그런 다음이 줄을 제거하십시오. global.asax

GlobalFilters.Filters.Add(new HandleErrorAttribute());

그리고 이것은 IIS7 + 및 IIS Express에만 해당됩니다.

Cassini를 사용하는 경우 .. 음 .. 어 .. 어색한 …
어색한


길고 설명 된 답변

나는 이것이 대답되었다는 것을 안다. 그러나 그 대답은 정말 간단합니다 ( David FowlerDamian Edwards 에게 정말로 응답 해주었습니다).

사용자 정의 작업을 수행 할 필요없습니다 .

의 경우 ASP.NET MVC3모든 비트와 조각이 있습니다.

1 단계-> 두 개의 지점에서 web.config를 업데이트하십시오.

<system.web>
    <customErrors mode="On" defaultRedirect="/ServerError">
      <error statusCode="404" redirect="/NotFound" />
    </customErrors>

<system.webServer>
    <httpErrors errorMode="Custom">
      <remove statusCode="404" subStatusCode="-1" />
      <error statusCode="404" path="/NotFound" responseMode="ExecuteURL" />
      <remove statusCode="500" subStatusCode="-1" />
      <error statusCode="500" path="/ServerError" responseMode="ExecuteURL" />
    </httpErrors>

...
<system.webServer>
...
</system.web>

이제 사용하기로 결정한 경로를주의해서 기록하십시오. 당신은 아무것도 사용할 수 있지만 내 경로는

  • /NotFound <-404를 찾을 수 없으면 오류 페이지입니다.
  • /ServerError<-다른 오류의 경우 내 코드에서 발생하는 오류를 포함하십시오. 이것은 500 내부 서버 오류입니다

첫 번째 섹션에 하나의 사용자 정의 항목 <system.web>만 있는 방법을 참조하십시오 항목? 나는 오직 하나 개의 상태 코드를 나열했습니다 때문에 포함한 다른 모든 오류 .. 다른 모든 오류가 설정에 의해 처리됩니다 (코드 버그를 가지고 있으며, 사용자의 요청을 충돌 때 발생하는 즉. 그 성가신 오류) 라고 .. , 404 페이지를 찾을 수없는 경우 경로로 이동하십시오 .statusCode="404"500 Server ErrordefaultRedirect="/ServerError"/ServerError

확인. 그 길을 벗어났습니다 .. 지금 내 경로에 나열된global.asax

2 단계-Global.asax에서 경로 생성

여기 전체 경로 섹션이 있습니다.

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("{*favicon}", new {favicon = @"(.*/)?favicon.ico(/.*)?"});

    routes.MapRoute(
        "Error - 404",
        "NotFound",
        new { controller = "Error", action = "NotFound" }
        );

    routes.MapRoute(
        "Error - 500",
        "ServerError",
        new { controller = "Error", action = "ServerError"}
        );

    routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new {controller = "Home", action = "Index", id = UrlParameter.Optional}
        );
}

여기에는 두 개의 무시 경로가 표시됩니다-> axd'sfavicons(ooo! 보너스 무시 경로, 당신을 위해!) 그런 다음 (그리고 순서는 여기에서 중요합니다), 나는 두 개의 명시 적 오류 처리 경로가 있습니다. 이 경우 기본 설정입니다. 물론, 나는 더 많은 것을 가지고 있지만 그것은 내 웹 사이트에 특별합니다. 오류 경로가 목록의 맨 위에 있는지 확인하십시오. 순서는 필수적 입니다.

마지막으로 global.asax파일 내부에있는 동안 HandleError 특성을 전역 적으로 등록하지 않습니다. 아뇨, 아뇨 나다. 아니. 니엔 부정. 안돼 …

이 줄을 제거하십시오 global.asax

GlobalFilters.Filters.Add(new HandleErrorAttribute());

3 단계-조치 방법으로 제어기 작성

이제 .. 우리는 두 가지 행동 방법으로 컨트롤러를 추가합니다 …

public class ErrorController : Controller
{
    public ActionResult NotFound()
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        return View();
    }

    public ActionResult ServerError()
    {
        Response.StatusCode = (int)HttpStatusCode.InternalServerError;

        // Todo: Pass the exception into the view model, which you can make.
        //       That's an exercise, dear reader, for -you-.
        //       In case u want to pass it to the view, if you're admin, etc.
        // if (User.IsAdmin) // <-- I just made that up :) U get the idea...
        // {
        //     var exception = Server.GetLastError();
        //     // etc..
        // }

        return View();
    }

    // Shhh .. secret test method .. ooOOooOooOOOooohhhhhhhh
    public ActionResult ThrowError()
    {
        throw new NotImplementedException("Pew ^ Pew");
    }
}

좋아, 이것 좀 봐 우선, 여기 에는 속성 이 없습니다 [HandleError] . 왜? 내장 된 ASP.NET프레임 워크는 이미 오류를 처리 하고 있기 때문에 오류를 처리하기 위해 해야하는 모든 똥을 지정했습니다. :)이 방법에 있습니다!

다음으로 두 가지 행동 방법이 있습니다. 거기에 힘든 것은 없습니다. 예외 정보를 표시하려면 Server.GetLastError()해당 정보를 얻는 데 사용할 수 있습니다 .

보너스 WTF : 예, 오류 처리를 테스트하기 위해 세 번째 조치 방법을 만들었습니다.

4 단계-보기 작성

마지막으로 두 개의 뷰를 만듭니다. 이 컨트롤러에 대해 정상적인 시점에 배치하십시오.

여기에 이미지 설명을 입력하십시오

보너스 코멘트

  • 당신은 필요하지 않습니다 Application_Error(object sender, EventArgs e)
  • 위의 단계는 모두 Elmah 와 100 % 완벽하게 작동 합니다. 엘마 프레이크 스 약!

그리고 그것은 내 친구들입니다.

자, 이것을 많이 읽은 것을 축하하고 유니콘을 상으로 삼았습니다!

여기에 이미지 설명을 입력하십시오


답변

MVC (특히 MVC3) 에서 404를 올바르게 관리하는 방법에 대해 많은 것을 조사 했으며 IMHO는 내가 생각해 낸 최고의 솔루션입니다.

global.asax에서 :

public class MvcApplication : HttpApplication
{
    protected void Application_EndRequest()
    {
        if (Context.Response.StatusCode == 404)
        {
            Response.Clear();

            var rd = new RouteData();
            rd.DataTokens["area"] = "AreaName"; // In case controller is in another area
            rd.Values["controller"] = "Errors";
            rd.Values["action"] = "NotFound";

            IController c = new ErrorsController();
            c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
        }
    }
}

ErrorsController :

public sealed class ErrorsController : Controller
{
    public ActionResult NotFound()
    {
        ActionResult result;

        object model = Request.Url.PathAndQuery;

        if (!Request.IsAjaxRequest())
            result = View(model);
        else
            result = PartialView("_NotFound", model);

        return result;
    }
}

(선택 과목)

설명:

AFAIK에는 ASP.NET MVC3 앱이 404를 생성 할 수있는 6 가지 경우가 있습니다.

(ASP.NET Framework에서 자동 생성 🙂

(1) URL이 라우팅 테이블에서 일치하는 것을 찾지 않습니다.

(ASP.NET MVC 프레임 워크에 의해 자동으로 생성됨)

(2) URL이 라우팅 테이블에서 일치하는 것을 찾지 만 존재하지 않는 컨트롤러를 지정합니다.

(3) URL은 라우팅 테이블에서 일치하는 항목을 찾지 만 존재하지 않는 작업을 지정합니다.

(수동 생성 🙂

(4) 액션은 HttpNotFound () 메소드를 사용하여 HttpNotFoundResult를 반환합니다.

(5) 액션은 상태 코드가 404 인 HttpException을 발생시킵니다.

(6) 작업은 Response.StatusCode 속성을 404로 수동으로 수정합니다.

일반적으로 3 가지 목표를 달성하려고합니다.

(1) 사용자에게 404 오류 페이지를 보여줍니다.

(2) 클라이언트 응답에 404 상태 코드를 유지하십시오 (특히 SEO에 중요 함).

(3) 302 리디렉션을 포함하지 않고 직접 응답을 보냅니다.

이를 달성하기 위해 여러 가지 방법이 있습니다.

(1)

<system.web>
    <customErrors mode="On">
        <error statusCode="404" redirect="~/Errors/NotFound"/>
    </customError>
</system.web>

이 솔루션의 문제점 :

  1. (1), (4), (6)의 경우 목표 (1)을 준수하지 않습니다.
  2. 목표 (2)를 자동으로 준수하지 않습니다. 수동으로 프로그래밍해야합니다.
  3. 목표 (3)을 준수하지 않습니다.

(2)

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

이 솔루션의 문제점 :

  1. IIS 7 이상에서만 작동합니다.
  2. 사례 (2), (3), (5)에서 목표 (1)을 준수하지 않습니다.
  3. 목표 (2)를 자동으로 준수하지 않습니다. 수동으로 프로그래밍해야합니다.

(삼)

<system.webServer>
    <httpErrors errorMode="Custom" existingResponse="Replace">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

이 솔루션의 문제점 :

  1. IIS 7 이상에서만 작동합니다.
  2. 목표 (2)를 자동으로 준수하지 않습니다. 수동으로 프로그래밍해야합니다.
  3. 응용 프로그램 레벨 http 예외를 숨 깁니다. 예를 들어 customErrors 섹션, System.Web.Mvc.HandleErrorAttribute 등은 사용할 수 없습니다. 일반적인 오류 페이지 만 표시 할 수는 없습니다.

(4)

<system.web>
    <customErrors mode="On">
        <error statusCode="404" redirect="~/Errors/NotFound"/>
    </customError>
</system.web>

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

이 솔루션의 문제점 :

  1. IIS 7 이상에서만 작동합니다.
  2. 목표 (2)를 자동으로 준수하지 않습니다. 수동으로 프로그래밍해야합니다.
  3. (2), (3), (5)의 경우 목표 (3)을 준수하지 않습니다.

전에 문제가있는 사람들은 자신의 라이브러리를 만들려고 시도했습니다 ( http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html 참조 ). 그러나 이전 솔루션은 외부 라이브러리 사용의 복잡성없이 모든 경우를 다루는 것으로 보입니다.


답변

나는 cottsaks 솔루션을 정말 좋아하고 매우 명확하게 설명되어 있다고 생각합니다. 내 유일한 추가는 다음과 같이 2 단계를 변경하는 것이 었습니다.

public abstract class MyController : Controller
{

    #region Http404 handling

    protected override void HandleUnknownAction(string actionName)
    {
        //if controller is ErrorController dont 'nest' exceptions
        if(this.GetType() != typeof(ErrorController))
        this.InvokeHttp404(HttpContext);
    }

    public ActionResult InvokeHttp404(HttpContextBase httpContext)
    {
        IController errorController = ObjectFactory.GetInstance<ErrorController>();
        var errorRoute = new RouteData();
        errorRoute.Values.Add("controller", "Error");
        errorRoute.Values.Add("action", "Http404");
        errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
        errorController.Execute(new RequestContext(
             httpContext, errorRoute));

        return new EmptyResult();
    }

    #endregion
}

기본적으로 이는 유효하지 않은 조치 및 컨트롤러를 포함하는 URL이 예외 루틴을 두 번 트리거하지 못하게합니다. 예 : asdfsdf / dfgdfgd와 같은 URL의 경우


답변

잘못된 컨트롤러에 대해 @cottsak의 메소드를 얻을 수있는 유일한 방법은 다음과 같이 CustomControllerFactory에서 기존 경로 요청을 수정하는 것입니다.

public class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(requestContext, controllerType);
            else
                return ObjectFactory.GetInstance(controllerType) as Controller;
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                requestContext.RouteData.Values["controller"] = "Error";
                requestContext.RouteData.Values["action"] = "Http404";
                requestContext.RouteData.Values.Add("url", requestContext.HttpContext.Request.Url.OriginalString);

                return ObjectFactory.GetInstance<ErrorController>();
            }
            else
                throw ex;
        }
    }
}

MVC 2.0을 사용하고 있다고 언급해야합니다.