Ich habe etwas Basiscode, um Fehler in meiner MVC-Anwendung zu ermitteln. Derzeit habe ich in meinem Projekt einen Controller namens Error
mit den Aktionsmethoden HTTPError404()
, HTTPError500()
und General()
. Sie akzeptieren alle einen String-Parameter error
. Verwenden oder Ändern des folgenden Codes ... Was ist der beste/richtige Weg, um die Daten zur Verarbeitung an den Error-Controller zu übergeben? Ich hätte gerne eine möglichst robuste Lösung.
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?
}
}
Anstatt dafür eine neue Route anzulegen, können Sie einfach zu Ihrem Controller/Ihrer Aktion umleiten und die Informationen per Abfragestring weiterleiten. Zum Beispiel:
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));
}
Dann erhält Ihr Controller, was Sie wollen:
// GET: /Error/HttpError404
public ActionResult HttpError404(string message) {
return View("SomeView", message);
}
Es gibt einige Kompromisse bei Ihrem Ansatz. Seien Sie sehr vorsichtig beim Schleifen dieser Art der Fehlerbehandlung. Eine andere Sache ist, dass Sie ein Session-Objekt für all diese Treffer erstellen, da Sie durch die asp.net-Pipeline gehen, um einen 404-Prozessor zu verarbeiten. Dies kann bei stark genutzten Systemen ein Problem (Leistung) sein.
Um die erste Frage zu beantworten, "Wie werden Routedaten korrekt an den Fehlercontroller übergeben?"
IController errorController = new ErrorController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
Implementieren Sie dann in Ihrer ErrorController-Klasse eine Funktion wie folgt:
[AcceptVerbs(HttpVerbs.Get)]
public ViewResult Error(Exception exception)
{
return View("Error", exception);
}
Dadurch wird die Ausnahme in die Ansicht verschoben. Die Ansichtsseite sollte wie folgt deklariert werden:
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<System.Exception>" %>
Und den Code, um den Fehler anzuzeigen:
<% if(Model != null) { %> <p><b>Detailed error:</b><br /> <span class="error"><%= Helpers.General.GetErrorMessage((Exception)Model, false) %></span></p> <% } %>
Hier ist die Funktion, die alle Ausnahmemeldungen aus dem Ausnahmebaum erfasst:
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);
}
}
}
Ich habe eine Lösung für Ajax-Problem gefunden, die von Lion_cl notiert wurde.
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");
}
}
Ich hatte vorher Probleme mit der Idee, eine globale Fehlerbehandlungsroutine in einer MVC-App zu zentralisieren. Ich habe einen Beitrag in den ASP.NET-Foren .
Es behandelt im Wesentlichen alle Anwendungsfehler in der Datei global.asax, ohne dass ein Fehler-Controller erforderlich ist, der mit dem [HandlerError]
-Attribut verziert wird oder mit dem customErrors
-Knoten in der web.config-Datei getüftelt wird.
Eine bessere Möglichkeit, Fehler in MVC zu behandeln, besteht möglicherweise darin, das HandleError-Attribut auf Ihren Controller oder Ihre Aktion anzuwenden und die Shared/Error.aspx-Datei zu aktualisieren, um das zu tun, was Sie möchten. Das Model-Objekt auf dieser Seite enthält eine Exception-Eigenschaft sowie ControllerName und ActionName.
Application_Error hat ein Problem mit Ajax-Anforderungen. Wenn ein Fehler in Aktion behandelt wurde, der von Ajax aufgerufen wurde, wird die Fehleransicht im resultierenden Container angezeigt.
Brian,. Diese Vorgehensweise eignet sich hervorragend für Anfragen, die nicht von Ajax stammen. Wie Lion_cl jedoch angibt, wird bei einem Ajax-Aufruf eine Share/Error.aspx-Ansicht (oder Ihre benutzerdefinierte Fehlerseitenansicht) an die Ajax-Anrufer - Der Benutzer wird NICHT zur Fehlerseite weitergeleitet.
Dies ist möglicherweise nicht der beste Weg für MVC ( https://stackoverflow.com/a/9461386/5869805 )
Im Folgenden wird beschrieben, wie Sie eine Ansicht in Application_Error rendern und in die http-Antwort schreiben. Sie müssen keine Weiterleitung verwenden. Dadurch wird eine zweite Anfrage an den Server verhindert, sodass der Link in der Adressleiste des Browsers gleich bleibt. Das kann gut oder schlecht sein, es hängt davon ab, was Sie wollen.
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.
}
Aussicht
@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>
}
Verwenden Sie folgenden Code für die Umleitung auf der Routenseite . Exception verwenden. Die Abfragezeichenfolge für die Coz-Ausnahmebedingung gibt einen Fehler aus, wenn die Abfragestringlänge verlängert wird.
routeData.Values.Add("error", exception.Message);
// clear error on server
Server.ClearError();
Response.RedirectToRoute(routeData.Values);