前些时日,老周在升级“华南闲肾回收登记平台”时,为了扩展业务,尤其是允许其他开发人员在其他平台向本系统提交有关肾的介绍资料,于是就为该系统增加了几个 Web API。
其中,有关肾的介绍采用纯文本方式提交,大概的代码是这样的。
[Route("api/[controller]/[action]")] public class PigController : Controller { [HttpPost] public string KidneyRemarks([FromBody]string remarks) { return $"根据你的描述,贵肾的当前状态为:{remarks}"; } }
这个 Action 很简单(主要为了方便别人看懂),参数接受一个字符串实例,返回的也是字符串。哦,重点要记住,对参数要加上 FromBody 特性。嗯,为啥呢。因为我们要得到的数据是从客户端发来的 HTTP 正文提取的,应用这个特性就是说参数的值来自于提交的正文,而不是 Header,也不是 url 参数。
随后老周兴高采烈地用 Postman 进行测试。
幻想总是很美丽的,现实总是很骨感的。结果……

没成功,这时候,按照常规思路,会产生各种怀疑。怀疑地址错了吗?哪个配置没写上?是不是路由不正确?……
别急,看看服务器返回的状态码:415 Unsupported Media Type。啥意思呢,其实,这就是问题所在了。我们提交纯文本类型的数据,用的 Content-Type 是 text/plain,可是,不受支持!
不信?现在把提交的内容改为 JSON 看看。

看看,我没说错吧。
这就很明了啦,JSON 默认是被支持的,但是纯文本不行。有办法让它支持 text / plain 类型的数据吗?答案是:能的。
在 Startup 中使用 ConfigureServices 方法配置服务时,我们一般就是简单地写上。
services.AddMvc();
然后,各个 MVC 选项保持默认。
在 MVC 选项中,可以控制输入和输出的格式,分别由两个属性来管理:
InputFormatters 属性:是一个集合,里面的每个对象都要实现 IInputFormatter 接口,默认提供对 JSON 和 XML 的支持。
OutputFormatters 属性:也是一个集合,里面的元素都要实现 IOutputFormatter 接口,默认支持 JSON 和 XML,也支持文本类型。
也就是说,输出是支持纯文本的,所以 Action 可以返回 string 类型的值,但输入是不支持文本格式的,所以,用 text / plain 格式提交,就会得到 415 代码了。
明白了这个原理,解决起问题来就好办了,咱们自己实现一个支持纯文本格式的 InputFormatter 就行了。不过呢,我们也不必直接实现 IInputFormatter 接口,因为,有个抽象类挺好使的—— TextInputFormatter,处理文本直接实现它就好了。
于是乎,老周就写了这个类。
public sealed class PlainTextInputFormatter : TextInputFormatter { public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) { string content; using(var reader = context.ReaderFactory(context.HttpContext.Request.Body, encoding)) { content = await reader.ReadToEndAsync(); } // 最后一步别忘了 return await InputFormatterResult.SuccessAsync(content); } }
TextInputFormatter 类只有 ReadRequestBodyAsync 方法是抽象的,所以,如果没其他活要干的话,只实现这个方法就够了。这个方法的功能就是读取 HTTP 请求的正文,然后把你读取到的内容填充给 InputFormatterResult 对象。
InputFormatterResult 类很有意思的,没有公共构造函数,你无法 new 对象,只能靠媒人介绍对象,通过 Failure、Success 这些方法直接返回对象实例。这些方法你看名字就知道什么用途了,不用多解释。
在上面代码中,ReaderFactory 属性其实是个委托,通过构造函数创建的,不过,这个委托实例在传进 ReadRequestBodyAsync 方法时已经创建,你只需要像调用方法一样调用它就行了,第一个参数是一个流,哪里的流?当然是 HTTP 请求的正文了,这里可以透过 HttpContext 的 Request 的 Body 来获得;第二个参数嘛,呵呵,是文本编码,这个好办,直接把传进 ReadRequestBodyAsync 方法的 encoding 传过去就行了。
ReaderFactory 委托调用后返回一个 TextReader,是了,我们就是用它来读取请求正文的。最后把读出来的字符串填充给 InputFormatterResult 就行了。
不过呢,这个类现在还不能用,因为默认情况下,SupportedMediaTypes 集合是空的,你得添加一下,它支持哪些 Content-Type,我们这里只要 text / plain 就行了。

