ASP.NET MVC Özel Hata İşleme Application_Error Global.asax?


108

MVC uygulamamdaki hataları belirlemek için bazı temel kodum var. Şu anda Projemdeki adında bir denetleyiciniz Erroraksiyon yöntemlerle HTTPError404(), HTTPError500()ve General(). Hepsi bir dize parametresi kabul eder error. Aşağıdaki kodu kullanma veya değiştirme. Verileri işlenmek üzere Hata denetleyicisine iletmenin en iyi / doğru yolu nedir? Mümkün olduğunca sağlam bir çözüme sahip olmak istiyorum.

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

Yanıtlar:


104

Bunun için yeni bir yol oluşturmak yerine, denetleyicinize / eyleminize yeniden yönlendirebilir ve bilgileri sorgu dizesi aracılığıyla iletebilirsiniz. Örneğin:

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

Daha sonra kontrol cihazınız istediğinizi alacaktır:

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

Yaklaşımınızla ilgili bazı ödünler var. Bu tür bir hata işlemede döngü yaparken çok dikkatli olun. Diğer bir şey de, bir 404'ü işlemek için asp.net ardışık düzeninden geçtiğiniz için, tüm bu isabetler için bir oturum nesnesi oluşturacaksınız. Bu, yoğun olarak kullanılan sistemler için bir sorun (performans) olabilir.


"Dönmeye dikkat et" derken tam olarak ne demek istiyorsun? Bu tür bir hatayı yeniden yönlendirmenin daha iyi bir yolu var mı (yoğun olarak kullanılan bir sistem olduğu varsayılarak)?
aherrick

4
Döngü ile kastettiğim, hata sayfanızda bir hata olduğunda, o zaman hata sayfanıza tekrar tekrar yönlendirilirsiniz ... (örneğin, hatanızı bir veritabanına kaydetmek istersiniz ve başarısız olur).
andrecarlucci

125
Hatalarda yeniden yönlendirme, web mimarisine aykırıdır. İstemcinin hatanın tam içeriğini bilmesi için sunucu doğru HTTP durum kodunu yanıtladığında URI aynı kalmalıdır. HandleErrorAttribute.OnException veya Controller.OnException uygulamak daha iyi bir çözümdür. Ve bunlar başarısız olursa, Global.asax içinde bir Server.Transfer ("~ / Error") yapın.
Asbjørn Ulsberg

1
@Chris, Kabul edilebilir, ancak en iyi uygulama değil. Özellikle de sıklıkla HTTP 200 durum kodu ile sunulan bir kaynak dosyasına yönlendirildiği için, bu da müşteriyi her şeyin yolunda gittiğine inanmaya bırakıyor.
Asbjørn Ulsberg

1
Bunun sunucuda çalışması için web.config dosyasına <httpErrors errorMode = "Ayrıntılı" /> eklemem gerekiyordu.
Jeroen K

28

"Yönlendirilmiş veriler hata denetleyicisine nasıl düzgün bir şekilde iletilir?" Sorusunu yanıtlamak için:

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

Ardından, ErrorController sınıfınızda şuna benzer bir işlev uygulayın:

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

Bu, istisnayı Görünüm'e iter. Görünüm sayfası aşağıdaki şekilde beyan edilmelidir:

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

Ve hatayı gösteren kod:

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

İstisna ağacından tüm istisna mesajlarını toplayan işlev şu şekildedir:

    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 tarafından not edilen ajax sorunu için bir çözüm buldum.

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

Bunu uygularken aşağıdaki hatayı aldım: 'System.Web.HttpRequest', 'IsAjaxRequest' için bir tanım içermiyor. Bu makalenin bir çözümü var: stackoverflow.com/questions/14629304/…
Julian Dormon 01

8

Daha önce bir MVC uygulamasında küresel bir hata işleme rutini merkezileştirme fikriyle mücadele ettim. ASP.NET forumlarında bir gönderim var .

Temel olarak global.asax'taki tüm uygulama hatalarınızı bir hata denetleyicisine ihtiyaç duymadan, [HandlerError]öznitelikle süslemeden veya customErrorsweb.config'deki düğümle uğraşmadan halleder .


6

Belki de MVC'deki hataları işlemenin daha iyi bir yolu, HandleError özniteliğini denetleyicinize veya eyleminize uygulamak ve istediğinizi yapmak için Shared / Error.aspx dosyasını güncellemektir. Bu sayfadaki Model nesnesi, bir Exception özelliğinin yanı sıra ControllerName ve ActionName içerir.


1
404O halde bir hatayla nasıl başa çıkacaksınız ? bunun için belirlenmiş bir denetleyici / eylem olmadığından?
Dementic

Kabul edilen cevap 404'leri içerir. Bu yaklaşım yalnızca 500 hata için kullanışlıdır.
Brian

Belki de bunu cevabınızda düzenlemelisiniz. Perhaps a better way of handling errorsTüm Hatalar'a benziyor ve yalnızca 500'e benzemiyor.
Dementic

4

Application_Error Ajax istekleriyle ilgili sorun yaşıyor. Eylemde Ajax tarafından çağrılan hata ele alınırsa, ortaya çıkan konteynerin içinde Hata Görünümünüzü görüntüler.


4

Bu MVC için en iyi yol olmayabilir ( https://stackoverflow.com/a/9461386/5869805 )

Aşağıda Application_Error'da bir görünümü nasıl oluşturacağınız ve bunu http yanıtına nasıl yazacağınız anlatılmaktadır. Yönlendirme kullanmanıza gerek yoktur. Bu, sunucuya ikinci bir isteği önleyecektir, böylece tarayıcının adres çubuğundaki bağlantı aynı kalacaktır. Bu iyi veya kötü olabilir, ne istediğinize bağlı.

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

Görünüm

@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'nun en iyi yolu budur. Tam olarak aradığım şey.
Steve Harris

@SteveHarris yardımcı olduğuna sevindim! :)
burkay

3

Brian, Bu yaklaşım Ajax dışı istekler için harika çalışıyor, ancak Lion_cl'nin de belirttiği gibi, bir Ajax çağrısı sırasında bir hatayla karşılaşırsanız, Paylaş / Hata.aspx görünümünüz (veya özel hata sayfası görünümünüz) Ajax arayana geri dönecektir. -Kullanıcı hata sayfasına YÖNLENDİRİLMEYECEKTİR.


0

Yönlendirme sayfasında yeniden yönlendirme için Aşağıdaki kodu kullanın. İstisna kullanın İstisna durumu mesajı. Coz istisna sorgu dizesi, sorgu dizesinin uzunluğunu uzatırsa hata verir.

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

-1

Bu hata işleme yaklaşımıyla ilgili sorun yaşıyorum: web.config durumunda:

<customErrors mode="On"/>

Hata işleyici, Error.shtml görünümünü arıyor ve kontrol akışı yalnızca istisnadan sonra Application_Error global.asax'a adım atıyor

System.InvalidOperationException: 'Hata' görünümü veya yöneticisi bulunamadı veya hiçbir görünüm motoru aranan konumları desteklemiyor. Aşağıdaki konumlar arandı: ~ / 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) ........ ............

Yani

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

httpException her zaman null ve ardından customErrors mode = "On" :( Yanıltıcı O zaman <customErrors mode="Off"/>veya <customErrors mode="RemoteOnly"/>kullanıcılar customErrors html'yi görüyor, Sonra customErrors mode = "Açık" bu kod da yanlış


Bu kodun başka bir sorunu

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

Gerçek hata kodu yerine 302 kodlu dönüş sayfası (402,403 vb.)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.