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


    主要目标

    在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: Fiddler
    Host: localhost:5000
    Content-Type: application/json
    Content-Length: 16 
    

    请求body:

    {"123456"}
    

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

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

    fiddler查看请求header

    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具有特定内容类型的映射,如果您传递的数据不符合这些内容类型,则无法转换数据,因此它假定没有匹配的端点可以处理请求。
    那么怎么支持原始的请求映射呢?

    支持原始正文请求

    不幸的是,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
    

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

    读取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: 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
    

    源代码

    示例代码已上传到 CsharpFanDemo

    参考链接

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

  • 相关阅读:
    jsp中的绝对路径、相对路径和访问jsp的方式
    Java链接MySQL数据库的用配置文件和不用配置文件的代码
    易宝支付开发文档
    安装Visual Studio IntelliCode提供代码智能提示AI
    Generator生成器函数执行过程的理解
    git pull --rebase的理解
    react-native-cli运行项目及打包apk失败的解决过程
    bfc与浮动元素的关系
    触发bfc解决父子元素嵌套垂直方向margin塌陷问题
    html文本或元素6px间距问题
  • 原文地址:https://www.cnblogs.com/fancunwei/p/9567497.html
Copyright © 2020-2023  润新知