前言
我们讲过 ASP.NET Core web APi 路由绑定, 本节我们来讲讲如何获取客户端请求过来的内容
ASP.NET Core Web APi 捕获 Request.Body 内容
- [HttpPost]
- [Route("api/blog/jsonstring")]
- public string Index([FromBody] string content)
- {
- return content;
- }
- // 或者
- [HttpPost("api/blog/jsonstring")]
- public string Index([FromBody] string content)
- {
- return content;
- }
由上图我们能够看到发出的为 Post 请求且 Content-Type 为 application/json, 所以此时在后台接受请求需要通过 FromBody 特性接受来自 Post 请求中的 Body 内容这里需要特别说明的是: 当在 vue 利用 axios 发出 Post 请求且参数为简单类型参数时, 但是在后台只能利用对象接收, 即不能按照如下形式接收参数
- [HttpPost("api/blog/jsonstring")]
- public int Index([FromBody] int id)
- {
- return id;
- }
对于简单类型参数此时无需额外再定义对象来接收, 我们可以采用变通的方式来接收即动态对象
- [HttpPost("api/blog/jsonstring")]
- public int Index([FromBody] dynaminc dy)
- {
- var id = dy.id as int;
- return id;
- }
- // 或者
- [HttpPost("api/blog/jsonstring")]
- public int Index([FromBody] JObject jo)
- {
- JToken token = jo["id"];
- if(token.Type == JTokenType.Null)
- {
- // Do your logic
- }
- ......
- }
上述两种方式皆可, 利用 JObject 好处在于可判断参数是否为空情况, 而 dynamic 可能会出现异常如果我们想发送一个 RAW 字符串或者二进制数据, 并且想要把它作为请求的一部分, 那么这个时候就有意思, 同时事情也会变得更加复杂因为 ASP.NET Core 只会处理它所知道的信息, 默认情况下是 JSON 和 Form 数据 默认情况下原始数据不能直接映射到控制器上的方法参数上什么意思呢? 接下来我们若改变 Content-Type 为 text/plain, 此时将返回状态为 415, 如下图
那么对于此种情况, 我们该如何获取请求参数呢? 不幸的是, ASP NET Core 不允许我们仅仅通过方法参数以任何有意义的方式捕获原始数据因此我们需要通过处理 Request.Body 来获取原始数据, 然后反序列化它
我们可以捕获原始的 Request.Body 并从原始缓冲区中读取参数最简而有效的方法是接受不带参数的 POST 或 PUT 数据, 然后从 Request.Body 读取原始数据:
- [HttpPost("api/blog/jsonstring")]
- public async Task<string> Index()
- {
- var result = string.Empty;
- using (var reader = new StreamReader(Request.Body, Encoding.UTF8))
- {
- result = await reader.ReadToEndAsync();
- }
- return result;
- }
若我们想要读取二进制数据, 我们可如下操作
- [HttpPost("api/blog/bytes")]
- public async Task<byte[]> RawBinaryData()
- {
- using (var ms = new MemoryStream(2048))
- {
- await Request.Body.CopyToAsync(ms);
- return ms.ToArray();
- }
- }
代码中的结果被捕获为二进制字节并以 JSON 返回, 这也就是为什么我们会从上图看到 base64 编码的结果字符串伪装成二进制结果的原因
像上述操作若很频繁我们完全可以封装起来, 比如第三方调用我们接口时封装如下:
- public static class HttpRequestExtensions
- {
- public static async Task<string> GetRawBodyStringAsync(this HttpRequest request, Encoding encoding = null)
- {
- if (encoding is null)
- encoding = Encoding.UTF8;
- using (var reader = new StreamReader(request.Body, encoding))
- return await reader.ReadToEndAsync();
- }
- public static async Task<byte[]> GetRawBodyBytesAsync(this HttpRequest request)
- {
- using (var ms = new MemoryStream(2048))
- {
- await request.Body.CopyToAsync(ms);
- return ms.ToArray();
- }
- }
- }
如果我们期望对于原始参数使用确定的方法, 那么我们还需要做更多额外的工作也就是说需要自定义 InputFormatter 在 ASP.NET Core 中通过使用 InputFormatter 来自定义格式化内容, 并将自定义格式化内容注入到请求 ASP.NET Core 管道中, 并根据特定参数类型来决定是否需要处理请求内容最终请求运行通过则将请求主体进行反序列化对于自定义 InputFormatter 需要满足以下两个条件
(1)必须使用 [FromBody] 特性来触发它
(2) 对于对应的请求内容需自定义对应处理
- public class HandleRequestBodyFormatter : InputFormatter
- {
- public HandleRequestBodyFormatter()
- {
- SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
- SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/octet-stream"));
- }
- /// <summary>
- /// 允许 text/plain, application/octet-stream 和没有 Content-Type 的参数类型解析到原始数据
- /// </summary>
- /// <param name="context"></param>
- /// <returns></returns>
- public override Boolean CanRead(InputFormatterContext context)
- {
- if (context == null) throw new ArgumentNullException(nameof(context));
- var contentType = context.HttpContext.Request.ContentType;
- if (string.IsNullOrEmpty(contentType) || contentType == "text/plain" ||
- contentType == "application/octet-stream")
- return true;
- return false;
- }
- /// <summary>
- /// 处理 text/plain 或者没有 Content-Type 作为字符串结果
- /// 处理 application/octet-stream 类型作为 byte[]数组结果
- /// </summary>
- /// <param name="context"></param>
- /// <returns></returns>
- public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
- {
- var request = context.HttpContext.Request;
- var contentType = context.HttpContext.Request.ContentType;
- if (string.IsNullOrEmpty(contentType) || contentType == "text/plain")
- {
- using (var reader = new StreamReader(request.Body))
- {
- var content = await reader.ReadToEndAsync();
- return await InputFormatterResult.SuccessAsync(content);
- }
- }
- if (contentType == "application/octet-stream")
- {
- using (var ms = new MemoryStream(2048))
- {
- await request.Body.CopyToAsync(ms);
- var content = ms.ToArray();
- return await InputFormatterResult.SuccessAsync(content);
- }
- }
- return await InputFormatterResult.FailureAsync();
- }
- }
上述自定义 InputFormatter 使用 CanRead 方法来检查要支持的请求内容类型, 然后使用 ReadRequestBodyAsync 方法将内容进行读取并反序列化为类型结果, 该结果类型在控制器中的方法参数中返回
最后需要做的则是将我们自定义的 InputFormatter 注入到 MVC 请求管道中一切大功告成
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddMvc(o => o.InputFormatters.Insert(0, new HandleRequestBodyFormatter()));
- }
通过对请求输入参数的自定义格式化处理, 我们现在可以使用来自客户端 Post 或者 Put 请求且类型为 text/plain, application/octet-stream 或者没有类型下面我们来通过例子说明
- [HttpPost("api/blog/rawstring")]
- public string RawRequestBodyFormatter([FromBody] string rawString)
- {
- return rawString;
- }
ASP.NET Core 自身本来就支持 application/json 类型, 我们通过自定义 InputFormatter 不过是多了对 text/plain 额外类型的支持而已
- [HttpPost("api/blog/rawbytes")]
- public byte[] RawBytesFormatter([FromBody] byte[] rawData)
- {
- return rawData;
- }
总结
本文比较详细的讲解了在 ASP.NET Core WebAPi 中如何获取请求原始 Body 中的参数并添加了额外类型的支持, 希望对阅读本文的您有所帮助
来源: https://www.cnblogs.com/CreateMyself/p/8410686.html