0

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

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

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

但是 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);
            }
        }
4

1 回答 1

2

构建一个将包装 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; }
}
于 2013-03-09T21:41:05.367 回答