ASP.NET MVC Глобальная обработка ошибок
У меня есть обычай HandleError
атрибут, который имеет дело с ошибками в конвейере MVC; у меня есть protected void Application_Error(object sender, EventArgs e)
метод на моем Global.asax
который обрабатывает ошибки извне конвейера.
Я столкнулся с сценарием, который я не знал, был возможен; В реализации DI есть зависимость для connectionString
, который берется из файла конфигурации приложения.
Поскольку строка подключения еще не существует, возникает ошибка при создании контроллера, это обычно делает Application_Error
обработчик запускается, и отображается правильная страница ошибки (путем рендеринга частичного представления в виде строки и отправки его в качестве ответа, а в случае неудачи он просто записывает "фатальное исключение" в ответ.
За исключением этого случая, я получаю желтый "мертвый" экран смерти ASP.NET по умолчанию. Рассказывая мне:
Ошибка выполнения
Описание: на сервере произошла ошибка приложения. Текущие пользовательские настройки ошибок для этого приложения предотвращают просмотр сведений об ошибке приложения.
Подробно: чтобы детали этого конкретного сообщения об ошибке можно было просматривать на локальном сервере, создайте тег в файле конфигурации "web.config", расположенном в корневом каталоге текущего веб-приложения. Для этого тега должен быть установлен атрибут "mode" "RemoteOnly". Чтобы детали были доступны для просмотра на удаленных компьютерах, установите для "mode" значение "Off".
У меня нет defaultRedirect
установить в моем customErrors
и не оказалось Off
потому что я не хочу перенаправлять, а отображать ошибки на той же странице, на которой находится пользователь, избегая ненужного перенаправления.
Как я могу справиться с таким сценарием? И в чем даже причина того, что он ведет себя так, а не как любая другая ошибка вне контроллера?
Я понимаю, что это вряд ли случится часто, но я хотел бы иметь возможность остановить YSOD (отчасти потому, что я хочу скрыть технологию, которую я использую, но в основном потому, что она совсем не симпатична и не удобна для пользователя)
Я даже попытался зарегистрировать обработчик для UnhandledExceptions, но он также не сработал.
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
Код, который в конечном итоге производит это:
return ConfigurationManager.ConnectionStrings[key].ConnectionString;
, где ConnectionStrings[key]
является null
,
Обновить
Вот как обрабатываются ошибки приложения:
protected void Application_Error(object sender, EventArgs e)
{
this.HandleApplicationError(new ResourceController());
}
public static void HandleApplicationError(this HttpApplication application, BaseController controller)
{
if (application == null)
{
throw new ArgumentNullException("application");
}
if (controller == null)
{
throw new ArgumentNullException("controller");
}
application.Response.Clear();
Exception exception = application.Server.GetLastError();
LogApplicationException(application.Response, exception);
try
{
RenderExceptionViewResponse(application, exception, controller);
}
catch (Exception exceptionRenderingView) // now we're in trouble. let's be as graceful as possible.
{
RenderExceptionTextResponse(application, exceptionRenderingView);
}
finally
{
application.Server.ClearError();
}
}
private static void LogApplicationException(HttpResponse response, Exception exception)
{
if (exception is HttpException)
{
HttpException httpException = (HttpException)exception;
if (httpException.GetHttpCode() == (int)HttpStatusCode.NotFound)
{
_log.Debug(Resources.Error.WebResourceNotFound, httpException);
response.Status = Resources.Constants.NotFound;
return;
}
}
_log.Error(Resources.Error.UnhandledException, exception);
}
private static void RenderExceptionViewResponse(HttpApplication application, Exception exception, BaseController controller)
{
if (!RenderAsJsonResponse(application, Resources.User.UnhandledExceptionJson))
{
ErrorViewModel model = WebUtility.GetErrorViewModel(exception);
string result = controller.RenderViewToString(Resources.Constants.ErrorViewName, model);
application.Response.Write(result);
}
}
private static void RenderExceptionTextResponse(HttpApplication application, Exception exceptionRenderingView)
{
application.Response.Clear();
if (!RenderAsJsonResponse(application, Resources.User.FatalExceptionJson))
{
application.Response.Write(Resources.User.FatalException);
}
_log.Fatal(Resources.Error.FatalException, exceptionRenderingView);
}
private static bool RenderAsJsonResponse(HttpApplication application, string message)
{
if (application.Request.IsAjaxRequest())
{
application.Response.Status = Resources.Constants.HttpSuccess;
application.Response.ContentType = Resources.Constants.JsonContentType;
application.Response.Write(message);
return true;
}
return false;
}
Это атрибут, который я использую для украшения моего базового контроллера:
public class ErrorHandlingAttribute : HandleErrorAttribute
{
public Type LoggerType { get; set; }
public ErrorHandlingAttribute()
: this(typeof(ErrorHandlingAttribute))
{
}
public ErrorHandlingAttribute(Type loggerType)
{
LoggerType = loggerType;
}
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled)
{
return;
}
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
OnAjaxException(filterContext);
}
else
{
OnRegularException(filterContext);
}
}
internal protected void OnRegularException(ExceptionContext filterContext)
{
Exception exception = filterContext.Exception;
ILog logger = LogManager.GetLogger(LoggerType);
logger.Error(Resources.Error.UnhandledException, exception);
filterContext.HttpContext.Response.Clear();
ErrorViewModel model = WebUtility.GetErrorViewModel(exception);
filterContext.Result = new ViewResult
{
ViewName = Resources.Constants.ErrorViewName,
ViewData = new ViewDataDictionary(model)
};
filterContext.ExceptionHandled = true;
}
internal protected void OnAjaxException(ExceptionContext filterContext)
{
Exception exception = filterContext.Exception;
ILog logger = LogManager.GetLogger(LoggerType);
logger.Error(Resources.Error.UnhandledAjaxException, exception);
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.Status = Resources.Constants.HttpSuccess;
string errorMessage = WebUtility.GetUserExceptionMessage(exception, true);
filterContext.Result = new ExceptionJsonResult(new[] { errorMessage });
filterContext.ExceptionHandled = true;
}
}
А это мой customErrors
:
<customErrors mode="On" />
Как вы можете видеть, они довольно обширны, однако они даже не запускаются в случае доступа к ConnectionStrings
где ConnectionString
не существует; что-то вроде загадочного.
Он срабатывает в любом контроллере, содержащем исключение или исключения, не входящие в контроллер, поэтому я не понимаю, почему этот случай отличается.
3 ответа
Application_Error
может быть рекомендуемым способом обработки ошибок в ASP.NET WebForms. Но не в MVC.
У нас есть фильтры ошибок, которые заботятся об ошибках за нас. Проблема с фильтром состоит в том, что он работает только тогда, когда был вызван контроллер. Какая проблема для ошибок 404 и 401 (не найдены и авторизация) и вашей проблемы с подключением к базе данных.
customErrors
это путь сюда. Я не понимаю, почему перенаправление должно быть проблемой?
Я рассматриваю правильную обработку ошибок в статье блога: http://blog.gauffin.org/2011/11/how-to-handle-errors-in-asp-net-mvc/
Чтобы быть правильными для наших посетителей и поисковых систем, мы должны возвращать значимые коды состояния HTTP в случае возникновения ошибок, с которыми сталкивается наш сайт. Неверно возвращать HTTP-код состояния 200 при ошибке, даже если в то же время мы возвращаем представление, объясняющее, что произошла ошибка. если пользователь вводит неправильный адрес (наиболее частая ошибка пользователя), мы должны вернуть код состояния HTTP 404, а не возвращать или перенаправлять в представление, где будет возвращен код состояния 200.
Краткое и краткое изложение рекомендаций ЗДЕСЬ.
Вы не показали, как именно вы обрабатываете ошибки в событии Application_Error, и как реализован ваш пользовательский атрибут HandleAttribute, поэтому трудно догадаться, в чем может быть проблема. Распространенная проблема, за которой нужно следить при реализации Application_Error, заключается в том, что вы отображаете какое-то представление об ошибке, которое само выдает ошибку. Представьте, например, представление об ошибке, которое зависит от макета, и внутри этого макета вы вызываете Html.Action
помощник для отображения содержимого другого действия, которое само выполняет доступ к базе данных и тому подобное. Ваши сообщения об ошибках должны быть максимально статичными. В идеале у вас должен быть другой макет, чтобы избежать подобных ситуаций.
Я также могу предложить вам рассмотреть следующий подход к обработке ошибок.