Pull to refresh

Обработка всех исключений в контроллерах с помощью атрибута

Reading time3 min
Views14K
Всё мы знаем, что в ASP.NET MVC есть такой атрибут HandleErrorAttribute, который как сказано в MSDN
Представляет атрибут, используемый для обработки исключения, вызываемого методом действия.

Но нигде, в том же MSDN не сказано (ткните меня носом дайте ссылку где это написано, если я просмотрел), что он обрабатывает только исключения, устанавливающие код ответа сервера в 500.

Посмотрев на исходный код HandleErrorAttribute легко убедиться в этом. Там имеются следующие строки:

// If this is not an HTTP 500 (for example, if somebody throws an HTTP 404 from an action method),
// ignore it.
if (new HttpException(null, exception).GetHttpCode() != 500) {
    return;
}

Не знаю, как вам, а мне удобнее при возникновении исключения, чтобы пользователи видели специальную страницу для этого, а не «жёлтую страницу смерти» или вообще как браузер отображает стандартную для него страницу с кодом ответа сервера (зависит от настроек в Web.config, но об этом позже).


Создание собственного аналога HandleErrorAttribute


Итак, мы выяснили, что стандартный HandleErrorAttribute нам не подходит, ну что же, создадим свой.

Можно конечно, создать класс наследуя его от интерфейса IExceptionFilter, но нас в общем то устраивает поведение стандартного HandleErrorAttribute, если бы он обрабатывал все исключения. А раз нас почти всё устраивает, будет наследовать наш класс от неугодного нам HandleErrorAttribute.

public class HandleAllErrorAttribute : HandleErrorAttribute
{
    public override void OnException(ExceptionContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }

        // If custom errors are disabled, we need to let the normal ASP.NET exception handler
        // execute so that the user can see useful debugging information.
        if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
        {
            return;
        }

        Exception exception = filterContext.Exception;
 
        if (!ExceptionType.IsInstanceOfType(exception))
        {
            return;
        }

        string controllerName = (string)filterContext.RouteData.Values["controller"];
        string actionName = (string)filterContext.RouteData.Values["action"];
        HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
        filterContext.Result = new ViewResult
        {
            ViewName = View,
            MasterName = Master,
            ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
            TempData = filterContext.Controller.TempData
        };
        filterContext.ExceptionHandled = true;
        filterContext.HttpContext.Response.Clear();
        filterContext.HttpContext.Response.StatusCode = new HttpException(null, exception).GetHttpCode();

        // Certain versions of IIS will sometimes use their own error page when
        // they detect a server error. Setting this property indicates that we
        // want it to try to render ASP.NET MVC's error page instead.
        filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
    }
}

Для начала скажу, что это код стандартного HandleErrorAttribute, за исключением нескольких строк, проверяющих на HttpStatusCode.

Собственно, что он делает:
  • Проверяет входные параметры на валидность
  • Проверяет включены ли customErrors
  • Заполняет данными класс HandleErrorInfo
  • Создаёт новый ViewResult, заполняет данными и присваивает вместо текущего
  • Очищает ошибки сервера и устанавливает код ответа сервера


Класс наш готов, теперь заменим стандартный атрибут на наш. Сделаем это через глобальные фильтры, чтобы нам не писать его каждому контроллеру или действию контроллера. В данном случае это не критично, но в реальности может пригодиться. Итак, идём в Global.asax.cs и прописываем вместо стандартного фильтра, наш только что созданный атрибут:

filters.Add(new HandleAllErrorAttribute());


Испытание


Теперь, чтобы убедится, что наш фильтр работает и передаёт необходимые данные во View, несколько изменим стандартный ~/Views/Shared/Error.cshtml:

@model System.Web.Mvc.HandleErrorInfo

@{
    ViewBag.Title = "Error";
}

<h2>
    Sorry, an error occurred while processing your request.
</h2>
<h3>@Model.Exception.Message</h3>

И добавим в Web.config (который находится в корне) в секцию System.Web следующую строку:

<customErrors mode="On" defaultRedirect="Error" />

Этой строчкой мы включаем пользовательскую обработку ошибок, точнее пользовательские страницы для ошибок.

Теперь нам необходимо вызвать исключение, я сделаю это в действии стандартного контроллера (созданного студией):

public ActionResult About()
{
    throw new HttpException(403, "Доступ запрещён!");
    return View();
}

Всё! Запускаем проект, переходим в раздел «About» и видим, что наш атрибут отработал корректно.


Tags:
Hubs:
+5
Comments3

Articles