ASP.NET MVC 사용자 지정 오류 처리 Application_Error Global.asax?


108

내 MVC 응용 프로그램의 오류를 확인하는 몇 가지 기본 코드가 있습니다. 현재 내 프로젝트에서 나는라는 컨트롤러가 Error동작 방법과를 HTTPError404(), HTTPError500()하고 General(). 모두 문자열 매개 변수를 error받습니다. 아래 코드를 사용하거나 수정합니다. 처리를 위해 데이터를 오류 컨트롤러에 전달하는 가장 좋은 / 올바른 방법은 무엇입니까? 가능한 한 강력한 솔루션을 갖고 싶습니다.

protected void Application_Error(object sender, EventArgs e)
{
    Exception exception = Server.GetLastError();
    Response.Clear();

    HttpException httpException = exception as HttpException;
    if (httpException != null)
    {
        RouteData routeData = new RouteData();
        routeData.Values.Add("controller", "Error");
        switch (httpException.GetHttpCode())
        {
            case 404:
                // page not found
                routeData.Values.Add("action", "HttpError404");
                break;
            case 500:
                // server error
                routeData.Values.Add("action", "HttpError500");
                break;
            default:
                routeData.Values.Add("action", "General");
                break;
        }
        routeData.Values.Add("error", exception);
        // clear error on server
        Server.ClearError();

        // at this point how to properly pass route data to error controller?
    }
}

답변:


104

이를위한 새 경로를 만드는 대신 컨트롤러 / 작업으로 리디렉션하고 쿼리 문자열을 통해 정보를 전달할 수 있습니다. 예를 들면 :

protected void Application_Error(object sender, EventArgs e) {
  Exception exception = Server.GetLastError();
  Response.Clear();

  HttpException httpException = exception as HttpException;

  if (httpException != null) {
    string action;

    switch (httpException.GetHttpCode()) {
      case 404:
        // page not found
        action = "HttpError404";
        break;
      case 500:
        // server error
        action = "HttpError500";
        break;
      default:
        action = "General";
        break;
      }

      // clear error on server
      Server.ClearError();

      Response.Redirect(String.Format("~/Error/{0}/?message={1}", action, exception.Message));
    }

그러면 컨트롤러가 원하는 것을 수신합니다.

// GET: /Error/HttpError404
public ActionResult HttpError404(string message) {
   return View("SomeView", message);
}

접근 방식에는 몇 가지 장단점이 있습니다. 이러한 종류의 오류 처리에서 루핑에 매우주의하십시오. 다른 것은 asp.net 파이프 라인을 통해 404를 처리하기 때문에 모든 적중에 대한 세션 개체를 생성한다는 것입니다. 이는 많이 사용되는 시스템의 경우 문제 (성능)가 될 수 있습니다.


"루핑에주의하세요"라고 말할 때 정확히 무슨 뜻입니까? 이러한 유형의 오류 리디렉션을 처리하는 더 좋은 방법이 있습니까 (많이 사용되는 시스템이라고 가정)?
aherrick

4
루핑이란 오류 페이지에 오류가있을 때 오류 페이지로 계속 리디렉션된다는 것을 의미합니다 (예를 들어, 데이터베이스에 오류를 기록하고 다운되었습니다).
andrecarlucci

125
오류에 대한 리디렉션은 웹 아키텍처에 위배됩니다. 서버가 올바른 HTTP 상태 코드에 응답 할 때 URI는 동일하게 유지되어 클라이언트가 오류의 정확한 컨텍스트를 알 수 있습니다. HandleErrorAttribute.OnException 또는 Controller.OnException을 구현하는 것이 더 나은 솔루션입니다. 실패하면 Global.asax에서 Server.Transfer ( "~ / Error")를 수행합니다.
Asbjørn Ulsberg

1
@Chris, 허용되지만 모범 사례는 아닙니다. 특히 HTTP 200 상태 코드와 함께 제공되는 리소스 파일로 리디렉션되는 경우가 많기 때문에 클라이언트는 모든 것이 정상이라고 믿게됩니다.
Asbjørn Ulsberg 2013

1
서버에서이 작업을 수행하려면 web.config에 <httpErrors errorMode = "Detailed"/>를 추가해야했습니다.
Jeroen K

28

초기 질문 "오류 컨트롤러에 routedata를 올바르게 전달하는 방법"에 대답하려면 다음을 수행하십시오.

IController errorController = new ErrorController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));

그런 다음 ErrorController 클래스에서 다음과 같은 함수를 구현하십시오.

[AcceptVerbs(HttpVerbs.Get)]
public ViewResult Error(Exception exception)
{
    return View("Error", exception);
}

이것은 예외를 뷰로 푸시합니다. 보기 페이지는 다음과 같이 선언되어야합니다.

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<System.Exception>" %>

그리고 오류를 표시하는 코드 :

<% if(Model != null) { %>  <p><b>Detailed error:</b><br />  <span class="error"><%= Helpers.General.GetErrorMessage((Exception)Model, false) %></span></p> <% } %>

다음은 예외 트리에서 모든 예외 메시지를 수집하는 함수입니다.

    public static string GetErrorMessage(Exception ex, bool includeStackTrace)
    {
        StringBuilder msg = new StringBuilder();
        BuildErrorMessage(ex, ref msg);
        if (includeStackTrace)
        {
            msg.Append("\n");
            msg.Append(ex.StackTrace);
        }
        return msg.ToString();
    }

    private static void BuildErrorMessage(Exception ex, ref StringBuilder msg)
    {
        if (ex != null)
        {
            msg.Append(ex.Message);
            msg.Append("\n");
            if (ex.InnerException != null)
            {
                BuildErrorMessage(ex.InnerException, ref msg);
            }
        }
    }

9

Lion_cl에서 언급 한 ajax 문제에 대한 해결책을 찾았습니다.

global.asax :

protected void Application_Error()
    {           
        if (HttpContext.Current.Request.IsAjaxRequest())
        {
            HttpContext ctx = HttpContext.Current;
            ctx.Response.Clear();
            RequestContext rc = ((MvcHandler)ctx.CurrentHandler).RequestContext;
            rc.RouteData.Values["action"] = "AjaxGlobalError";

            // TODO: distinguish between 404 and other errors if needed
            rc.RouteData.Values["newActionName"] = "WrongRequest";

            rc.RouteData.Values["controller"] = "ErrorPages";
            IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
            IController controller = factory.CreateController(rc, "ErrorPages");
            controller.Execute(rc);
            ctx.Server.ClearError();
        }
    }

ErrorPagesController

public ActionResult AjaxGlobalError(string newActionName)
    {
        return new AjaxRedirectResult(Url.Action(newActionName), this.ControllerContext);
    }

AjaxRedirectResult

public class AjaxRedirectResult : RedirectResult
{
    public AjaxRedirectResult(string url, ControllerContext controllerContext)
        : base(url)
    {
        ExecuteResult(controllerContext);
    }

    public override void ExecuteResult(ControllerContext context)
    {
        if (context.RequestContext.HttpContext.Request.IsAjaxRequest())
        {
            JavaScriptResult result = new JavaScriptResult()
            {
                Script = "try{history.pushState(null,null,window.location.href);}catch(err){}window.location.replace('" + UrlHelper.GenerateContentUrl(this.Url, context.HttpContext) + "');"
            };

            result.ExecuteResult(context);
        }
        else
        {
            base.ExecuteResult(context);
        }
    }
}

AjaxRequestExtension

public static class AjaxRequestExtension
{
    public static bool IsAjaxRequest(this HttpRequest request)
    {
        return (request.Headers["X-Requested-With"] != null && request.Headers["X-Requested-With"] == "XMLHttpRequest");
    }
}

이것을 구현하는 동안 다음 오류가 발생했습니다. 'System.Web.HttpRequest'에 'IsAjaxRequest'에 대한 정의가 포함되어 있지 않습니다. 이 문서는 해결책이 stackoverflow.com/questions/14629304/...
줄리안 Dormon

8

나는 전에 MVC 앱에서 전역 오류 처리 루틴을 중앙 집중화한다는 아이디어에 어려움을 겪었습니다. 나는이 는 ASP.NET 포럼에 게시물을 .

기본적으로 오류 컨트롤러, [HandlerError]속성 장식 또는 customErrorsweb.config 의 노드 조작없이 global.asax의 모든 애플리케이션 오류를 처리합니다 .


6

MVC에서 오류를 처리하는 더 좋은 방법은 컨트롤러 또는 작업에 HandleError 특성을 적용하고 Shared / Error.aspx 파일을 업데이트하여 원하는 작업을 수행하는 것입니다. 해당 페이지의 Model 개체에는 Exception 속성과 ControllerName 및 ActionName이 포함됩니다.


1
그러면 404오류를 어떻게 처리 하시겠습니까? 지정된 컨트롤러 / 액션이 없기 때문에?
Dementic

허용되는 답변에는 404가 포함됩니다. 이 접근 방식은 500 개의 오류에만 유용합니다.
Brian

아마도 당신은 그것을 당신의 대답으로 편집해야 할 것입니다. Perhaps a better way of handling errors500만이 아닌 모든 오류와 거의 비슷하게 들립니다.
Dementic

4

Ajax 요청에 문제가있는 Application_Error. Ajax가 호출 한 Action에서 오류가 처리되면 결과 컨테이너 내부에 오류보기가 표시됩니다.


4

이것은 MVC ( https://stackoverflow.com/a/9461386/5869805 )에 가장 좋은 방법이 아닐 수 있습니다.

다음은 Application_Error에서 뷰를 렌더링하고 http 응답에 쓰는 방법입니다. 리디렉션을 사용할 필요가 없습니다. 이렇게하면 서버에 대한 두 번째 요청이 방지되므로 브라우저 주소 표시 줄의 링크가 동일하게 유지됩니다. 이것은 좋거나 나쁠 수 있으며 원하는 것에 따라 다릅니다.

Global.asax.cs

protected void Application_Error()
{
    var exception = Server.GetLastError();
    // TODO do whatever you want with exception, such as logging, set errorMessage, etc.
    var errorMessage = "SOME FRIENDLY MESSAGE";

    // TODO: UPDATE BELOW FOUR PARAMETERS ACCORDING TO YOUR ERROR HANDLING ACTION
    var errorArea = "AREA";
    var errorController = "CONTROLLER";
    var errorAction = "ACTION";
    var pathToViewFile = $"~/Areas/{errorArea}/Views/{errorController}/{errorAction}.cshtml"; // THIS SHOULD BE THE PATH IN FILESYSTEM RELATIVE TO WHERE YOUR CSPROJ FILE IS!

    var requestControllerName = Convert.ToString(HttpContext.Current.Request.RequestContext?.RouteData?.Values["controller"]);
    var requestActionName = Convert.ToString(HttpContext.Current.Request.RequestContext?.RouteData?.Values["action"]);

    var controller = new BaseController(); // REPLACE THIS WITH YOUR BASE CONTROLLER CLASS
    var routeData = new RouteData { DataTokens = { { "area", errorArea } }, Values = { { "controller", errorController }, {"action", errorAction} } };
    var controllerContext = new ControllerContext(new HttpContextWrapper(HttpContext.Current), routeData, controller);
    controller.ControllerContext = controllerContext;

    var sw = new StringWriter();
    var razorView = new RazorView(controller.ControllerContext, pathToViewFile, "", false, null);
    var model = new ViewDataDictionary(new HandleErrorInfo(exception, requestControllerName, requestActionName));
    var viewContext = new ViewContext(controller.ControllerContext, razorView, model, new TempDataDictionary(), sw);
    viewContext.ViewBag.ErrorMessage = errorMessage;
    //TODO: add to ViewBag what you need
    razorView.Render(viewContext, sw);
    HttpContext.Current.Response.Write(sw);
    Server.ClearError();
    HttpContext.Current.Response.End(); // No more processing needed (ex: by default controller/action routing), flush the response out and raise EndRequest event.
}

전망

@model HandleErrorInfo
@{
    ViewBag.Title = "Error";
    // TODO: SET YOUR LAYOUT
}
<div class="">
    ViewBag.ErrorMessage
</div>
@if(Model != null && HttpContext.Current.IsDebuggingEnabled)
{
    <div class="" style="background:khaki">
        <p>
            <b>Exception:</b> @Model.Exception.Message <br/>
            <b>Controller:</b> @Model.ControllerName <br/>
            <b>Action:</b> @Model.ActionName <br/>
        </p>
        <div>
            <pre>
                @Model.Exception.StackTrace
            </pre>
        </div>
    </div>
}

이것은 IMO가 가장 좋은 방법입니다. 내가 찾던 바로 그것.
Steve Harris

@SteveHarris가 도움이되어 기쁩니다! :)
burkay

3

Brian,이 접근 방식은 Ajax가 아닌 요청에 적합하지만 Lion_cl이 언급했듯이 Ajax 호출 중에 오류가 발생하면 Share / Error.aspx보기 (또는 사용자 지정 오류 페이지보기)가 Ajax 호출자에게 반환됩니다. -사용자는 오류 페이지로 리디렉션되지 않습니다.


0

경로 페이지에서 리디렉션하려면 다음 코드를 사용하십시오. exception.Message instide of exception을 사용하십시오. Coz 예외 쿼리 문자열이 쿼리 문자열 길이를 확장하면 오류가 발생합니다.

routeData.Values.Add("error", exception.Message);
// clear error on server
Server.ClearError();
Response.RedirectToRoute(routeData.Values);

-1

이 오류 처리 방법에 문제가 있습니다. web.config의 경우 :

<customErrors mode="On"/>

오류 처리기가 Error.shtml보기를 검색하고 예외 후에 만 ​​Application_Error global.asax에 대한 제어 흐름 단계

System.InvalidOperationException : '오류'보기 또는 해당 마스터가 없거나 검색된 위치를 지원하는보기 엔진이 없습니다. 다음 위치가 검색되었습니다. ~ / Views / home / Error.aspx ~ / Views / home / Error.ascx ~ / Views / Shared / Error.aspx ~ / Views / Shared / Error.ascx ~ / Views / home / Error. cshtml ~ / Views / home / Error.vbhtml ~ / Views / Shared / Error.cshtml ~ / Views / Shared / Error.vbhtml at System.Web.Mvc.ViewResult.FindView (ControllerContext context) ........ ............

그래서

 Exception exception = Server.GetLastError();
  Response.Clear();
  HttpException httpException = exception as HttpException;

httpException 그것은 그런 오해의 소지 :( "에"= customErrors에 모드는 null 항상 <customErrors mode="Off"/>또는 <customErrors mode="RemoteOnly"/>사용자가 볼 customErrors에 HTML을, 그리고 customErrors에 모드 =이 코드를 잘못 너무 "에"


이 코드의 또 다른 문제는

Response.Redirect(String.Format("~/Error/{0}/?message={1}", action, exception.Message));

실제 오류 코드 (402,403 등) 대신 코드 302가있는 페이지 반환

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.