【问题标题】:Access Raw Request Body访问原始请求正文
【发布时间】:2015-10-19 04:00:57
【问题描述】:

我正在尝试在 ASP.net 5 中访问请求的原始输入正文/流。过去,我能够将输入流的位置重置为 0 并将其读入内存流,但是当我尝试要从上下文中执行此操作,输入流要么为空,要么引发错误(System.NotSupportedException =>“不支持指定的方法。”)。

在下面的第一个示例中,如果我将控制器方法的参数对象类型声明为动态,我可以访问控制器中的原始请求。由于各种原因,这不是一个解决方案,无论如何我都需要访问身份验证过滤器中的原始请求正文。

这个例子有效,但不是一个合理的解决方案:

[HttpPost("requestme")]
public string GetRequestBody([FromBody] dynamic body)
{   
    return body.ToString();
}

抛出错误:

[HttpPost("requestme")]
public string GetRequestBody()
{
    var m = new MemoryStream();
    Request.Body.CopyTo(m);

    var contentLength = m.Length;

    var b = System.Text.Encoding.UTF8.GetString(m.ToArray());

    return b;
}

抛出错误:

[HttpPost("requestme")]
public string GetRequestBody()
{
    Request.Body.Position = 0;
    var input = new StreamReader(Request.Body).ReadToEnd();

    return input;
}

抛出错误:

[HttpPost("requestme")]
public string GetRequestBody()
{
    Request.Body.Position = 0;
    var input = new MemoryStream();
    Request.Body.CopyTo(input);

    var inputString = System.Text.Encoding.UTF8.GetString(input.ToArray());

    return inputString;
}

我需要访问我正在构建的 API 的每个请求的原始请求正文。

任何帮助或指导将不胜感激!

编辑:

这是我要读取请求正文的代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Http;

namespace API.Filters
{
    public class CustomAuthorizationAttribute : Attribute, IAuthorizationFilter
    {
        public CustomAuthorizationAttribute()
        { }

        public void OnAuthorization(AuthorizationContext context)
        {
            if (context == null)
                throw new ArgumentNullException("OnAuthorization AuthorizationContext context can not be null.");
            else
            {
                if (this.AuthorizeCore(context.HttpContext) == false)
                {
                    // Do Other Stuff To Check Auth
                }
                else
                {
                    context.Result = new HttpUnauthorizedResult();
                }
            }
        }

        protected virtual bool AuthorizeCore(HttpContext httpContext)
        {
            var result = false;

            using (System.IO.MemoryStream m = new System.IO.MemoryStream())
            {
                try
                {
                    if (httpContext.Request.Body.CanSeek == true)
                        httpContext.Request.Body.Position = 0;

                    httpContext.Request.Body.CopyTo(m);

                    var bodyString = System.Text.Encoding.UTF8.GetString(m.ToArray());

                    return CheckBody(bodyString); // Initial Auth Check returns true/false <-- Not Shown In Code Here on Stack Overflow
                }
                catch (Exception ex)
                {
                    Logger.WriteLine(ex.Message);
                }
            }
                return false;
        }
    }
}

当调用标记有 CustomAuthorization 属性的控制器方法时,将访问此代码。

[Filters.CustomAuthorizationAuthorization]
[HttpPost]
public ActionResult Post([FromBody]UserModel Profile)
{
    // Process Profile
}

【问题讨论】:

  • 抛出的错误是什么?
  • 无论如何我都需要在身份验证过滤器中访问原始请求正文 ...这个问答可能会有所帮助stackoverflow.com/q/31464359/571637
  • @haim770 错误是我帖子的顶部,它的 System.NotSupportedException 消息->“不支持指定的方法。”

标签: asp.net-core asp.net-core-mvc asp.net-core-webapi


【解决方案1】:

我知道我迟到了,但就我而言,只是我在路由时遇到了问题,如下所示 在 startup.cs 文件中,我使用 /api 开始路由

app.MapWhen(context => context.Request.Path.StartsWithSegments(new PathString("/api")),
            a =>
            {
                //if (environment.IsDevelopment())
                //{
                //  a.UseDeveloperExceptionPage();
                //}

                a.Use(async (context, next) =>
                {
                    // API Call
                    context.Request.EnableBuffering();
                    await next();
                });
            }
        //and I was putting in controller 
    [HttpPost]
    [Route("/Register", Name = "Register")]
        //Just Changed the route to start with /api like my startup.cs file
    [HttpPost]
    [Route("/api/Register", Name = "Register")]
    //and now the params are not null and I can ready the body request multiple

【讨论】:

    【解决方案2】:

    更新
    下面的信息现在已经很过时了。由于性能原因,默认情况下这是不可能的,但幸运的是可以更改。最新的解决方案应该是使用EnableBuffering 启用请求缓冲:

    Request.EnableBuffering();
    

    有关更多信息,另请参阅此博客文章:https://devblogs.microsoft.com/aspnet/re-reading-asp-net-core-request-bodies-with-enablebuffering/


    旧的、过时的答案供参考

    Request.Body 的实现依赖于控制器动作。

    如果操作包含参数,则由Microsoft.AspNet.WebUtilities.FileBufferingReadStream 实现,支持查找(Request.Body.CanSeek == true)。该类型还支持设置Request.Body.Position

    但是,如果您的操作不包含任何参数,则它由 Microsoft.AspNet.Loader.IIS.FeatureModel.RequestBody 实现,这支持搜索 (Request.Body.CanSeek == false)。这意味着您无法调整Position 属性,您可以直接开始读取流。

    这种差异可能与 MVC 需要从请求体中提取参数值有关,因此它需要读取请求。

    在您的情况下,您的操作没有任何参数。因此使用了Microsoft.AspNet.Loader.IIS.FeatureModel.RequestBody,如果您尝试设置Position 属性,则会引发异常。


    **解决方案**:要么不设置位置,要么先检查您是否真的_可以_设置位置:
    if (Request.Body.CanSeek)
    {
        // Reset the position to zero to read from the beginning.
        Request.Body.Position = 0;
    }
    
    var input = new StreamReader(Request.Body).ReadToEnd();
    

    【讨论】:

    • 嗨,亨克!谢谢您的帮助!你知道我将如何在属性过滤器中处理这个问题吗?当我在过滤器中运行相同的代码时,它会以相同的方式出错。我试图准备原始输入正文的上下文来自 AuthorizationContext。
    • @BadgerDev 你能在过滤器中显示代码吗?您会收到什么例外情况?
    • 这是一个有趣的答案,尽管您假设 OP 正在使用 IIS。同样的评论确实适用于每个流主机,如 IIS 或 WebListener。
    • 我在 DNX 中的 IIS 8.5 上托管最终应用程序。这可能会在某个时候发生变化,因为这里的事情可能会发生变化。 @HenkMollema 我编辑了原始帖子以包含我要运行的代码示例。我尝试了您的示例,但由于某种原因,VS 2015 中的调试器只是跳过了它。
    • 只是一个后续。请求是否有参数并不重要。我还没有看到可以设置流位置的请求,当我尝试读取流时,无论其位置如何,每次请求时,流基本上是空的。 AspNet 管道中必须有一些东西在清空流或在处理流后不复制回流。
    【解决方案3】:

    您需要调用 Request.EnableRewind() 以允许流倒带以便您阅读它。

    string bodyAsString;
    Request.EnableRewind();
    using (var streamReader = new StreamReader(Request.Body, Encoding.UTF8))
    {
        bodyAsString = streamReader.ReadToEnd();
    }
    

    【讨论】:

      【解决方案4】:

      我也遇到了同样的问题。把方法签名中的参数去掉,然后你想怎么读Request.BodyStream就可以了。

      【讨论】:

      • 这很奏效。我有一个第 3 方使用错误的内容类型执行回调,这意味着我的方法始终没有记录任何内容 - 实现这一点允许我记录在我的终点抛出的任何垃圾!干杯。
      【解决方案5】:

      您在最后三个 sn-ps 中看到的异常是使用 IIS 或 WebListener 等流式主机时尝试多次读取请求正文的直接结果 - 一次通过 MVC 6,一次在您的自定义代码中。您可以查看此 SO 问题以获取更多信息:Read body twice in Asp.Net 5

      也就是说,我只希望在使用 application/x-www-form-urlencoded 时会发生这种情况,因为 MVC 开始读取带有文件上传等冗长请求的请求流是不安全的。如果不是这种情况,那么它可能是一个 MVC 错误,您应该在 https://github.com/aspnet/Mvc 上报告。

      对于解决方法,您应该查看这个 SO 答案,它解释了如何使用 context.Request.ReadFormAsync 或添加手动缓冲:Read body twice in Asp.Net 5

      app.Use(next => async context => {
          // Keep the original stream in a separate
          // variable to restore it later if necessary.
          var stream = context.Request.Body;
      
          // Optimization: don't buffer the request if
          // there was no stream or if it is rewindable.
          if (stream == Stream.Null || stream.CanSeek) {
              await next(context);
      
              return;
          }
      
          try {
              using (var buffer = new MemoryStream()) {
                  // Copy the request stream to the memory stream.
                  await stream.CopyToAsync(buffer);
      
                  // Rewind the memory stream.
                  buffer.Position = 0L;
      
                  // Replace the request stream by the memory stream.
                  context.Request.Body = buffer;
      
                  // Invoke the rest of the pipeline.
                  await next(context);
              }
          }
      
          finally {
              // Restore the original stream.
              context.Request.Body = stream;
          }
      });
      

      【讨论】:

        猜你喜欢
        • 2010-11-06
        • 2015-11-20
        • 2020-07-19
        • 2018-11-27
        • 1970-01-01
        • 1970-01-01
        • 2019-02-20
        • 2017-06-25
        • 2012-10-22
        相关资源
        最近更新 更多