前些时日,老周在升级“华南闲肾回收登记平台”时,为了扩展业务,尤其是允许其他开发人员在其他平台向本系统提交有关肾的介绍资料,于是就为该系统增加了几个 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 就行了。

        public PlainTextInputFormatter()
{
SupportedMediaTypes.Add("text/plain");
SupportedEncodings.Add(System.Text.Encoding.UTF8);
}

这些写在构造函数里就 OK 了。注意 SupportedEncodings 集合,是配置字符编码,一般嘛,UTF-8 最合适了。你也可以从 TextInputFormatter 类的两个只读的静态字段中获取。

protected static readonly Encoding UTF8EncodingWithoutBOM;
protected static readonly Encoding UTF16EncodingLittleEndian;

现在基本可以用了。因为我们上面写的那个 Action 是带字符串类型参数的,如果你觉得不放心,可以覆写一下 CanReadType 方法,这个方法有个 type 参数,指的是 Model Type,说白了就是 Action 要接收的参数的类型,咱们这里是 string,所以,实现这个方法很简单,如果是字符串类型就返回 true,表示能读取,否则返回 false,表示不能读。

回到 Startup 类,找到 ConfigureServices 方法,我们在 AddMvc 的时候要对 Options 配置一下,把咱们刚刚写好的 InputFormatter 加进去。

        public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(opt =>
{
opt.InputFormatters.Add(new PlainTextInputFormatter());
});
}

好了,现在再请 Postman 大叔,重新测试一下。

嗯,皆大欢喜,又解决一个问题了。

我们不妨继续扩展一下,如果提交的是 text / plain 数据内容,而 Action 想让其赋值给 DateTime 或者 int 类型的参数呢。其实也一样,就是自己实现一下输入格式。这一次我们不继承 TextInputFormatter 类了,而是继承抽象程度更高的 InputFormatter 类。

    public sealed class CustInputFormatter : InputFormatter
{
public CustInputFormatter()
{
SupportedMediaTypes.Add("text/plain");
} protected override bool CanReadType(Type type)
{
return (type == typeof(DateTime)) || (type == typeof(int));
} public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
string val;
using (var reader = context.ReaderFactory(context.HttpContext.Request.Body, Encoding.UTF8))
{
val = await reader.ReadToEndAsync();
}
InputFormatterResult result = null;
if(context.ModelType == typeof(DateTime))
{
result = InputFormatterResult.Success(DateTime.Parse(val));
}
else
{
result = InputFormatterResult.Success(int.Parse(val));
}
return result;
}
}

这一次应该不用我解释,你都能看懂了。不过注意一点,因为要应用的目标参数可能是 int 和 DateTime 类型,所以,在填充 InputFormatterResult 对象时,你要先检查一下 ModelType 属性。

            if(context.ModelType == typeof(DateTime))
{
result = InputFormatterResult.Success(DateTime.Parse(val));
}
else
{
result = InputFormatterResult.Success(int.Parse(val));
}

现在应用一下这个输入格式类。

        public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(o =>
{
o.InputFormatters.Add(new CustInputFormatter());
});
}

下面来试试吧,建一个 Controller,然后定义两个 Action,一个接收 int 类型的参数,一个接收 DateTime 类型的参数。

    [Route("[controller]/[action]")]
public class TestController : Controller
{
[HttpPost]
public string Upload([FromBody]DateTime dt)
{
return $"你提交的时间是:{dt}";
} [HttpPost]
public string UploadInt([FromBody]int val)
{
return $"你提交的整数值是:{val}";
}
}

FromBody 特性千万要记得用上,不然待会读不了你又要到处 Debug 了。

好,测试开始了,首先试一下 DateTime 类型的。

再试一下 int 类型的。

感觉如何,好刺激吧。好啦,今天的高大上技巧就分享到这儿了。

示例源代码下载:请用洪荒之力猛点这里

【ASP.NET Core】从向 Web API 提交纯文本内容谈起的更多相关文章

  1. 【ASP.NET Core学习】Web API

    这里介绍在ASP.NET Core中使用Web API创建 RESTful 服务,本文使用VSCode + NET Core3.0 创建简单Rest API 格式化输出 JSON Patch请求 Op ...

  2. 从头编写 asp.net core 2.0 web api 基础框架 (1)

    工具: 1.Visual Studio 2017 V15.3.5+ 2.Postman (Chrome的App) 3.Chrome (最好是) 关于.net core或者.net core 2.0的相 ...

  3. 从头编写 asp.net core 2.0 web api 基础框架 (3)

    第一部分:http://www.cnblogs.com/cgzl/p/7637250.html 第二部分:http://www.cnblogs.com/cgzl/p/7640077.html 之前我介 ...

  4. 【转载】从头编写 asp.net core 2.0 web api 基础框架 (3)

    Github源码地址:https://github.com/solenovex/Building-asp.net-core-2-web-api-starter-template-from-scratc ...

  5. 【转载】从头编写 asp.net core 2.0 web api 基础框架 (1)

    工具: 1.Visual Studio 2017 V15.3.5+ 2.Postman (Chrome的App) 3.Chrome (最好是) 关于.net core或者.net core 2.0的相 ...

  6. 从头编写asp.net core 2.0 web api 基础框架 (5) + 使用Identity Server 4建立Authorization Server (7) 可运行前后台源码

    前台使用angular 5, 后台是asp.net core 2.0 web api + identity server 4. 从头编写asp.net core 2.0 web api 基础框架: 第 ...

  7. 使用 ASP.NET Core MVC 创建 Web API(五)

    使用 ASP.NET Core MVC 创建 Web API 使用 ASP.NET Core MVC 创建 Web API(一) 使用 ASP.NET Core MVC 创建 Web API(二) 使 ...

  8. 使用 ASP.NET Core MVC 创建 Web API(一)

    从今天开始来学习如何在 ASP.NET Core 中构建 Web API 以及每项功能的最佳适用场景.关于此次示例的数据库创建请参考<学习ASP.NET Core Razor 编程系列一> ...

  9. 使用 ASP.NET Core MVC 创建 Web API(二)

    使用 ASP.NET Core MVC 创建 Web API 使用 ASP.NET Core MVC 创建 Web API(一) 六.添加数据库上下文 数据库上下文是使用Entity Framewor ...

随机推荐

  1. Kafka的接口回调 +自定义分区、拦截器

    一.接口回调+自定义分区 1.接口回调:在使用消费者的send方法时添加Callback回调 producer.send(new ProducerRecord<String, String> ...

  2. 【JavaScript】快速入门

    摘抄地址快速入门 No1: JavaScript严格区分大小写 No2: JavaScript不区分整数和浮点数,统一用Number表示 NaN表示Not a Number,当无法计算结果时用NaN表 ...

  3. android studio打可执行jar包

    android studio可以通过library工程打出jar包 解压会看到META-INF/MANIFEST.MF文件的打开如下: Manifest-Version: 1.0 增加一行,注意冒号后 ...

  4. Kosaraju与Tarjan(图的强连通分量)

    Kosaraju 这个算法是用来求解图的强连通分量的,这个是图论的一些知识,前段时间没有学,这几天在补坑... 强连通分量: 有向图中,尽可能多的若干顶点组成的子图中,这些顶点都是相互可到达的,则这些 ...

  5. vs2008单元测试

    调试是解决错误的过程,测试是发现软件缺陷的过程.每一个软件在交付使用时前都必须经过测试.软件测试是软件开发的重要组成部分,现在已经发展成专门的技术. 在消除了程序中的语法错误和运行错误后,程序仍然不能 ...

  6. 解决Intellij IDEA部署JavaWeb项目 404问题

    今天朋友问了我一个问题,他的公司项目用idea启动tomcat是成功的, 但是访问页面是404.一般来说,这个问题普遍都是打war包的时候配置有一定的问题导致的. 检查一下Project Struct ...

  7. 学习Nodejs:《Node.js开发指南》微博项目express2迁移至express4过程中填的坑

    <Node.js开发指南>项目地址https://github.com/BYVoid/microblog好不容易找到的基础版教程,但书中是基于express2的,而现在用的是express ...

  8. H5图片压缩上传

    1.所用到技术 HTML5 API:filereader.canvas 以及 formdata 目前来说,HTML5的各种新API都在移动端的webkit上得到了较好的实现.本次使用到的FileRea ...

  9. JSONP原理实现及url传递参数封装

      利用在页面中创建<script>节点的方法向不同域提交HTTP请求的方法称为JSONP,这项技术可以解决跨域提交Ajax请求的问题. JSONP的优点是:它不像XMLHttpReque ...

  10. ES搜索社区

    好问题 1.比如我要索引的条目为“33分钟侦探”,我想在用户输入“3”.“33”.“三三”.“三十三”.“三十三分钟”等的情况下都命中该条目,请问有没有什么好的方式实现? PS:使用的是ansj分词器 ...