Application_Error и ExceptionFilter

Поэтому недавно я создал ExceptionFilter, который обрабатывает все ошибки, кроме ошибок API. ExceptionFilter выглядит следующим образом:

public class ExceptionAttribute : IExceptionFilter
{

    /// <summary>
    /// Handles any exception
    /// </summary>
    /// <param name="filterContext">The current context</param>
    public void OnException(ExceptionContext filterContext)
    {

        // If our exception has been handled, exit the function
        if (filterContext.ExceptionHandled)
            return;

        // If our exception is not an ApiException
        if (!(filterContext.Exception is ApiException))
        {

            // Set our base status code
            var statusCode = (int)HttpStatusCode.InternalServerError;

            // If our exception is an http exception
            if (filterContext.Exception is HttpException)
            {

                // Cast our exception as an HttpException
                var exception = (HttpException)filterContext.Exception;

                // Get our real status code
                statusCode = exception.GetHttpCode();
            }

            // Set our context result
            var result = CreateActionResult(filterContext, statusCode);

            // Set our handled property to true
            filterContext.ExceptionHandled = true;
        }
    }

    /// <summary>
    /// Creats an action result from the status code
    /// </summary>
    /// <param name="filterContext">The current context</param>
    /// <param name="statusCode">The status code of the error</param>
    /// <returns></returns>
    protected virtual ActionResult CreateActionResult(ExceptionContext filterContext, int statusCode)
    {

        // Create our context
        var context = new ControllerContext(filterContext.RequestContext, filterContext.Controller);
        var statusCodeName = ((HttpStatusCode)statusCode).ToString();

        // Create our route
        var controller = (string)filterContext.RouteData.Values["controller"];
        var action = (string)filterContext.RouteData.Values["action"];
        var model = new HandleErrorInfo(filterContext.Exception, controller, action);

        // Create our result
        var view = SelectFirstView(context, string.Format("~/Views/Error/{0}.cshtml", statusCodeName), "~/Views/Error/Index.cshtml", statusCodeName);
        var result = new ViewResult { ViewName = view, ViewData = new ViewDataDictionary<HandleErrorInfo>(model) };

        // Return our result
        return result;
    }

    /// <summary>
    /// Gets the first view name that matches the supplied names
    /// </summary>
    /// <param name="context">The current context</param>
    /// <param name="viewNames">A list of view names</param>
    /// <returns></returns>
    protected string SelectFirstView(ControllerContext context, params string[] viewNames)
    {
        return viewNames.First(view => ViewExists(context, view));
    }

    /// <summary>
    /// Checks to see if a view exists
    /// </summary>
    /// <param name="context">The current context</param>
    /// <param name="name">The name of the view to check</param>
    /// <returns></returns>
    protected bool ViewExists(ControllerContext context, string name)
    {
        var result = ViewEngines.Engines.FindView(context, name, null);

        return result.View != null;
    }
}

Как видите, если ошибка не является исключением ApiException, я перенаправляю пользователя на контроллер ошибок. ApiException — это просто ошибка, которая возникает, когда я делаю вызов API из MVC. Когда происходят эти ошибки, я хотел бы вернуть ошибку в виде JSON обратно клиенту, чтобы JavaScript мог ее обработать.

Я думал, что это не сделает обработка ошибки, но вместо этого она генерирует ошибку сервера (хотя и с ошибкой JSON) следующим образом:

Ошибка сервера в приложении '/'

{сообщение: сбой проверки validateMove:\r\nЭлемент отправлен и не может быть перемещен}

Описание: во время выполнения текущего веб-запроса возникло необработанное исключение. Пожалуйста, просмотрите трассировку стека для получения дополнительной информации об ошибке и о том, где она возникла в коде.

Сведения об исключении: SapphirePlus.Web.ApiException: {сообщение: ошибка проверки validateMove:\r\nЭлемент отправлен и не может быть перемещен}

Ошибка источника:

Строка 181: если (response.StatusCode != HttpStatusCode.OK)

Строка 182: создать новое исключение ApiException (результат);

Итак, мой вопрос: могу ли я получить метод Application_Error для получения ошибок, которые ARE ApiException, и вернуть ошибку в формате JSON?


person r3plica    schedule 10.02.2016    source источник
comment
Похоже, вы забыли присвоить результат: filterContext.Result = result;   -  person Khanh TO    schedule 10.02.2016
comment
ах да, это не проблема, но спасибо, что указали на это!   -  person r3plica    schedule 10.02.2016


Ответы (2)


Итак, мой вопрос: могу ли я получить метод Application_Error для получения ошибок, которые являются исключениями ApiException, и вернуть ошибку в виде JSON?

Конечно:

protected void Application_Error()
{
    var apiException = Server.GetLastError() as ApiException;
    if (apiException != null)
    {
        Response.Clear();
        Server.ClearError();

        Response.StatusCode = 400;
        Context.Response.ContentType = "application/json";
        Context.Response.Write("YOUR JSON HERE");
    }
}
person Darin Dimitrov    schedule 10.02.2016
comment
Похоже, это не работает, оно просто записывает содержимое в браузер и никогда не обрабатывается запросом ajax. - person r3plica; 10.02.2016
comment
Что ж, тогда вам, вероятно, следует исправить свой запрос AJAX. Поскольку вы предоставили ровно 0 сведений о том, как выполняется этот запрос, было бы неплохо иметь возможность помочь в решении этой проблемы. - person Darin Dimitrov; 10.02.2016
comment
Я сказал, что это делается из MVC, т. е. вызов выполняется изнутри контроллера MVC к внешнему API (кажется безумным, я знаю, но устройство, которое я использую, на самом деле не поддерживает JavaScript). Во всяком случае, мне удалось решить эту проблему с помощью исключения фильтра. Я опубликую свой ответ через минуту. - person r3plica; 11.02.2016

В конце концов мне вообще не нужно было использовать Global.asax, я смог обработать все это внутри моего класса ExceptionAttribute следующим образом:

public class ExceptionAttribute : IExceptionFilter
{

    /// <summary>
    /// Handles any exception
    /// </summary>
    /// <param name="filterContext">The current context</param>
    public void OnException(ExceptionContext filterContext)
    {

        // If our exception has been handled, exit the function
        if (filterContext.ExceptionHandled)
            return;

        // Set our base status code
        var statusCode = (int)HttpStatusCode.BadRequest;

        // If our exception is an http exception
        if (filterContext.Exception is HttpException)
        {

            // Cast our exception as an HttpException
            var exception = (HttpException)filterContext.Exception;

            // Get our real status code
            statusCode = exception.GetHttpCode();
        }

        // Set our context result
        var result = CreateActionResult(filterContext, statusCode);

        // Set our handled property to true
        filterContext.Result = result;
        filterContext.ExceptionHandled = true;
    }

    /// <summary>
    /// Creats an action result from the status code
    /// </summary>
    /// <param name="filterContext">The current context</param>
    /// <param name="statusCode">The status code of the error</param>
    /// <returns></returns>
    protected virtual ActionResult CreateActionResult(ExceptionContext filterContext, int statusCode)
    {

        // Create our context
        var context = new ControllerContext(filterContext.RequestContext, filterContext.Controller);
        var statusCodeName = ((HttpStatusCode)statusCode).ToString();

        // Create our route
        var controller = (string)filterContext.RouteData.Values["controller"];
        var action = (string)filterContext.RouteData.Values["action"];
        var model = new HandleErrorInfo(filterContext.Exception, controller, action);

        // Create our result
        var view = SelectFirstView(context, string.Format("~/Views/Error/{0}.cshtml", statusCodeName), "~/Views/Error/Index.cshtml", statusCodeName);
        var result = new ViewResult { ViewName = view, ViewData = new ViewDataDictionary<IError>(this.Factorize(model)) };

        // Return our result
        return result;
    }

    /// <summary>
    /// Factorizes the HandleErrorInfo
    /// </summary>
    /// <param name="error"></param>
    /// <returns></returns>
    protected virtual IError Factorize(HandleErrorInfo error)
    {

        // Get the error
        var model = new Error
        {
            Message = "There was an unhandled exception."
        };

        // If we have an error
        if (error != null)
        {

            // Get our exception
            var exception = error.Exception;

            // If we are dealing with an ApiException
            if (exception is ApiException || exception is HttpException)
            {

                // Get our JSON
                var json = JObject.Parse(exception.Message);
                var message = json["exceptionMessage"] != null ? json["exceptionMessage"] : json["message"];

                // If we have a message
                if (message != null)
                {

                    // Update our model message
                    model.Message = message.ToString();
                }
            }
            else
            {

                // Update our message
                model.Message = exception.Message;
            }
        }

        // Return our response
        return model;
    }

    /// <summary>
    /// Gets the first view name that matches the supplied names
    /// </summary>
    /// <param name="context">The current context</param>
    /// <param name="viewNames">A list of view names</param>
    /// <returns></returns>
    protected string SelectFirstView(ControllerContext context, params string[] viewNames)
    {
        return viewNames.First(view => ViewExists(context, view));
    }

    /// <summary>
    /// Checks to see if a view exists
    /// </summary>
    /// <param name="context">The current context</param>
    /// <param name="name">The name of the view to check</param>
    /// <returns></returns>
    protected bool ViewExists(ControllerContext context, string name)
    {
        var result = ViewEngines.Engines.FindView(context, name, null);

        return result.View != null;
    }
}

Это обработало любую ошибку Mvc, и для моих вызовов API я сделал следующее:

    /// <summary>
    /// Used to handle the api response
    /// </summary>
    /// <param name="response">The HttpResponseMessage</param>
    /// <returns>Returns a string</returns>
    private async Task<string> HandleResponse(HttpResponseMessage response)
    {

        // Read our response content
        var result = await response.Content.ReadAsStringAsync();

        // If there was an error, throw an HttpException
        if (response.StatusCode != HttpStatusCode.OK)
            throw new ApiException(result);

        // Return our result if there are no errors
        return result;
    }

Это позволило мне перехватить ApiError и обработать ответ иначе, чем при любом другом исключении.

person r3plica    schedule 11.02.2016