传统实现

在搭建 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 之前的版本不支持这种处理方法』。

ASP.NET Web API 2.0 统一响应格式的更多相关文章

  1. ASP.NET Web API 2.0新特性:Attribute Routing1

    ASP.NET Web API 2.0新特性:Attribute Routing[上篇] 对于一个针对ASP.NET Web API的调用请求来说,请求的URL和对应的HTTP方法的组合最终决定了目标 ...

  2. 一个ASP.NET Web API 2.0应用

    在一个空ASP.NET Web项目上创建一个ASP.NET Web API 2.0应用 由于ASP.NET Web API具有与ASP.NET MVC类似的编程方式,再加上目前市面上专门介绍ASP.N ...

  3. How ASP.NET Web API 2.0 Works?[持续更新中…]

    一.概述 RESTful Web API [Web标准篇]RESTful Web API [设计篇] 在一个空ASP.NET Web项目上创建一个ASP.NET Web API 2.0应用 二.路由 ...

  4. 在一个空ASP.NET Web项目上创建一个ASP.NET Web API 2.0应用

    由于ASP.NET Web API具有与ASP.NET MVC类似的编程方式,再加上目前市面上专门介绍ASP.NET Web API 的书籍少之又少(我们看到的相关内容往往是某本介绍ASP.NET M ...

  5. 让ASP.NET Web API支持POST纯文本格式(text/plain)的数据

    今天在web api中遇到了这样一个问题,虽然api的参数类型是string,但只能接收post body中json格式的string,不能接收原始string. web api是这样定义的: pub ...

  6. ASP.NET WEB API 初探

    本文初步介绍如何简单创建一个ASP.NET Web Api 程序. Web Api 顾名思义就是一个Api接口,客户端可调用此接口进行业务操作.此类应用与 ASP.NET  web服务(即使用扩展名. ...

  7. 基于.Net Framework 4.0 Web API开发(3):ASP.NET Web APIs 异常的统一处理Attribute 和统一写Log 的Attribute的实现

    概述:  ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作.但是项目,总有异常发生,本节就来谈谈API的异常 ...

  8. ASP.NET Web API 记录请求响应数据到日志的一个方法

    原文:http://blog.bossma.cn/dotnet/asp-net-web-api-log-request-response/ ASP.NET Web API 记录请求响应数据到日志的一个 ...

  9. 在 ASP.NET Web API 中使用 Attribute 统一处理异常

    并非所有的异常都需要 try-catch 进行重复的处理,这会导致大量的重复性代码,一旦后续系统出现异常处理机制的修改,随着代码量增多,修改也会变的更加困难. ASP.NET Web API 中特别增 ...

随机推荐

  1. SQL Server 2016/2014/2012/2008/2005/2000简体中文企业版下载地址

    为什么只提供企业版下载呢?因为不管你是学生还是工作研究人员,企业版都是功能最为齐全的一个版本,比如企业版都集成了SQL Server Management Studio管理界面(俗称企业管理器的可视化 ...

  2. android示例:一个简单的登陆程序

    最近写了个简单的登陆程序,有几点收获: 1.懂得如何在LinearLayout中嵌套LinearLayout,完善布局的行列: 2.用android:layout_weight控制控件的比重: 3.用 ...

  3. 网页中引用优酷视频去广告自动播放代码[xyytit]

    很多时候需要在网站中加入视频,可视频太大放自己服务器上太占空间,可以把视频上传到优酷网上,这样节省了空间,打开速度方面也会有不少提升.下面教大家如何引用自动播放的优酷视频.把下面的代码加在你网页适当位 ...

  4. 最高频的K个单词 · Top K Frequent Words

    [抄题]: 给一个单词列表,求出这个列表中出现频次最高的K个单词. [思维问题]: 以为已经放进pq里就不能改了.其实可以改,利用每次取出的都是顶上的最小值就行了.(性质) 不知道怎么处理k个之外的数 ...

  5. 【D3D】Direct3D中LPRECT(上左右底)和LPoint(x,y)之前转换

    D3DSprite.cpp void CD3DSprite::DrawText(CD3DFont *pFont, char *szString, RECT &DesRect, D3DCOLOR ...

  6. Asp.Net 用户验证(自定义IPrincipal和IIdentity)

    http://www.cnblogs.com/JimmyZhang/archive/2008/12/07/1349457.html

  7. 创建cookie

    cookie的创建using System;using System.Collections.Generic;using System.Linq;using System.Web;using Syst ...

  8. vue的过滤器

    Vue.Js 提供了强大的过滤器API,能够对数据进行各种过滤处理,返回需要的结果 vue的过滤器一般在JavaScript 表达式的尾部,由“|”符号指示: 过滤器可以让我们的代码更加优美,一般可以 ...

  9. java读取网页图片路径并下载到本地

    java读取网页图片路径并下载到本地 最近公司需要爬取一些网页上的数据,自己就简单的写了一个demo,其中有一些数据是图片,需要下载下来到本地并且 将图片的路径保存到数据库,示例代码如下: packa ...

  10. 2018.07.06 BZOJ 1588: HNOI2002营业额统计(非旋treap)

    1588: [HNOI2002]营业额统计 Time Limit: 5 Sec Memory Limit: 162 MB Description 营业额统计 Tiger最近被公司升任为营业部经理,他上 ...