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

    将项目中的第三方jar包上传至maven私服中 上传jar包到maven私服 在你使用的maven配置文件settings中加上如下信息 代表你访问的账号密码 <servers> < ...

  2. P2279 [HNOI2003]消防局的设立 贪心or树形dp

    题目描述 2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地.起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了一个巨大的树状 ...

  3. JavaEE 之 DBCP

    1.DBCP a.定义:DBCP(DataBase Connection Pool)数据库连接池,是java数据库连接池的一种,由Apache开发,通过数据库连接池,可以让程序自动管理数据库连接的释放 ...

  4. NN:神经网络算法进阶优化法,进一步提高手写数字识别的准确率—Jason niu

    上一篇文章,比较了三种算法实现对手写数字识别,其中,SVM和神经网络算法表现非常好准确率都在90%以上,本文章进一步探讨对神经网络算法优化,进一步提高准确率,通过测试发现,准确率提高了很多. 首先,改 ...

  5. 在Visual Sutdio 2017中使用boost库

    在Visual Sutdio 2017中使用boost库     转载 https://blog.csdn.net/u011054333/article/details/78648294 对C++有一 ...

  6. Selenium+PhantomJS使用时报错原因及解决方案(转)

    Selenium+PhantomJS使用时报错原因及解决方案     问题 今天在使用selenium+PhantomJS动态抓取网页时,出现如下报错信息: UserWarning: Selenium ...

  7. git&github快速掌握

    git&github快速掌握 安装git 版本库创建 代码修改并提交 代码回滚 工作区和暂存区 撤销操作 删除操作 更多操作 Windows下安装git https://gitforwindo ...

  8. Java内存管理-掌握类加载器的核心源码和设计模式(六)

    勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 上一篇文章介绍了类加载器分类以及类加载器的双亲委派模型,让我们能够从整体上对类加载器有 ...

  9. python数据结构之栈

    栈 栈(stack),有些地方称为堆栈,是一种容器,可存入数据元素.访问元素.删除元素,它的特点在于只能允许在容器的一端(称为栈顶端指标,英语:top)进行加入数据(英语:push)和输出数据(英语: ...

  10. XamarinAndroid组件教程RecylerView自定义适配器动画

    XamarinAndroid组件教程RecylerView自定义适配器动画 如果RecyclerViewAnimators.Adapters命名空间中没有所需要的适配器动画,开发者可以自定义动画.此时 ...