Обработка пользовательских ошибок ASP.NET MVC Application_Error Global.asax?
У меня есть базовый код для определения ошибок в моем приложении 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?
}
}
10 ответов
Вместо того, чтобы создавать новый маршрут для этого, вы можете просто перенаправить на ваш контроллер / действие и передать информацию через строку запроса. Например:
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, вы создадите объект сеанса для всех этих попаданий. Это может быть проблемой (производительность) для интенсивно используемых систем.
Чтобы ответить на первоначальный вопрос "как правильно передать 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);
}
}
}
Я нашел решение проблемы AJAX, отмеченной 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");
}
}
Раньше я боролся с идеей централизации глобальной обработки ошибок в приложении MVC. У меня есть пост на форумах ASP.NET.
Он в основном обрабатывает все ошибки вашего приложения в global.asax без необходимости в контроллере ошибок, украшающем [HandlerError]
атрибут, или возиться с customErrors
узел в web.config.
Это может быть не лучшим способом для MVC ( /questions/41025374/aspnet-mvc-globalnaya-obrabotka-oshibok/41025385#41025385)
Ниже показано, как вы отображаете представление в Application_Error и записываете его в ответ http. Вам не нужно использовать редирект. Это предотвратит повторный запрос к серверу, поэтому ссылка в адресной строке браузера останется прежней. Это может быть хорошо или плохо, это зависит от того, что вы хотите.
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>
}
Возможно, лучший способ обработки ошибок в MVC - это применить атрибут HandleError к вашему контроллеру или действию и обновить файл Shared/Error.aspx, чтобы сделать то, что вы хотите. Объект Model на этой странице включает в себя свойство Exception, а также ControllerName и ActionName.
Application_Error, имеющая проблему с Ajax -запросами. Если ошибка обработана в Action, который вызван Ajax - он отобразит ваш Просмотр ошибок внутри результирующего контейнера.
Брайан, этот подход прекрасно работает для запросов, не относящихся к Ajax, но, как сказал Lion_cl, если у вас возникла ошибка во время вызова Ajax, ваше представление Share/Error.aspx (или пользовательское представление страницы ошибок) будет возвращено вызывающей программе Ajax. - пользователь НЕ будет перенаправлен на страницу с ошибкой.
Используйте следующий код для перенаправления на страницу маршрута. Используйте исключение. Сообщение об исключении. Строка запроса исключения Coz выдает ошибку, если она увеличивает длину строки запроса.
routeData.Values.Add("error", exception.Message);
// clear error on server
Server.ClearError();
Response.RedirectToRoute(routeData.Values);
У меня проблема с этим подходом к обработке ошибок: В случае 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 в System.Web.Mvc.ViewResult.FindView(контекст ControllerContext) ....................
Так
Exception exception = Server.GetLastError();
Response.Clear();
HttpException httpException = exception as HttpException;
httpException всегда равно null, тогда customErrors mode="On":(Это вводит в заблуждение <customErrors mode="Off"/>
или же <customErrors mode="RemoteOnly"/>
пользователи видят html customErrors, затем customErrors mode = "On" и этот код тоже неверный
Еще одна проблема этого кода
Response.Redirect(String.Format("~/Error/{0}/?message={1}", action, exception.Message));
Страница возврата с кодом 302 вместо реального кода ошибки (402,403 и т. Д.)