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 2009

4
ループとは、エラーページでエラーが発生した場合、エラーページに何度もリダイレクトされることを意味します(たとえば、データベースにエラーを記録し、エラーが発生した場合)。
andrecarlucci

125
エラーのリダイレクトは、Webのアーキテクチャに反します。サーバーが正しいHTTPステータスコードに応答するとき、URIは同じままである必要があります。これにより、クライアントはエラーの正確なコンテキストを知ることができます。HandleErrorAttribute.OnExceptionまたはController.OnExceptionを実装する方が優れたソリューションです。これらが失敗した場合は、Global.asaxでServer.Transfer( "〜/ Error")を実行します。
アスビョルンUlsberg

1
@Chris、それは許容できるが、ベストプラクティスではない。特に、HTTP 200ステータスコードで提供されるリソースファイルにリダイレクトされることがよくあるため、クライアントはすべてが正常に行われたと信じるようになります。
アスビョルンUlsberg

1
サーバーでこれを機能させるには、<httpErrors errorMode = "Detailed" />をweb.configに追加する必要がありました。
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

8

以前、グローバルエラー処理ルーチンをMVCアプリに集中させるという考えに苦労しました。私が持っているASP.NETフォーラムのポストを

基本的には、エラーコントローラーを必要とせず、global.asax内のすべてのアプリケーションエラーを処理し、[HandlerError]属性で装飾したりcustomErrors、web.configのノードを操作したりします。


6

おそらく、MVCでエラーを処理するより良い方法は、HandleError属性をコントローラーまたはアクションに適用し、Shared / Error.aspxファイルを更新して必要なことを行うことです。そのページのモデルオブジェクトには、ExceptionプロパティとControllerNameおよびActionNameが含まれています。


1
404次に、エラーをどのように処理しますか?そのために指定されたコントローラ/アクションがないので?
認知症の

受け入れられた回答には404が含まれます。このアプローチは、500エラーの場合にのみ役立ちます。
ブライアン

多分あなたはそれをあなたの答えに編集するべきです。Perhaps a better way of handling errors500だけではなく、All Errorsによく似ています。
認知症の

4

Ajaxリクエストに問題があるApplication_Error。Ajaxによって呼び出されたアクションでエラーが処理された場合-結果のコンテナー内にエラービューが表示されます。


4

これはMVCにとって最良の方法ではない可能性があります(https://stackoverflow.com/a/9461386/5869805

以下は、Application_Errorでビューをレンダリングし、http応答に書き込む方法です。リダイレクトを使用する必要はありません。これにより、サーバーへの2番目の要求が防止されるため、ブラウザーのアドレスバーのリンクは同じままになります。これは良いか悪いか、それはあなたが何をしたいかに依存します。

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の最良の方法です。まさに私が探していたもの。
スティーブハリス

@SteveHarris助けてくれて嬉しいです!:)
burkay

3

ブライアン、このアプローチは非Ajaxリクエストに適していますが、Lion_clが述べたように、Ajax呼び出し中にエラーが発生した場合、Share / Error.aspxビュー(またはカスタムエラーページビュー)がAjax呼び出し元に返されます。 -ユーザーはエラーページにリダイレクトされません。


0

ルートページでリダイレクトするには、次のコードを使用します。exception.Message intExceptionを使用してください。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は常にnullで、次にcustomErrors mode = "On" :(それは誤解を招くものである<customErrors mode="Off"/><customErrors mode="RemoteOnly"/>、またはユーザーにcustomErrors htmlが表示され、次にcustomErrors mode = "On"と表示されます。このコードも間違っています


このコードの別の問題は

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

実際のエラーコード(402、403など)ではなく、コード302のページを返す

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.