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 คุณจะสร้างวัตถุเซสชันสำหรับ Hit เหล่านั้นทั้งหมด นี่อาจเป็นปัญหา (ประสิทธิภาพ) สำหรับระบบที่ใช้งานหนัก


เมื่อคุณพูดว่า "ระวังการวนซ้ำ" คุณหมายถึงอะไร? มีวิธีที่ดีกว่าในการจัดการการเปลี่ยนเส้นทางข้อผิดพลาดประเภทนี้หรือไม่ (สมมติว่าเป็นระบบที่ใช้งานหนัก)
aherrick

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

125
การเปลี่ยนเส้นทางเมื่อเกิดข้อผิดพลาดจะขัดต่อสถาปัตยกรรมของเว็บ URI ควรยังคงเหมือนเดิมเมื่อเซิร์ฟเวอร์ตอบสนองรหัสสถานะ HTTP ที่ถูกต้องเพื่อให้ไคลเอนต์ทราบบริบทที่แน่นอนของความล้มเหลว การใช้ HandleErrorAttribute OnException หรือ Controller OnException เป็นทางออกที่ดีกว่า และหากล้มเหลวให้ทำ Server.Transfer ("~ / Error") ใน Global.asax
Asbjørn Ulsberg

1
@ คริสยอมรับได้ แต่ไม่ใช่แนวทางปฏิบัติที่ดีที่สุด โดยเฉพาะอย่างยิ่งเนื่องจากมักจะเปลี่ยนเส้นทางไปยังไฟล์ทรัพยากรที่ให้บริการด้วยรหัสสถานะ HTTP 200 ซึ่งทำให้ลูกค้าเชื่อว่าทุกอย่างเรียบร้อยดี
Asbjørn Ulsberg

1
ฉันต้องเพิ่ม <httpErrors errorMode = "รายละเอียด" /> ลงใน web.config เพื่อให้สามารถใช้งานได้บนเซิร์ฟเวอร์
Jeroen K

28

หากต้องการตอบคำถามเริ่มต้น "วิธีส่งข้อมูลเส้นทางไปยังตัวควบคุมข้อผิดพลาดอย่างถูกต้อง":

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

ฉันพบวิธีแก้ปัญหา ajax ที่ Lion_cl ระบุไว้

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/…
Julian Dormon

8

ฉันต่อสู้กับแนวคิดที่จะรวมรูทีนการจัดการข้อผิดพลาดทั่วโลกไว้ที่ส่วนกลางในแอป MVC มาก่อน ฉันมีการโพสต์ในฟอรั่ ASP.NET

โดยทั่วไปจะจัดการข้อผิดพลาดของแอปพลิเคชันทั้งหมดของคุณใน global.asax โดยไม่ต้องใช้ตัวควบคุมข้อผิดพลาดตกแต่งด้วย[HandlerError]แอตทริบิวต์หรือเล่นซอกับcustomErrorsโหนดใน web.config


6

บางทีวิธีที่ดีกว่าในการจัดการข้อผิดพลาดใน MVC คือการใช้แอตทริบิวต์ HandleError กับคอนโทรลเลอร์หรือการดำเนินการของคุณและอัปเดตไฟล์ Shared / Error.aspx เพื่อทำสิ่งที่คุณต้องการ วัตถุ Model ในเพจนั้นมีคุณสมบัติ Exception เช่นเดียวกับ ControllerName และ ActionName


1
คุณจะจัดการกับ404ข้อผิดพลาดอย่างไร? เนื่องจากไม่มีตัวควบคุม / การกระทำที่กำหนดไว้สำหรับสิ่งนั้น?
Dementic

คำตอบที่ยอมรับ ได้แก่ 404 วิธีนี้มีประโยชน์สำหรับข้อผิดพลาด 500 ข้อเท่านั้น
Brian

บางทีคุณควรแก้ไขเป็นคำตอบของคุณ Perhaps a better way of handling errorsฟังดูเหมือนข้อผิดพลาดทั้งหมดไม่ใช่แค่ 500 เท่านั้น
Dementic

4

Application_Error มีปัญหากับคำขอ Ajax หากมีการจัดการข้อผิดพลาดใน Action ซึ่งเรียกโดย Ajax - จะแสดง Error View ของคุณภายในคอนเทนเนอร์ที่เป็นผลลัพธ์


4

นี่อาจไม่ใช่วิธีที่ดีที่สุดสำหรับ MVC ( https://stackoverflow.com/a/9461386/5869805 )

ด้านล่างนี้คือวิธีที่คุณแสดงมุมมองใน Application_Error และเขียนลงใน http response คุณไม่จำเป็นต้องใช้การเปลี่ยนเส้นทาง การดำเนินการนี้จะป้องกันไม่ให้มีการร้องขอครั้งที่สองไปยังเซิร์ฟเวอร์ดังนั้นลิงก์ในแถบที่อยู่ของเบราว์เซอร์จะยังคงเหมือนเดิม สิ่งนี้อาจดีหรือไม่ดีขึ้นอยู่กับสิ่งที่คุณต้องการ

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

ใช้รหัสต่อไปนี้สำหรับการเปลี่ยนเส้นทางในหน้าเส้นทาง ใช้ข้อยกเว้นข้อความในข้อยกเว้น สตริงเคียวรีข้อยกเว้น 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 ที่ System.Web.Mvc.ViewResult.FindView (บริบท ControllerContext) ........ ............

ดังนั้น

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

httpException เป็นโมฆะเสมอแล้วโหมด customErrors = "เปิด" :( มันทำให้เข้าใจผิดจากนั้น<customErrors mode="Off"/>หรือ<customErrors mode="RemoteOnly"/>ผู้ใช้เห็น customErrors html แล้วโหมด customErrors = "เปิด" รหัสนี้ก็ผิดด้วย


อีกปัญหาหนึ่งของรหัสนี้ที่

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

ย้อนกลับหน้าด้วยรหัส 302 แทนรหัสข้อผิดพลาดจริง (402,403 ฯลฯ )

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