ASP.NET MVC Custom Error Handling Application_Error Global.asax?

108

Saya memiliki beberapa kode dasar untuk menentukan kesalahan dalam aplikasi MVC saya. Saat ini di proyek saya saya memiliki kontroler yang disebut Errordengan metode tindakan HTTPError404(), HTTPError500()dan General(). Mereka semua menerima parameter string error. Menggunakan atau memodifikasi kode di bawah ini. Apa cara terbaik / tepat untuk meneruskan data ke Pengontrol kesalahan untuk diproses? Saya ingin mendapatkan solusi yang sekuat mungkin.

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?
    }
}
aherrick
sumber

Jawaban:

104

Alih-alih membuat rute baru untuk itu, Anda bisa mengalihkan ke controller / action Anda dan meneruskan informasi melalui querystring. Misalnya:

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

Kemudian pengontrol Anda akan menerima apa pun yang Anda inginkan:

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

Ada beberapa pengorbanan dengan pendekatan Anda. Berhati-hatilah dengan perulangan dalam penanganan kesalahan semacam ini. Hal lainnya adalah karena Anda akan melalui pipeline asp.net untuk menangani 404, Anda akan membuat objek sesi untuk semua klik tersebut. Ini bisa menjadi masalah (kinerja) untuk sistem yang banyak digunakan.

andrecarlucci
sumber
Ketika Anda mengatakan "hati-hati terhadap perulangan" apa sebenarnya yang Anda maksud? Apakah ada cara yang lebih baik untuk menangani jenis pengalihan kesalahan ini (dengan asumsi itu adalah sistem yang banyak digunakan)?
aherrick
4
Dengan perulangan maksud saya ketika Anda memiliki kesalahan di halaman kesalahan Anda, maka Anda akan diarahkan ke halaman kesalahan Anda lagi dan lagi ... (misalnya, Anda ingin mencatat kesalahan Anda dalam database dan itu turun).
andrecarlucci
125
Mengarahkan pada kesalahan bertentangan dengan arsitektur web. URI harus tetap sama saat server merespons kode status HTTP yang benar sehingga klien mengetahui konteks kegagalan yang sebenarnya. Menerapkan HandleErrorAttribute.OnException atau Controller.OnException adalah solusi yang lebih baik. Dan jika gagal, lakukan Server.Transfer ("~ / Error") di Global.asax.
Asbjørn Ulsberg
1
@ Chris, Itu bisa diterima, tapi bukan praktik terbaik. Terutama karena sering dialihkan ke file sumber daya yang disajikan dengan kode status HTTP 200, yang membuat klien percaya bahwa semuanya berjalan baik-baik saja.
Asbjørn Ulsberg
1
Saya harus menambahkan <httpErrors errorMode = "Detailed" /> ke web.config agar ini berfungsi di server.
Jeroen K
28

Untuk menjawab pertanyaan awal "bagaimana cara mengirimkan routedata dengan benar ke pengontrol kesalahan?":

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

Kemudian di kelas ErrorController Anda, terapkan fungsi seperti ini:

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

Ini mendorong pengecualian ke View. Halaman tampilan harus dinyatakan sebagai berikut:

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

Dan kode untuk menampilkan kesalahan:

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

Berikut adalah fungsi yang mengumpulkan semua pesan pengecualian dari pohon pengecualian:

    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);
            }
        }
    }
Tim Cooper
sumber
9

Saya menemukan solusi untuk masalah ajax yang dicatat oleh 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");
    }
}
Jozef Krchňavý
sumber
Saat menerapkan ini saya mendapat kesalahan berikut: 'System.Web.HttpRequest' tidak berisi definisi untuk 'IsAjaxRequest'. Artikel ini memiliki solusi: stackoverflow.com/questions/14629304/…
Julian Dormon
8

Saya berjuang dengan gagasan untuk memusatkan rutinitas penanganan kesalahan global di aplikasi MVC sebelumnya. Saya memiliki posting di forum ASP.NET .

Ini pada dasarnya menangani semua kesalahan aplikasi Anda di global.asax tanpa memerlukan pengontrol kesalahan, mendekorasi dengan [HandlerError]atribut, atau mengutak-atik customErrorsnode di web.config.

Jack Hsu
sumber
6

Mungkin cara yang lebih baik untuk menangani kesalahan di MVC adalah dengan menerapkan atribut HandleError ke controller atau tindakan Anda dan memperbarui file Shared / Error.aspx untuk melakukan apa yang Anda inginkan. Objek Model di halaman itu menyertakan properti Exception serta ControllerName dan ActionName.

Brian
sumber
1
Bagaimana Anda akan menangani 404kesalahan? karena tidak ada pengontrol / tindakan yang ditujukan untuk itu?
Dementik
Jawaban yang diterima mencakup 404. Pendekatan ini hanya berguna untuk 500 kesalahan.
Brian
Mungkin Anda harus mengeditnya menjadi jawaban Anda. Perhaps a better way of handling errorsterdengar seperti Semua Kesalahan dan bukan hanya 500.
Dementik
4

Application_Error mengalami masalah dengan permintaan Ajax. Jika kesalahan ditangani dalam Tindakan yang disebut oleh Ajax - itu akan menampilkan Tampilan Kesalahan Anda di dalam wadah yang dihasilkan.

Victor Gelmutdinov
sumber
4

Ini mungkin bukan cara terbaik untuk MVC ( https://stackoverflow.com/a/9461386/5869805 )

Di bawah ini adalah cara Anda merender tampilan di Application_Error dan menuliskannya ke respons http. Anda tidak perlu menggunakan pengalihan. Ini akan mencegah permintaan kedua ke server, jadi tautan di bilah alamat browser akan tetap sama. Ini mungkin baik atau buruk, itu tergantung pada apa yang Anda inginkan.

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.
}

Melihat

@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>
}
burkay
sumber
Ini adalah cara terbaik IMO. Persis apa yang saya cari.
Steve Harris
@TeveHris senang itu membantu! :)
burkay
3

Brian, Pendekatan ini bekerja sangat baik untuk permintaan non-Ajax, tetapi seperti yang dinyatakan Lion_cl, jika Anda mengalami kesalahan selama panggilan Ajax, tampilan Share / Error.aspx Anda (atau tampilan halaman kesalahan kustom Anda) akan dikembalikan ke pemanggil Ajax- -pengguna TIDAK akan diarahkan ke halaman kesalahan.

tak terbantahkan
sumber
0

Gunakan kode Mengikuti untuk mengarahkan pada halaman rute. Gunakan pengecualian. Pesan dalam pengecualian. String kueri pengecualian Coz memberikan kesalahan jika memperpanjang panjang string kueri.

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

Saya punya masalah dengan pendekatan penanganan kesalahan ini: Dalam kasus web.config:

<customErrors mode="On"/>

Penangan kesalahan sedang mencari tampilan Error.shtml dan aliran kontrol masuk ke Application_Error global.asax hanya setelah pengecualian

System.InvalidOperationException: Tampilan 'Error' atau masternya tidak ditemukan atau tidak ada mesin tampilan yang mendukung lokasi yang dicari. Lokasi berikut dicari: ~ / 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 di System.Web.Mvc.ViewResult.FindView (konteks ControllerContext) ........ ............

Begitu

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

httpException selalu null lalu customErrors mode = "On" :( Menyesatkan Then <customErrors mode="Off"/>atau<customErrors mode="RemoteOnly"/> pengguna melihat customErrors html, Lalu customErrors mode = "On" kode ini juga salah


Masalah lain dari kode ini yaitu

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

Kembalikan halaman dengan kode 302 sebagai gantinya kode kesalahan nyata (402.403 dll)

Александр Шмыков
sumber