Обработка ошибок в 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". Включить детали, которые можно просмотреть на удаленных компьютерах, установите "режим" на "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 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 не существует; что вызывает недоумение.

Он срабатывает в любом контроллере, содержащем исключение или исключения, не входящие в контроллер, поэтому я не понимаю, почему этот случай не отличается.

Ответ 1

Application_Error может быть рекомендованным способом обработки ошибок в ASP.NET WebForms. Но не в MVC.

Мы получили фильтры ошибок, которые заботятся об ошибках для нас. Проблема с фильтром заключается в том, что он работает только при вызове контроллера. В чем проблема 404 и 401 ошибок (не найдена и авторизация) и проблема с подключением к базе данных.

customErrors - это путь сюда. Я не понимаю, почему перенаправление должно быть проблемой?

Я получаю правильную обработку ошибок в статье в блоге: http://blog.gauffin.org/2011/11/how-to-handle-errors-in-asp-net-mvc/

Ответ 2

Вы не указали, как именно вы обрабатываете ошибки в своем событии Application_Error, и как реализован ваш пользовательский HandleAttribute, поэтому трудно догадаться, в чем проблема. Общей проблемой для наблюдения за внедрением Application_Error является то, что вы визуализируете некоторое представление об ошибке, которое само вызывает ошибку. Подумайте, например, об ошибке, которая зависит от макета и внутри этого макета, который вы вызываете помощником Html.Action, чтобы отображать содержимое другого действия, которое само по себе делает доступ к базе данных и тому подобное. Ваши представления ошибок должны быть как можно более статичными. В идеале у вас должен быть другой макет, чтобы избежать подобных ситуаций.

Я также могу предложить вам взглянуть на следующий подход для обработки ошибок.

Ответ 3

Чтобы быть правильными для наших посетителей и поисковых систем, мы должны возвращать значимые коды состояния HTTP, если возникают ошибки, обращенные к нашему сайту. Нечестно возвращать код статуса HTTP 200 при ошибке, даже если в то же время мы возвращаем представление, объясняя, что произошла ошибка. если пользователь вводит неверный адрес (наиболее частая ошибка пользователя), мы должны вернуть код статуса 404 HTTP и не возвращать или перенаправлять на представление, где будет возвращен код состояния 200.
Есть краткий и чистый обзор рекомендаций ЗДЕСЬ.