c# - handling - Error de manejo personalizado de ASP.NET MVC Application_Error Global.asax?
onexception mvc (10)
Application_Error tiene problemas con las solicitudes de Ajax. Si el error se maneja en Acción, invocado por Ajax, mostrará su vista de error dentro del contenedor resultante.
Tengo un código básico para determinar los errores en mi aplicación MVC. Actualmente en mi proyecto tengo un controlador llamado Error
con los métodos de acción HTTPError404()
, HTTPError500()
y General()
. Todos ellos aceptan un error
parámetro de cadena. Usando o modificando el código a continuación. ¿Cuál es la mejor forma de pasar los datos al controlador de errores para su procesamiento? Me gustaría tener una solución robusta como sea posible.
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?
}
}
Brian, este enfoque funciona muy bien para solicitudes que no son de Ajax, pero como Lion_cl declaró, si tienes un error durante una llamada de Ajax, tu vista de Share / Error.aspx (o tu vista de página de error personalizada) será devuelta al llamador de Ajax. -el usuario NO será redirigido a la página de error.
En lugar de crear una nueva ruta para eso, puede redireccionar a su controlador / acción y pasar la información a través de la cadena de consulta. Por ejemplo:
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));
}
Entonces tu controlador recibirá lo que quieras:
// GET: /Error/HttpError404
public ActionResult HttpError404(string message) {
return View("SomeView", message);
}
Hay algunas concesiones con su enfoque. Tenga mucho cuidado con el bucle en este tipo de manejo de errores. Otra cosa es que ya que está pasando por la tubería asp.net para manejar un 404, creará un objeto de sesión para todos esos hits. Esto puede ser un problema (rendimiento) para sistemas muy utilizados.
Encontré una solución para el problema ajax señalada por 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");
}
}
Esta puede no ser la mejor manera para MVC ( https://.com/a/9461386/5869805 )
A continuación se muestra cómo renderizar una vista en Application_Error y escribirla en la respuesta http. No es necesario usar el redireccionamiento. Esto evitará una segunda solicitud al servidor, por lo que el enlace en la barra de direcciones del navegador se mantendrá igual. Esto puede ser bueno o malo, depende de lo que quieras.
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.
}
Ver
@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>
}
Luché con la idea de centralizar una rutina global de manejo de errores en una aplicación MVC antes. Tengo una publicación en los foros de ASP.NET .
Básicamente maneja todos sus errores de aplicación en el archivo.asax global sin la necesidad de un controlador de errores, decorando con el atributo [HandlerError]
, o jugando con el nodo customErrors
en el web.config.
Para responder a la pregunta inicial "¿Cómo pasar correctamente los datos de ruta al controlador de errores?":
IController errorController = new ErrorController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
Luego, en su clase ErrorController, implemente una función como esta:
[AcceptVerbs(HttpVerbs.Get)]
public ViewResult Error(Exception exception)
{
return View("Error", exception);
}
Esto empuja la excepción a la Vista. La página de vista debe declararse de la siguiente manera:
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<System.Exception>" %>
Y el código para mostrar el error:
<% if(Model != null) { %> <p><b>Detailed error:</b><br /> <span class="error"><%= Helpers.General.GetErrorMessage((Exception)Model, false) %></span></p> <% } %>
Aquí está la función que reúne todos los mensajes de excepción del árbol de excepciones:
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);
}
}
}
Quizás una mejor forma de manejar errores en MVC sea aplicar el atributo HandleError a su controlador o acción y actualizar el archivo Shared / Error.aspx para hacer lo que desee. El objeto Modelo en esa página incluye una propiedad Exception así como también ControllerName y ActionName.
Tengo un problema con este enfoque de manejo de errores: en el caso de web.config:
<customErrors mode="On"/>
El controlador de errores está buscando la vista Error.shtml y el flujo de control ingresa a Application_Error global.asax solo después de la excepción
System.InvalidOperationException: no se encontró la vista ''Error'' o su maestro o ningún motor de vista admite las ubicaciones buscadas. Se realizaron búsquedas en las siguientes ubicaciones: ~ / 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 en System.Web.Mvc.ViewResult.FindView (ControllerContext context) ........ ............
Asi que
Exception exception = Server.GetLastError();
Response.Clear();
HttpException httpException = exception as HttpException;
httpException es siempre nulo y luego customErrors mode = "On" :( Es engañoso Entonces <customErrors mode="Off"/>
o <customErrors mode="RemoteOnly"/>
los usuarios ven customErrors html, Then customErrors mode = "On" this el código es incorrecto también
Otro problema de este código que
Response.Redirect(String.Format("~/Error/{0}/?message={1}", action, exception.Message));
Devuelva la página con el código 302 en lugar del código de error real (402,403, etc.)
Utilice el siguiente código para redirigir en la página de ruta. Use exception.Message inst de excepción. La cadena de consulta de excepción de Coz da error si amplía la longitud de la cadena de consulta.
routeData.Values.Add("error", exception.Message);
// clear error on server
Server.ClearError();
Response.RedirectToRoute(routeData.Values);