• ASP.NET Web API 2.0 统一响应格式


    传统实现

    在搭建 Web API 服务的时候,针对客户端请求,我们一般都会自定义响应的 JSON 格式,比如:

    {
    	"Data" : {
    		"Id" : 100,
    		"Name" : "Robin"
    	},
    	"ErrorMessage" : "错误消息"
    }
    

    在基于 ASP.NET Web API 的应用程序,我们一般会创建一个相应结构的 C# 类,如下:

    public class ApiResult
    {  
       public string ErrorMessage { get; set; }
       public object Data { get; set; }
    }
    

    这里约定, ErrorMessage 为空或null,即表示没有异常,这时 Data 就是需要的数据;反之如果 ErrorMessage 不为空或null, 则代表错误消息,这时 Data 为null。

    接下来在 Action 中返回该类的一个实例, Web API 会在内部调用格式化器将对象序列化为 JSON 或 XML 等格式,如下:

    public class UserController : ApiController
    {
    	public IHttpActionResult GetUser()
    	{
    		return new ApiResult()
    		{
    			Data = new User{ Id = 100, Name = "Robin" },
    			ErrorMessage = string.Empty
    		};
    	}
    }
    
    public class User
    {
    	public int Id {get; set;}
    	public string Name {get; set;}
    }
    

    好了,传统做法就是这样,也可以实现。但是再进一步考虑,如果有非常多的 Action 方法,每次都要写 reutrn new ApiResult(){......} 是不是特别繁琐呢?

    问题

    有没有办法在 Action 方法中只返回真正需要的数据,但是返回给客户端时又统一成约定的 JSON 结构呢?

    解决方案

    当然有办法,借助 Web API 提供的 ActionFilter 就可以实现。

    首先我们新建一个 CustomActionFilter :

    public class CustomActionFilter : ActionFilterAttribute
    {
        public override void OnActionExecuted(HttpActionExecutedContext context)
        {
            var content = context.Response?.Content as ObjectContent;
            if (content != null)
            {
                content.Value = new ApiResult 
    				            { 
    					            Data = content.Value, 
    							};
            }
        }
    }
    

    然后 Action 方法这样写:

    public User GetAll()
    {
    	return new new User{Id = 100, Name = "Robin"};
    }
    

    这样实现的另一个好处是,由于返回值是强类型,可以据此生成 API 文档,方法的可读性也更好。

    异常处理

    前面提到的需求实现了,然后再进一步考虑,如何处理异常情况?
    如果由于代码 BUG 抛出未处理的异常,Web API仍然会调用 CustomActionFilter 中的代码,但是这时 Response = null,也就无法给 content.Value 重新赋值。
    这时 Web API 会将框架约定的 JSON 消息返回给客户端,而不是我们业务上需要的,如下是 Web API 抛出的未处理异常消息:

    {
      "Message": "An error has occurred.",
      "ExceptionMessage": "No MessageException parameter",
      "ExceptionType": "Framework.Common.MessageException",
      "StackTrace": "   在 Controllers.FooController.GetAll() 位置......
    }
    

    这时如果还希望异常消息遵循业务约定的 JSON 格式,该如何做呢?
    这里要分几种情况:

    Action 内的异常

    可以直接在 CustomActionFilter 的 OnActionExecuted 方法中处理,改造后的代码如下:

    public override void OnActionExecuted(HttpActionExecutedContext context)
    {
        var content = context.Response?.Content as ObjectContent;
        if (content != null)
        {
            content.Value = new ApiResult { Data = content.Value };
        }
        
        // 设置发生异常时的消息
        if (context.Exception != null)
        {
            context.Response = new HttpResponseMessage(HttpStatusCode.InternalServerError)
            {
                Content = new StringContent(JsonConvert.SerializeObject(
                    new ApiResult
                    {
                        ErrorMessage = context.Exception.Message
                    }), Encoding.UTF8, "application/json")
            };
        }
    }
    

    同样也可以用自定义 ExceptionFilter 来达到同样的目的,这里为了简化不再贴代码。

    其他异常

    ActionFilterAttribute 和 ExceptionFilterAttribute 都只能处理部分异常,比如 Action 内的异常,但是譬如 以下的几种未处理异常,过滤器就爱莫能助了:

    1. 来自 Controller 构造器的异常。
    2. 来自 Message Handlers 的异常。
    3. 匹配路由过程中的异常
    4. 在序列化响应内容期间产生的异常

    为了处理全局范围内的未处理异常,Web API 提供了 ExceptionHandler 和 ExceptionLogger。
    详情可以参考我翻译的文档:ASP.NET Web API 2 中的全局错误处理
    其中只有 ExceptionHandler 可以在捕捉到未处理异常并处理后,对响应消息进行重新设置,而 ExceptionLogger 则不能。

    代码如下:

    public class CollectServiceExceptionHandler : ExceptionHandler
    {
        public override Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
        {
            context.Result = new ApiResult { ErrorMessage = context.Exception.Message };
    
            return base.HandleAsync(context, cancellationToken);
        }
    }
    

    注意:这里 ExceptionHandlerContextResult 属性的类型是 IHttpActionResult,所以**ApiResult ** 类要实现 IHttpActionResult 接口。

    ExceptionHandler 的用途就是:接收全局范围内未处理的异常,然后返回一个自定义的错误消息。

    总结

    实现开篇的需求,有三种实现方式:

    1. 自定义 ActionFilterAttribute
    2. 自定义 ExceptionFilterAttribute
    3. 自定义 ExceptionHandler

    补充:经 @ichengzi 指出,『web api 2.0 之前的版本不支持这种处理方法』。

  • 相关阅读:
    《剑指offer》面试题7:旋转数组的最小数字
    eclipse ------ TODO、FIXME、XXX 等任务标记
    RT-Thread ------ waitqueue
    RT-Thread ------ 设备注册
    ubuntu 安装 glibc
    openwrt上面移植MQTT代码
    MH5000-31模组无法识别SIM卡
    "Hello osmdroid World"手机GPS轨迹数据
    地质数据下载
    绘图软件Surfer绘制等高线
  • 原文地址:https://www.cnblogs.com/songxingzheng/p/6423697.html
Copyright © 2020-2023  润新知