Asp.Net Core控制器接收原始请求正文内容

作者 : 开心源码 本文共5444个字,预计阅读时间需要14分钟 发布时间: 2022-05-12 共197人阅读

主要目标

在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;        }    }

Json请求

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

User-Agent: FiddlerHost: localhost:5000Content-Type: application/jsonContent-Length: 16 

请求body:

{"123456"}

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

后端调试,查看请求输入结果
fiddler查看请求header
fiddler查看返回结果

注意!!

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

没有JSON

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

User-Agent: FiddlerHost: localhost:5000Content-Type:text/plainContent-Length: 16 

请求body:

{"123456"}

悲剧的事情来,报404!

不支持text/plain

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

支持原始正文请求

不幸的是,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: FiddlerHost: localhost:5000Content-Type: text/plainContent-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            }        }

查看执行结果

接收文本内容
接收二进制数据

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();        }

自动转换文本和二进制值

上面尽管处理了原始参数转换问题,但不够友好。假如你打算像原生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);        }

接受原始输入

        [HttpPost]        [Route("PostTextPlus")]        public string PostTextPlus([FromBody] string value)        {            return value;        }

而后你即可以发送post请求,像这样:

User-Agent: FiddlerHost: localhost:5000Content-Length: 6

或者者

User-Agent: FiddlerHost: localhost:5000Content-Type:text/plainContent-Length: 6

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

二进制数据

        [HttpPost]        [Route("PostBinaryPlus")]        public byte[] PostBinaryPlus([FromBody] byte[] value)        {            return value;        }

请求内容如下:

User-Agent: FiddlerHost: localhost:5000Content-Length: 6Content-Type: application/octet-stream

源代码

示例代码已上传到 CsharpFanDemo

参考链接

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

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Asp.Net Core控制器接收原始请求正文内容

发表回复