.Net Core WebApi控制器接收原始请求正文内容
主要目标
在Asp.net Core控制器中,通过自定义格式化程序来映射自定义处理控制器中的“未知”内容。
在Asp.net Core控制器中,通过自定义格式化程序来映射自定义处理控制器中的“未知”内容。
简单案例
为了演示这个问题,我们用VS2017创建一个默认的Asp.net Core Web Api项目。
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase{
[HttpGet]
public ActionResult<string> Get() {
return "ok";
}
[HttpPost]
[Route("PostX")]
public ActionResult<string> Post([FromBody] string value)
{
return value;
}
}
为了演示这个问题,我们用VS2017创建一个默认的Asp.net Core Web Api项目。
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase{
[HttpGet]
public ActionResult<string> Get() {
return "ok";
}
[HttpPost]
[Route("PostX")]
public ActionResult<string> Post([FromBody] string value)
{
return value;
}
}
Json请求
我们从最常见的json输入请求开始。
User-Agent: Fiddler
Host: localhost:5000
Content-Type: application/json
Content-Length: 16
请求body:
{"123456"}
通过后台调试和fiddler抓包,我们可以看到请求输入和返回。
后台调试,查看请求输入结果
fiddler查看请求header
fiddler查看返回结果
注意!!
- 别忘了[FromBody],有时候会忘的。
- 后台action接收类型为string的时候,请求body只能是字符串,不能传json对象。我演示这个例子时,被这点坑了。如果接收对象是一个类的时候,才可以传json对象。
我们从最常见的json输入请求开始。
User-Agent: Fiddler
Host: localhost:5000
Content-Type: application/json
Content-Length: 16
请求body:
{"123456"}
通过后台调试和fiddler抓包,我们可以看到请求输入和返回。
注意!!
- 别忘了[FromBody],有时候会忘的。
- 后台action接收类型为string的时候,请求body只能是字符串,不能传json对象。我演示这个例子时,被这点坑了。如果接收对象是一个类的时候,才可以传json对象。
没有JSON
虽然传输json数据是最常用的,但有时候我们需要支持普通的文本或者二进制信息。我们将Content-Type改为
text/plain
User-Agent: Fiddler
Host: localhost:5000
Content-Type:text/plain
Content-Length: 16
请求body:
{"123456"}
悲剧的事情来,报404!
不支持text/plain
事情到此就变得稍微复杂了一些,因为asp.netcore只处理它认识的类型,如json和formdata。默认情况下,原始数据不能直接映射到控制器参数。这是个小坑,不知你踩到过没有?仔细想想,这是有道理的。MVC具有特定内容类型的映射,如果您传递的数据不符合这些内容类型,则无法转换数据,因此它假定没有匹配的端点可以处理请求。
那么怎么支持原始的请求映射呢?
虽然传输json数据是最常用的,但有时候我们需要支持普通的文本或者二进制信息。我们将Content-Type改为
text/plain
User-Agent: Fiddler
Host: localhost:5000
Content-Type:text/plain
Content-Length: 16
请求body:
{"123456"}
悲剧的事情来,报404!
事情到此就变得稍微复杂了一些,因为asp.netcore只处理它认识的类型,如json和formdata。默认情况下,原始数据不能直接映射到控制器参数。这是个小坑,不知你踩到过没有?仔细想想,这是有道理的。MVC具有特定内容类型的映射,如果您传递的数据不符合这些内容类型,则无法转换数据,因此它假定没有匹配的端点可以处理请求。
那么怎么支持原始的请求映射呢?
支持原始正文请求
不幸的是,ASP.NET Core不允许您仅通过方法参数以任何有意义的方式捕获“原始”数据。无论如何,您需要对其进行一些自定义处理Request.Body以获取原始数据,然后对其进行反序列化。
您可以捕获原始数据Request.Body并从中直接读取原始缓冲区。
最简单,最不易侵入,但不那么明显的方法是使用一个方法接受没有参数的 POST或PUT数据,然后从Request.Body以下位置读取原始数据:
不幸的是,ASP.NET Core不允许您仅通过方法参数以任何有意义的方式捕获“原始”数据。无论如何,您需要对其进行一些自定义处理Request.Body以获取原始数据,然后对其进行反序列化。
您可以捕获原始数据Request.Body并从中直接读取原始缓冲区。
最简单,最不易侵入,但不那么明显的方法是使用一个方法接受没有参数的 POST或PUT数据,然后从Request.Body以下位置读取原始数据:
读取字符串缓冲区
[HttpPost]
[Route("PostText")]
public async Task<string> PostText()
{
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
return await reader.ReadToEndAsync();
}
}
这适用于一下Http和文本
User-Agent: Fiddler
Host: localhost:5000
Content-Type: text/plain
Content-Length: 6
要读取二进制数据,你可以使用以下内容:
[HttpPost]
[Route("PostText")]
public async Task<string> PostText()
{
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
return await reader.ReadToEndAsync();
}
}
这适用于一下Http和文本
User-Agent: Fiddler
Host: localhost:5000
Content-Type: text/plain
Content-Length: 6
要读取二进制数据,你可以使用以下内容:
读取byte缓冲区
[HttpPost]
[Route("PostBinary")]
public async Task<byte[]> PostBinary()
{
using (var ms = new MemoryStream(2048))
{
await Request.Body.CopyToAsync(ms);
return ms.ToArray(); // returns base64 encoded string JSON result
}
}
[HttpPost]
[Route("PostBinary")]
public async Task<byte[]> PostBinary()
{
using (var ms = new MemoryStream(2048))
{
await Request.Body.CopyToAsync(ms);
return ms.ToArray(); // returns base64 encoded string JSON result
}
}
查看执行结果
接收文本内容
接收二进制数据
HttpRequest静态扩展
如果你为了方便,写了很多HttpRequest的扩展,接收参数时,可以看起来更简洁一些。
public static class HttpRequestExtension
{
/// <summary>
///
/// </summary>
/// <param name="httpRequest"></param>
/// <param name="encoding"></param>
/// <returns></returns>
public static async Task<string> GetRawBodyStringFormater(this HttpRequest httpRequest, Encoding encoding)
{
if (encoding == null)
{
encoding = Encoding.UTF8;
}
using (StreamReader reader = new StreamReader(httpRequest.Body, encoding))
{
return await reader.ReadToEndAsync();
}
}
/// <summary>
/// 二进制
/// </summary>
/// <param name="httpRequest"></param>
/// <param name="encoding"></param>
/// <returns></returns>
public static async Task<byte[]> GetRawBodyBinaryFormater(this HttpRequest httpRequest, Encoding encoding)
{
if (encoding == null)
{
encoding = Encoding.UTF8;
}
using (StreamReader reader = new StreamReader(httpRequest.Body, encoding))
{
using (var ms = new MemoryStream(2048))
{
await httpRequest.Body.CopyToAsync(ms);
return ms.ToArray(); // returns base64 encoded string JSON result
}
}
}
}
[HttpPost]
[Route("PostTextX")]
public async Task<string> PostTextX()
{
return await Request.GetRawBodyStringAsyn();
}
/// <summary>
/// 接收
/// </summary>
/// <returns></returns>
[HttpPost]
[Route("PostBinaryX")]
public async Task<byte[]> PostBinaryX()
{
return await Request.GetRawBodyBinaryAsyn();
}
如果你为了方便,写了很多HttpRequest的扩展,接收参数时,可以看起来更简洁一些。
public static class HttpRequestExtension
{
/// <summary>
///
/// </summary>
/// <param name="httpRequest"></param>
/// <param name="encoding"></param>
/// <returns></returns>
public static async Task<string> GetRawBodyStringFormater(this HttpRequest httpRequest, Encoding encoding)
{
if (encoding == null)
{
encoding = Encoding.UTF8;
}
using (StreamReader reader = new StreamReader(httpRequest.Body, encoding))
{
return await reader.ReadToEndAsync();
}
}
/// <summary>
/// 二进制
/// </summary>
/// <param name="httpRequest"></param>
/// <param name="encoding"></param>
/// <returns></returns>
public static async Task<byte[]> GetRawBodyBinaryFormater(this HttpRequest httpRequest, Encoding encoding)
{
if (encoding == null)
{
encoding = Encoding.UTF8;
}
using (StreamReader reader = new StreamReader(httpRequest.Body, encoding))
{
using (var ms = new MemoryStream(2048))
{
await httpRequest.Body.CopyToAsync(ms);
return ms.ToArray(); // returns base64 encoded string JSON result
}
}
}
}
[HttpPost]
[Route("PostTextX")]
public async Task<string> PostTextX()
{
return await Request.GetRawBodyStringAsyn();
}
/// <summary>
/// 接收
/// </summary>
/// <returns></returns>
[HttpPost]
[Route("PostBinaryX")]
public async Task<byte[]> PostBinaryX()
{
return await Request.GetRawBodyBinaryAsyn();
}
自动转换文本和二进制值
上面虽然解决了原始参数转换问题,但不够友好。如果你打算像原生MVC那样自动映射参数的话,你需要做一些自定义格式化适配。
上面虽然解决了原始参数转换问题,但不够友好。如果你打算像原生MVC那样自动映射参数的话,你需要做一些自定义格式化适配。
创建一个Asp.net MVC InputFormatter
ASP.NET Core使用一种干净且更通用的方式来处理内容的自定义格式InputFormatter。输入格式化程序挂钩到请求处理管道,让您查看特定类型的内容以确定是否要处理它。然后,您可以阅读请求正文并对入站内容执行自己的反序列化。
InputFormatter有几个要求
- 您需要使用[FromBody]去获取
- 您必须能够查看请求并确定是否以及如何处理内容。
在这个例子中,对于“原始内容”,我想查看具有以下类型的请求:
- text/plain(文本)
- appliaction/octet-stream(byte[])
没有内容类型(string)
要创建格式化程序,你可以实现IInputFormatter或者从InputFormatter继承。
public class RawRequestBodyFormatter : IInputFormatter
{
public RawRequestBodyFormatter()
{
}
public bool CanRead(InputFormatterContext context)
{
if (context == null) throw new ArgumentNullException("argument is Null");
var contentType = context.HttpContext.Request.ContentType;
if (string.IsNullOrEmpty(contentType) || contentType == "text/plain" || contentType == "application/octet-stream")
return true;
return false;
}
public async Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
var contentType = context.HttpContext.Request.ContentType;
if (string.IsNullOrEmpty(contentType) || contentType.ToLower() == "text/plain")
{
using (StreamReader reader = new StreamReader(request.Body, Encoding.UTF8))
{
var content = await reader.ReadToEndAsync();
return await InputFormatterResult.SuccessAsync(content);
}
}
if (contentType == "application/octet-stream")
{
using (StreamReader reader = new StreamReader(request.Body, Encoding.UTF8))
{
using (var ms = new MemoryStream(2048))
{
await request.Body.CopyToAsync(ms);
var content = ms.ToArray();
return await InputFormatterResult.SuccessAsync(content);
}
}
}
return await InputFormatterResult.FailureAsync();
}
}
格式化程序用于CanRead()检查对内容类型的请求以支持,然后将ReadRequestBodyAsync()内容读取和反序列化为应在控制器方法的参数中返回的结果类型。
InputFormatter必须在ConfigureServices()启动代码中注册MVC :
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(o=>o.InputFormatters.Insert(0,new RawRequestBodyFormatter())).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
ASP.NET Core使用一种干净且更通用的方式来处理内容的自定义格式InputFormatter。输入格式化程序挂钩到请求处理管道,让您查看特定类型的内容以确定是否要处理它。然后,您可以阅读请求正文并对入站内容执行自己的反序列化。
InputFormatter有几个要求
- 您需要使用[FromBody]去获取
- 您必须能够查看请求并确定是否以及如何处理内容。
在这个例子中,对于“原始内容”,我想查看具有以下类型的请求:
- text/plain(文本)
- appliaction/octet-stream(byte[])
没有内容类型(string)
要创建格式化程序,你可以实现IInputFormatter或者从InputFormatter继承。
public class RawRequestBodyFormatter : IInputFormatter
{
public RawRequestBodyFormatter()
{
}
public bool CanRead(InputFormatterContext context)
{
if (context == null) throw new ArgumentNullException("argument is Null");
var contentType = context.HttpContext.Request.ContentType;
if (string.IsNullOrEmpty(contentType) || contentType == "text/plain" || contentType == "application/octet-stream")
return true;
return false;
}
public async Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
var contentType = context.HttpContext.Request.ContentType;
if (string.IsNullOrEmpty(contentType) || contentType.ToLower() == "text/plain")
{
using (StreamReader reader = new StreamReader(request.Body, Encoding.UTF8))
{
var content = await reader.ReadToEndAsync();
return await InputFormatterResult.SuccessAsync(content);
}
}
if (contentType == "application/octet-stream")
{
using (StreamReader reader = new StreamReader(request.Body, Encoding.UTF8))
{
using (var ms = new MemoryStream(2048))
{
await request.Body.CopyToAsync(ms);
var content = ms.ToArray();
return await InputFormatterResult.SuccessAsync(content);
}
}
}
return await InputFormatterResult.FailureAsync();
}
}
格式化程序用于CanRead()检查对内容类型的请求以支持,然后将ReadRequestBodyAsync()内容读取和反序列化为应在控制器方法的参数中返回的结果类型。
InputFormatter必须在ConfigureServices()启动代码中注册MVC :
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(o=>o.InputFormatters.Insert(0,new RawRequestBodyFormatter())).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
接受原始输入
[HttpPost]
[Route("PostTextPlus")]
public string PostTextPlus([FromBody] string value)
{
return value;
}
然后你就可以发送post请求,像这样:
User-Agent: Fiddler
Host: localhost:5000
Content-Length: 6
或者
User-Agent: Fiddler
Host: localhost:5000
Content-Type:text/plain
Content-Length: 6
请注意,您可以使用内容类型调用相同的控制器方法application/json并传递JSON字符串,这也将起作用。在RawRequestBodyFormatter 简单地增加它支持的附加内容类型的支持。
[HttpPost]
[Route("PostTextPlus")]
public string PostTextPlus([FromBody] string value)
{
return value;
}
然后你就可以发送post请求,像这样:
User-Agent: Fiddler
Host: localhost:5000
Content-Length: 6
或者
User-Agent: Fiddler
Host: localhost:5000
Content-Type:text/plain
Content-Length: 6
请注意,您可以使用内容类型调用相同的控制器方法application/json并传递JSON字符串,这也将起作用。在RawRequestBodyFormatter 简单地增加它支持的附加内容类型的支持。
二进制数据
[HttpPost]
[Route("PostBinaryPlus")]
public byte[] PostBinaryPlus([FromBody] byte[] value)
{
return value;
}
请求内容如下:
User-Agent: Fiddler
Host: localhost:5000
Content-Length: 6
Content-Type: application/octet-stream
[HttpPost]
[Route("PostBinaryPlus")]
public byte[] PostBinaryPlus([FromBody] byte[] value)
{
return value;
}
请求内容如下:
User-Agent: Fiddler
Host: localhost:5000
Content-Length: 6
Content-Type: application/octet-stream
源代码
示例代码已上传到 CsharpFanDemo
示例代码已上传到 CsharpFanDemo
参考链接
本文包含翻译和自己实践。主要思路和代码来源于以下链接:
Accepting Raw Request Body Content in ASP.NET Core API Controllers
.Net Core WebApi控制器接收原始请求正文内容的更多相关文章
- ASP.NET Core Web APi获取原始请求内容
前言 我们讲过ASP.NET Core Web APi路由绑定,本节我们来讲讲如何获取客户端请求过来的内容. ASP.NET Core Web APi捕获Request.Body内容 [HttpPos ...
- webapi 控制器接收POST参数时必须以对象的方式接收
webapi 控制器接收POST参数时必须以对象的方式接收
- .net core webapi通过中间件获取请求和响应内容
本文主要根据中间件来实现对.net core webapi中产生的请求和响应数据进行获取并存入日志文件中: 这里不详细介绍日志文件的使用.你可以自己接入NLog,log4net,Exceptionle ...
- 第十九节:Asp.Net Core WebApi基础总结和请求方式
一. 基础总结 1.Restful服务改造 Core下的WebApi默认也是Restful格式服务,即通过请求方式(Get,post,put,delete)来区分请求哪个方法,请求的URL中不需要写方 ...
- ASP.NET Core WebAPI控制器返回类型的最佳选项
前言 从.NET Core 2.1版开始,到目前为止,控制器操作可以返回三种类型的WebApi响应.这三种类型都有自己的优点和缺点,但都缺乏满足REST和高可测性的选项. ASP.NET Core中可 ...
- ASP.NET Core 入门教程 4、ASP.NET Core MVC控制器入门
一.前言 1.本教程主要内容 ASP.NET Core MVC控制器简介 ASP.NET Core MVC控制器操作简介 ASP.NET Core MVC控制器操作简介返回类型简介 ASP.NET C ...
- ASP.NET Core 入门笔记5,ASP.NET Core MVC控制器入门
摘抄自https://www.cnblogs.com/ken-io/p/aspnet-core-tutorial-mvc-controller-action.html 一.前言 1.本教程主要内容 A ...
- 关于修改.net core webapi中null默认返回的状态码。
在asp .net core webapi中,http请求的响应数据如果是null的话,我们知道状态码会返回204,即NoContent,为什么会出现这种情况呢? 因为在返回响应数据的时候,nul ...
- .net core 1.1.0 MVC 控制器接收Json字串 (JObject对象) (一)
.net core 1.1.0 MVC 控制器接收Json字串 (JObject对象) (二) Json是WEB交互常见的数据,.net core 处理方式是转为强类型,没有对应的强类型会被抛弃,有时 ...
随机推荐
- 使用代码段遍历,枚举类型Enum
最近项目中定义了一些枚举类型,需要将枚举的键值传给前端,用于制作下拉菜单. 1.首先定义了枚举类型 public enum 请假类型 : int { 病假 = 1, 事假 = 2, 婚假 = 3, 产 ...
- 万恶的浏览器缓存 Vuex state里面的成员改名后浏览器不会马上更新
今天在用Vuex的时候,在state里面加了个名叫rootUrl的属性 但是怎么都取不到值,重新启动程序,ctrl+f5浏览器刷新都不行,纠结了大半上午,于是用console.log(store.ge ...
- Freemarket语法
<#--freemarker HashMap取值--> <#assign maps={"1":"张三丰","2":&quo ...
- windows使用
将桌面.我的文档.收藏夹等转移到其他盘 方法很多,介绍如下: 一.新装的系统,桌面.我的文档.收藏夹等都是默认在C盘的,并且这些数据都是用户经常用到的一些数据.为了避免以后系统崩溃所带来的危险,最好的 ...
- 自动代码质量分析(GitLab+JenKins+SonarQube)
自动代码质量分析(GitLab+JenKins+SonarQube) 1.需求场景 开发提交代码自动执行代码质量分析. 2.所需应用 GitLab,JenKins,SonarQube 3.架构图 4. ...
- spring-bean 版本的问题(报错:org.xml.sax.SAXParseException; lineNumber: 14; columnNumber: 75;)
当XML中配置的xsd是4.0,而引用的包是4以下的spring-bean.jar时,当服务器能连网时没问题,不能连网时,就报以下类似错误: org.xml.sax.SAXParseException ...
- fatal: cannot create directoryxxxx': Invalid argument
问题:fatal: cannot create directoryxxxx': Invalid argument 环境:git 拉取远程仓库的代码后 原因:同事是mac,这个文件夹命名规则在苹果上没有 ...
- 服务器安装安装Office2007以上版本注意事项
1.安装Office2007以上版本.(如安装的是Office2007需安装SaveAsPDFandXPS.exe组件) 2.确认网站在IIS内使用的登录用户.(如图所示用户为IUSR,下面操作以此用 ...
- js常见执行方法window.onload = function (){},$(document).ready()
1. window.onload = function(){}; 当页面DOM对象加载完毕,web浏览器能够运行JS时,此方法即被触发. 2. $(document).ready();当web页面以及 ...
- 1-100求和 sum(range(101))
print(sum(range(101))) s = 0for i in range(101): s += iprint(s)