ASP.NET MVC Custom Error Handling Application Error Global.asax?
j'ai un code de base pour déterminer les erreurs dans mon application MVC. Actuellement dans mon projet j'ai un contrôleur appelé Error
avec les méthodes d'action HTTPError404()
, HTTPError500()
, et General()
. Ils acceptent tous le paramètre error
. Utiliser ou modifier le code ci-dessous.
Quelle est la meilleure/bonne façon de transmettre les données au contrôleur des erreurs pour le traitement? Je voudrais avoir une solution robuste que possible.
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?
}
}
10 réponses
au lieu de créer une nouvelle route pour cela, vous pouvez simplement rediriger vers votre controller/action et passer l'information via querystring. Par exemple:
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));
}
alors votre contrôleur recevra ce que vous voulez:
// GET: /Error/HttpError404
public ActionResult HttpError404(string message) {
return View("SomeView", message);
}
Il y a des compromis avec votre approche. Soyez très prudent avec la boucle dans ce genre de manipulation d'erreur. L'autre chose est que puisque vous traversez le asp.net pipeline pour manipuler un 404, vous allez créer un objet session pour tous ces résultats. Cela peut être un problème (performance) pour les systèmes fortement utilisés.
pour répondre à la question initiale" comment passer correctement routedata au contrôleur d'erreur?":
IController errorController = new ErrorController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
alors dans votre classe ErrorController, implémentez une fonction comme celle-ci:
[AcceptVerbs(HttpVerbs.Get)]
public ViewResult Error(Exception exception)
{
return View("Error", exception);
}
cela pousse l'exception dans la vue. La page de vue doit être déclarée comme suit:
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<System.Exception>" %>
et le code pour afficher l'erreur:
<% if(Model != null) { %> <p><b>Detailed error:</b><br /> <span class="error"><%= Helpers.General.GetErrorMessage((Exception)Model, false) %></span></p> <% } %>
Voici la fonction qui rassemble le tout messages d'exception de l'arborescence des exceptions:
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);
}
}
}
j'ai trouvé une solution pour la question ajax noté par 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");
}
}
je me suis déjà battu avec l'idée de centraliser une routine globale de gestion des erreurs dans une application MVC. J'ai un post sur le ASP.NET forums .
il traite essentiellement toutes vos erreurs d'application dans le global.asax sans avoir besoin d'un contrôleur d'erreur, décorer avec l'attribut [HandlerError]
, ou jouer avec le noeud customErrors
dans le web.config.
peut-être qu'une meilleure façon de gérer les erreurs dans MVC est d'appliquer L'attribut HandleError à votre controller ou action et de mettre à jour le Shared/Error.fichier aspx faire ce que vous voulez. L'objet Model sur cette page inclut une propriété D'Exception ainsi que ControllerName et Actioname.
Application_Error ayant des problèmes avec les requêtes Ajax. Si l'erreur traitée dans L'Action qui a appelé par Ajax-il affichera votre vue D'erreur à l'intérieur du conteneur résultant.
Brian, Cette approche fonctionne très bien pour les requêtes non-Ajax, mais comme Lion_cl l'a indiqué, si vous avez une erreur lors d'un appel Ajax, votre part/erreur.aspx view (ou votre page d'erreur personnalisée) sera retourné à L'appelant Ajax--l'utilisateur ne sera pas redirigé vers la page d'erreur.
ce n'est peut-être pas la meilleure façon pour MVC ( https://stackoverflow.com/a/9461386/5869805 )
ci-dessous est la façon de rendre une vue dans Application_Error et de l'écrire dans la réponse http. Vous n'avez pas besoin d'utiliser la redirection. Cela empêchera une deuxième requête au serveur, de sorte que le lien dans la barre d'adresse du navigateur restera le même. Cela peut être bon ou mauvais, cela dépend de ce que vous voulez.
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.
}
Vue
@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>
}
utilisez le code suivant pour rediriger sur la page route. L'utilisation d'exception.Message instide d'exception. La chaîne de requête d'exception Coz donne l'erreur si elle étend la longueur de querystring.
routeData.Values.Add("error", exception.Message);
// clear error on server
Server.ClearError();
Response.RedirectToRoute(routeData.Values);
j'ai un problème avec cette approche de gestion des erreurs: Dans le cas du web.config:
<customErrors mode="On"/>
le gestionnaire d'erreurs recherche L'erreur de vue.shtml et l'étape du flux de contrôle vers Application_Error global.asax seulement après exception
Système.InvalidOperationException: la vue 'Error' ou son maître était pas trouvé ou pas de moteur de vue supporte les emplacements recherchés. Le emplacements suivants ont été recherchés: ~/Views/home/Erreur.aspx ~/Views/home/Erreur.ascx ~ / Views/Shared / Error.aspx ~ / Views / Shared / Error.ascx ~/Views/home/Erreur.cshtml ~/Views/home/Erreur.vbhtml ~/Views/Shared/Erreur.cshtml ~ / Views / Shared / Error.vbhtml à Système.Web.Mvc.ViewResult.FindView (controllercontext context)) ....................
Donc
Exception exception = Server.GetLastError();
Response.Clear();
HttpException httpException = exception as HttpException;
httpException est toujours null alors
customErrors mode= " On"
:(
Il est trompeur
Puis <customErrors mode="Off"/>
ou <customErrors mode="RemoteOnly"/>
les utilisateurs voir customErrors html,
Alors customErrors mode=" On "ce code est erroné aussi
un autre problème de ce code que
Response.Redirect(String.Format("~/Error/{0}/?message={1}", action, exception.Message));
page de retour avec le code 302 à la place du code d'erreur réel (402,403 etc)