主要目标

在Asp.net Core控制器中,通过自定义格式化程序来映射自定义处理控制器中的“未知”内容。

简单案例

为了演示这个问题,我们用VS2017创建一个默认的Asp.net Core Web Api项目。


  1. [Route("api/[controller]")]
  2. [ApiController]
  3. public class ValuesController : ControllerBase{
  4. [HttpGet]
  5. public ActionResult<string> Get() {
  6. return "ok";
  7. }
  8. [HttpPost]
  9. [Route("PostX")]
  10. public ActionResult<string> Post([FromBody] string value)
  11. {
  12. return value;
  13. }
  14. }

Json请求

我们从最常见的json输入请求开始。

  1. User-Agent: Fiddler
  2. Host: localhost:5000
  3. Content-Type: application/json
  4. Content-Length: 16

请求body:

  1. {"123456"}

通过后台调试和fiddler抓包,我们可以看到请求输入和返回。

后台调试,查看请求输入结果

fiddler查看请求header

fiddler查看返回结果

注意!!

  • 别忘了[FromBody],有时候会忘的。
  • 后台action接收类型为string的时候,请求body只能是字符串,不能传json对象。我演示这个例子时,被这点坑了。如果接收对象是一个类的时候,才可以传json对象。

没有JSON

虽然传输json数据是最常用的,但有时候我们需要支持普通的文本或者二进制信息。我们将Content-Type改为
text/plain

  1. User-Agent: Fiddler
  2. Host: localhost:5000
  3. Content-Type:text/plain
  4. Content-Length: 16

请求body:

  1. {"123456"}

悲剧的事情来,报404!

不支持text/plain

事情到此就变得稍微复杂了一些,因为asp.netcore只处理它认识的类型,如json和formdata。默认情况下,原始数据不能直接映射到控制器参数。这是个小坑,不知你踩到过没有?仔细想想,这是有道理的。MVC具有特定内容类型的映射,如果您传递的数据不符合这些内容类型,则无法转换数据,因此它假定没有匹配的端点可以处理请求。
那么怎么支持原始的请求映射呢?

支持原始正文请求

不幸的是,ASP.NET Core不允许您仅通过方法参数以任何有意义的方式捕获“原始”数据。无论如何,您需要对其进行一些自定义处理Request.Body以获取原始数据,然后对其进行反序列化。

您可以捕获原始数据Request.Body并从中直接读取原始缓冲区。

最简单,最不易侵入,但不那么明显的方法是使用一个方法接受没有参数的 POST或PUT数据,然后从Request.Body以下位置读取原始数据:

读取字符串缓冲区

  1. [HttpPost]
  2. [Route("PostText")]
  3. public async Task<string> PostText()
  4. {
  5. using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
  6. {
  7. return await reader.ReadToEndAsync();
  8. }
  9. }

这适用于一下Http和文本

  1. User-Agent: Fiddler
  2. Host: localhost:5000
  3. Content-Type: text/plain
  4. Content-Length: 6

要读取二进制数据,你可以使用以下内容:

读取byte缓冲区

  1. [HttpPost]
  2. [Route("PostBinary")]
  3. public async Task<byte[]> PostBinary()
  4. {
  5. using (var ms = new MemoryStream(2048))
  6. {
  7. await Request.Body.CopyToAsync(ms);
  8. return ms.ToArray(); // returns base64 encoded string JSON result
  9. }
  10. }

查看执行结果

接收文本内容

接收二进制数据

HttpRequest静态扩展

如果你为了方便,写了很多HttpRequest的扩展,接收参数时,可以看起来更简洁一些。

  1. public static class HttpRequestExtension
  2. {
  3. /// <summary>
  4. ///
  5. /// </summary>
  6. /// <param name="httpRequest"></param>
  7. /// <param name="encoding"></param>
  8. /// <returns></returns>
  9. public static async Task<string> GetRawBodyStringFormater(this HttpRequest httpRequest, Encoding encoding)
  10. {
  11. if (encoding == null)
  12. {
  13. encoding = Encoding.UTF8;
  14. }
  15. using (StreamReader reader = new StreamReader(httpRequest.Body, encoding))
  16. {
  17. return await reader.ReadToEndAsync();
  18. }
  19. }
  20. /// <summary>
  21. /// 二进制
  22. /// </summary>
  23. /// <param name="httpRequest"></param>
  24. /// <param name="encoding"></param>
  25. /// <returns></returns>
  26. public static async Task<byte[]> GetRawBodyBinaryFormater(this HttpRequest httpRequest, Encoding encoding)
  27. {
  28. if (encoding == null)
  29. {
  30. encoding = Encoding.UTF8;
  31. }
  32. using (StreamReader reader = new StreamReader(httpRequest.Body, encoding))
  33. {
  34. using (var ms = new MemoryStream(2048))
  35. {
  36. await httpRequest.Body.CopyToAsync(ms);
  37. return ms.ToArray(); // returns base64 encoded string JSON result
  38. }
  39. }
  40. }
  41. }
  1. [HttpPost]
  2. [Route("PostTextX")]
  3. public async Task<string> PostTextX()
  4. {
  5. return await Request.GetRawBodyStringAsyn();
  6. }
  7. /// <summary>
  8. /// 接收
  9. /// </summary>
  10. /// <returns></returns>
  11. [HttpPost]
  12. [Route("PostBinaryX")]
  13. public async Task<byte[]> PostBinaryX()
  14. {
  15. return await Request.GetRawBodyBinaryAsyn();
  16. }

自动转换文本和二进制值

上面虽然解决了原始参数转换问题,但不够友好。如果你打算像原生MVC那样自动映射参数的话,你需要做一些自定义格式化适配。

创建一个Asp.net MVC InputFormatter

ASP.NET Core使用一种干净且更通用的方式来处理内容的自定义格式InputFormatter。输入格式化程序挂钩到请求处理管道,让您查看特定类型的内容以确定是否要处理它。然后,您可以阅读请求正文并对入站内容执行自己的反序列化。
InputFormatter有几个要求

  • 您需要使用[FromBody]去获取
  • 您必须能够查看请求并确定是否以及如何处理内容。

在这个例子中,对于“原始内容”,我想查看具有以下类型的请求:

  • text/plain(文本)
  • appliaction/octet-stream(byte[])
    没有内容类型(string)

要创建格式化程序,你可以实现IInputFormatter或者从InputFormatter继承。

  1. public class RawRequestBodyFormatter : IInputFormatter
  2. {
  3. public RawRequestBodyFormatter()
  4. {
  5. }
  6. public bool CanRead(InputFormatterContext context)
  7. {
  8. if (context == null) throw new ArgumentNullException("argument is Null");
  9. var contentType = context.HttpContext.Request.ContentType;
  10. if (string.IsNullOrEmpty(contentType) || contentType == "text/plain" || contentType == "application/octet-stream")
  11. return true;
  12. return false;
  13. }
  14. public async Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
  15. {
  16. var request = context.HttpContext.Request;
  17. var contentType = context.HttpContext.Request.ContentType;
  18. if (string.IsNullOrEmpty(contentType) || contentType.ToLower() == "text/plain")
  19. {
  20. using (StreamReader reader = new StreamReader(request.Body, Encoding.UTF8))
  21. {
  22. var content = await reader.ReadToEndAsync();
  23. return await InputFormatterResult.SuccessAsync(content);
  24. }
  25. }
  26. if (contentType == "application/octet-stream")
  27. {
  28. using (StreamReader reader = new StreamReader(request.Body, Encoding.UTF8))
  29. {
  30. using (var ms = new MemoryStream(2048))
  31. {
  32. await request.Body.CopyToAsync(ms);
  33. var content = ms.ToArray();
  34. return await InputFormatterResult.SuccessAsync(content);
  35. }
  36. }
  37. }
  38. return await InputFormatterResult.FailureAsync();
  39. }
  40. }

格式化程序用于CanRead()检查对内容类型的请求以支持,然后将ReadRequestBodyAsync()内容读取和反序列化为应在控制器方法的参数中返回的结果类型。

InputFormatter必须在ConfigureServices()启动代码中注册MVC :

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddMvc(o=>o.InputFormatters.Insert(0,new RawRequestBodyFormatter())).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
  4. }

接受原始输入

  1. [HttpPost]
  2. [Route("PostTextPlus")]
  3. public string PostTextPlus([FromBody] string value)
  4. {
  5. return value;
  6. }

然后你就可以发送post请求,像这样:

  1. User-Agent: Fiddler
  2. Host: localhost:5000
  3. Content-Length: 6

或者

  1. User-Agent: Fiddler
  2. Host: localhost:5000
  3. Content-Type:text/plain
  4. Content-Length: 6

请注意,您可以使用内容类型调用相同的控制器方法application/json并传递JSON字符串,这也将起作用。在RawRequestBodyFormatter 简单地增加它支持的附加内容类型的支持。

二进制数据

  1. [HttpPost]
  2. [Route("PostBinaryPlus")]
  3. public byte[] PostBinaryPlus([FromBody] byte[] value)
  4. {
  5. return value;
  6. }

请求内容如下:

  1. User-Agent: Fiddler
  2. Host: localhost:5000
  3. Content-Length: 6
  4. Content-Type: application/octet-stream

源代码

示例代码已上传到 CsharpFanDemo

参考链接

本文包含翻译和自己实践。主要思路和代码来源于以下链接:
Accepting Raw Request Body Content in ASP.NET Core API Controllers

.Net Core WebApi控制器接收原始请求正文内容的更多相关文章

  1. ASP.NET Core Web APi获取原始请求内容

    前言 我们讲过ASP.NET Core Web APi路由绑定,本节我们来讲讲如何获取客户端请求过来的内容. ASP.NET Core Web APi捕获Request.Body内容 [HttpPos ...

  2. webapi 控制器接收POST参数时必须以对象的方式接收

    webapi    控制器接收POST参数时必须以对象的方式接收

  3. .net core webapi通过中间件获取请求和响应内容

    本文主要根据中间件来实现对.net core webapi中产生的请求和响应数据进行获取并存入日志文件中: 这里不详细介绍日志文件的使用.你可以自己接入NLog,log4net,Exceptionle ...

  4. 第十九节:Asp.Net Core WebApi基础总结和请求方式

    一. 基础总结 1.Restful服务改造 Core下的WebApi默认也是Restful格式服务,即通过请求方式(Get,post,put,delete)来区分请求哪个方法,请求的URL中不需要写方 ...

  5. ASP.NET Core WebAPI控制器返回类型的最佳选项

    前言 从.NET Core 2.1版开始,到目前为止,控制器操作可以返回三种类型的WebApi响应.这三种类型都有自己的优点和缺点,但都缺乏满足REST和高可测性的选项. ASP.NET Core中可 ...

  6. ASP.NET Core 入门教程 4、ASP.NET Core MVC控制器入门

    一.前言 1.本教程主要内容 ASP.NET Core MVC控制器简介 ASP.NET Core MVC控制器操作简介 ASP.NET Core MVC控制器操作简介返回类型简介 ASP.NET C ...

  7. ASP.NET Core 入门笔记5,ASP.NET Core MVC控制器入门

    摘抄自https://www.cnblogs.com/ken-io/p/aspnet-core-tutorial-mvc-controller-action.html 一.前言 1.本教程主要内容 A ...

  8. 关于修改.net core webapi中null默认返回的状态码。

    在asp .net core webapi中,http请求的响应数据如果是null的话,我们知道状态码会返回204,即NoContent,为什么会出现这种情况呢?   因为在返回响应数据的时候,nul ...

  9. .net core 1.1.0 MVC 控制器接收Json字串 (JObject对象) (一)

    .net core 1.1.0 MVC 控制器接收Json字串 (JObject对象) (二) Json是WEB交互常见的数据,.net core 处理方式是转为强类型,没有对应的强类型会被抛弃,有时 ...

随机推荐

  1. LeetCode题解之Largest Number

    1.题目描述 2. 将整数值转换为string  ,然后排序. 3.代码 string largestNumber(vector<int>& nums) { vector<s ...

  2. Cordova 8 架构使用sqlite - 谢厂节的博客 - 博客频道 - CSDN.NET - Google Chrome

    Cordova 8 架构使用sqlite 标签: androidcordova 2015-07-16 16:41 4302人阅读 评论(0) 收藏 举报  分类: IONIC/Cordova(18)  ...

  3. SQLSERVER中的元数据锁

    SQLSERVER中的元数据锁 网上对于元数据锁的资料真的非常少 元数据锁一般会出现在DDL语句里 下面列出数据库引擎可以锁定的资源 资源 说明 RID 用于锁定堆(heap)中的某一行 KEY 用于 ...

  4. 转:jQuery插件开发全解析

    jQuery插件的开发包括两种: 一种是类级别的插件开发,即给jQuery添加新的全局函数,相当于给jQuery类本身添加方法.jQuery的全局函数就是属于jQuery命名空间的函数,另一种是对象级 ...

  5. PowerShell发送邮件(587)

    #定义邮件服务器 $smtpServer = "mail.xx.com" $smtpUser = "sender" $smtpPassword = " ...

  6. python web编程CGI

    CGI(通用网关接口),CGI 是Web 服务器运行时外部程序的规范,按CGI 编写的程序可以扩展服务器功能. CGI 应用程序能与浏览器进行交互,还可通过数据库API 与数据库服务器等外部数据源进行 ...

  7. [Spark Core] Spark Client Job 提交三级调度框架

    0. 说明  官方文档  Job Scheduling Spark 调度核心组件: DagScheduler TaskScheduler BackendScheduler 1. DagSchedule ...

  8. Linux运维之--LVS、Nginx、HAproxy有什么区别?

    LVS: 是基于四层的转发 HAproxy: 是基于四层和七层的转发,是专业的代理服务器 Nginx: 是WEB服务器,缓存服务器,又是反向代理服务器,可以做七层的转发 区别: LVS由于是基于四层的 ...

  9. Alpha冲刺报告(7/12)(麻瓜制造者)

    今日已完成 邓弘立: 对主页UI进行了改进 符天愉: 打算开始写留言部分并且想要实现无限回复 搜索了下网上的实现方法,总结了两种方法,一种使用递归,一种使用嵌套集合.发现嵌套集合的方法很机智,,但是感 ...

  10. Django商城项目笔记No.18商品部分-数据表创建

    数据库表设计 在电商中对于商品,有两个重要的概念:SPU和SKU SPU = Standard Product Unit (标准产品单位) SPU是商品信息聚合的最小单位,是一组可服用.易检索的标准化 ...