【问题标题】:Handling Web API Exceptions At MVC With Handler使用 Handler 在 MVC 中处理 Web API 异常
【发布时间】:2013-02-24 01:18:39
【问题描述】:

我对 MVC 4 的异常处理有一些疑问。

我已经实现了HandleErrorAttribute 并派生了一个新属性来自定义。它真的很好用;但我不想每次都重定向用户自定义错误页面。

我在 Actions 中遇到的一些错误是从 Web API 抛出的,我想在当前页面向用户展示它们。 例如,如果用户想要创建一条记录,但 WebAPI 由于模型状态无效而引发异常,则异常详细信息会以友好的方式显示在创建视图中。

但HandleErrorAttribute默认重定向Error.cshtml。

我可以处理 Actions 中的所有异常,但我认为还有其他方法。

我也跟着http://www.prideparrot.com/blog/archive/2012/5/exception_handling_in_asp_net_mvc实现HandleErrorAttribute

 public class CustomHandleErrorAttribute : HandleErrorAttribute {
        private readonly ILogger _logger;

        public CustomHandleErrorAttribute() {
            _logger = new NLogger(typeof(CustomHandleErrorAttribute));
        }

        public override void OnException(ExceptionContext filterContext) {
            if(filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled) {
                return;
            }

            if(new HttpException(null, filterContext.Exception).GetHttpCode() != 500) {
                return;
            }

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

            // if the request is AJAX return JSON else view.
            if(filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest") {
                filterContext.Result = new JsonResult {
                    JsonRequestBehavior = JsonRequestBehavior.AllowGet,
                    Data = new {
                        error = true,
                        message = filterContext.Exception.Message
                    }
                };
            }
            else {
                var controllerName = (string)filterContext.RouteData.Values["controller"];
                var actionName = (string)filterContext.RouteData.Values["action"];
                var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
                filterContext.Result = new ViewResult {

                    ViewName = View,
                    MasterName = Master,
                    ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
                    TempData = filterContext.Controller.TempData
                };
            }

            _logger.Error(filterContext.Exception.Message, filterContext.Exception);

            filterContext.ExceptionHandled = true;
            filterContext.HttpContext.Response.Clear();
            filterContext.HttpContext.Response.StatusCode = 500;

            filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
        }
    }

我通过 HttpClient 和包装类进行了 Web API 调用。例如 Get 请求如下所示。

 public async Task<BrandInfo> Create(BrandInfo entity) {
            using(var apiResponse = await base.PostAsync(BaseUriTemplate, entity)) {

                if(apiResponse.IsSuccess) {
                    return apiResponse.Model;
                }

                throw new HttpApiRequestException(
                    string.Format(HttpRequestErrorFormat, (int)apiResponse.Response.StatusCode, apiResponse.Response.ReasonPhrase),
                    apiResponse.Response.StatusCode, apiResponse.HttpError);
            }
        }

【问题讨论】:

  • 重写方法不使用base.xx?
  • 抱歉我没听懂你说的。
  • 我假设你有这样的东西:public override onException,确保你没有base.onException(context);
  • 哦,好的。我不记得基地。我还编辑了我的答案并分享了我的代码。

标签: asp.net-mvc error-handling asp.net-web-api


【解决方案1】:

构建一个类来包装 HttpClient 并使用它来调用您的 Web API。 从 Web API 返回不同的 HTTP 状态代码,用于您希望发生重定向的情况(即 500 - 内部服务器错误,或 401 - 未经授权)和您希望显示模型状态错误的情况(400 - 我的选择是错误的请求)。 将包装盒中的状态代码处理为:

a) 如果您想要重定向时出现错误(从 Web API 收到 500 或 401),请抛出适当的异常

b) 当您不想要重定向(从 Web API 收到 400)时,只需从您的包装类返回一些可以在客户端显示的响应模型

在您的控制器中,假设您将从 HTTP 包装类返回响应模型,因为异常会导致它永远不会返回到控制器(您将全局处理它并执行重定向)。

如果您需要代码示例,我可以提供,但我认为您更多的是寻找一般概念而不是具体代码。

编辑:

在 Web API 方面:

public class ModelValidationFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            Dictionary<string,string> errors = new Dictionary<string, string>();
            foreach (KeyValuePair<string, ModelState> keyValue in actionContext.ModelState)
            {
                errors[keyValue.Key] = keyValue.Value.Errors.Select(e => e.ErrorMessage).FirstOrDefault();
            }
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, new ApiError(ApiErrorCode.ModelBindingError, errors));
        }
    }
}

在您的 global.asax 中:

        GlobalConfiguration.Configuration.Filters.Add(new ModelValidationFilterAttribute());

自定义 ApiError:

public class ApiError
{
    public ApiErrorCode ErrorCode { get; set; }
    public string ErrorMessage { get; set; }
    public Dictionary<string, string> ModelStateErrors;
}

在 MVC 方面,您的包装器 HttpClient 包装器类可能如下所示:

public class RPCService
{

    public async Task<RPCResponseModel<T>> GetAsync<T>(string controller, string action, Dictionary<string, string> queryParams, Dictionary<string, string> headers)
    {
        using (HttpClient client = new HttpClient())
        {
            client.BaseAddress = new Uri("your host goes here");
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); /// Tell RPC to return data as json
            if (headers != null) foreach (var header in headers) client.DefaultRequestHeaders.Add(header.Key, header.Value);
            string query = Query(queryParams);
                var response = await client.GetAsync(controller + "/" + action + query);
                if (response.IsSuccessStatusCode)
                {
                    return new RPCResponseModel<T>
                    {
                        StatusCode = response.StatusCode,
                        Data = await response.Content.ReadAsAsync<T>()
                    };
                }
                else if(response.StatusCode == HttpStatusCode.BadRequest)
                {
                    return new RPCResponseModel<T>
                    {
                        Error = await response.Content.ReadAsAsync<RPCErrorModel>(),
                        StatusCode = response.StatusCode
                    };
                }
else
{
    /// throw your exception to handle globally
}
        }
    }

响应模型是:

public class RPCErrorModel
{
    public int Code { get; set; }
    public string Message { get; set; }
    public Dictionary<string, string> ModelErrors;
}

public class RPCResponseModel
{
    public RPCErrorModel Error { get; set; }
    public HttpStatusCode StatusCode { get; set; }
}

public class RPCResponseModel<T> : RPCResponseModel
{
    public T Data { get; set; }
}

【讨论】:

  • 其实我的架构符合你的建议。我编辑了我的问题并添加了一个代码示例。我试图抛出一个 HttpApiRequestException 并从控制器处理它。我怎样才能做到这一点?
  • 我已经为您提供了上面的一些代码。它可能不适用于复制+粘贴,但它是我刚刚编写的内容的实现。因此,您在 MVC 端对 GetAsync 方法的调用将返回包装响应,其中包含状态代码和错误或数据。这将在 BadRequest 或 OK/NoContent 的情况下发生。如果您有错误,请从中提取模型状态错误并将其返回到视图,否则返回数据。如果您收到除此之外的任何其他状态代码,请抛出异常并在全局错误处理过滤器中捕获它/在某处重定向。
  • 其实这不是我想要的,因为我正在尝试使用属性处理所有异常,但这似乎也可以。感谢您的帮助。
猜你喜欢
  • 2012-05-04
  • 2014-01-16
  • 1970-01-01
  • 2015-01-24
  • 1970-01-01
  • 2017-10-11
  • 2014-07-16
  • 1970-01-01
  • 2016-12-02
相关资源
最近更新 更多