如何在asp.net核心webapi控制器中读取请求体?

Kas*_*tha 71 c# asp.net-web-api asp.net-core

我正试图在OnActionExecuting方法中读取请求体,但我总是null为身体做准备.

var request = context.HttpContext.Request;
var stream = new StreamReader(request.Body);
var body = stream.ReadToEnd();
Run Code Online (Sandbox Code Playgroud)

我试图将流位置显式设置为0,但这也不起作用.由于这是ASP.NET CORE,我认为事情没有什么不同.我可以在这里看到所有的示例,指的是旧的webapi版本.
有没有其他方法这样做?

Jea*_*ean 92

在ASP.Net Core中,读取身体请求的次数似乎很复杂,但是如果你的第一次尝试以正确的方式进行,那么下次尝试你应该没问题.

我通过替换正文流来阅读几个转变,但我认为以下是最干净的:

最重要的一点是

  1. 让请求知道您将阅读其身体两次或更多次,
  2. 不要关闭身体流,和
  3. 将它倒回到初始位置,这样内部过程就不会丢失.

[编辑]

正如Murad所指出的,您也可以利用.Net Core 2.1扩展:EnableBuffering它将大量请求存储到磁盘上而不是将其保存在内存中,从而避免存储在内存中的大流问题(文件,图像......) .您可以通过设置ASPNETCORE_TEMP环境变量来更改临时文件夹,并在请求结束后删除文件.

在AuthorizationFilter中,您可以执行以下操作:

// Helper to enable request stream rewinds
using Microsoft.AspNetCore.Http.Internal;
[...]
public class EnableBodyRewind : Attribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var bodyStr = "";
        var req = context.HttpContext.Request;

        // Allows using several time the stream in ASP.Net Core
        req.EnableRewind(); 

        // Arguments: Stream, Encoding, detect encoding, buffer size 
        // AND, the most important: keep stream opened
        using (StreamReader reader 
                  = new StreamReader(req.Body, Encoding.UTF8, true, 1024, true))
        {
            bodyStr = reader.ReadToEnd();
        }

        // Rewind, so the core is not lost when it looks the body for the request
        req.Body.Position = 0;

        // Do whatever work with bodyStr here

    }
}



public class SomeController : Controller
{
    [HttpPost("MyRoute")]
    [EnableBodyRewind]
    public IActionResult SomeAction([FromBody]MyPostModel model )
    {
        // play the body string again
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以在请求处理程序中再次使用正文.

在你的情况下,如果你得到一个null结果,这可能意味着已经在早期阶段读取了正文.在这种情况下,您可能需要使用中间件(见下文).

但是,如果处理大流,请小心,该行为意味着所有内容都已加载到内存中,在文件上载时不应触发此操作.

您可能希望将其用作中间件

我看起来像这样(再次,如果你下载/上传大文件,应该禁用它以避免内存问题):

public sealed class BodyRewindMiddleware
{
    private readonly RequestDelegate _next;

    public BodyRewindMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        try { context.Request.EnableRewind(); } catch { }
        await _next(context);
        // context.Request.Body.Dipose() might be added to release memory, not tested
    }
}
public static class BodyRewindExtensions
{
    public static IApplicationBuilder EnableRequestBodyRewind(this IApplicationBuilder app)
    {
        if (app == null)
        {
            throw new ArgumentNullException(nameof(app));
        }

        return app.UseMiddleware<BodyRewindMiddleware>();
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 即使我倒回到位置 0,流仍然是空的。 (2认同)
  • 你用过 `req.EnableRewind();` 吗?我使用上面的代码,效果很好。 (2认同)
  • 使用过 req.EnableRewind(); 不起作用。我得到 Position = 0,body length = 26,但读取 'body' 流出现了一个空字符串。 (2认同)
  • 也可以使用`request.EnableBuffering()`(包装在`EnableRewind()`上)在ASP.NET Core 2.1中可用https://docs.microsoft.com/zh-cn/dotnet/api/microsoft .aspnetcore.http.httprequestrewindextensions.enablebuffering?view = aspnetcore-2.1 (2认同)

小智 21

在 .NET Core 3.1 中添加响应缓冲的一种快速方法是

    app.Use((context, next) =>
    {
        context.Request.EnableBuffering();
        return next();
    });
Run Code Online (Sandbox Code Playgroud)

在 Startup.cs 中。我发现这也保证在读取流之前启用缓冲,这对于 .Net Core 3.1 和我见过的其他一些中间件/授权过滤器答案来说是一个问题。

然后你可以HttpContext.Request.Body在你的处理程序中读取你的请求正文,正如其他几个人所建议的那样。

另外值得考虑的是,它EnableBuffering具有重载,允许您在使用临时文件之前限制它在内存中的缓冲量,以及对您的缓冲的总体限制。请注意,如果请求超过此限制,则会引发异常并且该请求将永远不会到达您的处理程序。

  • @EbramShehata - 正确的顺序是什么? (8认同)
  • 在没有 @EbramShehata 回复的情况下,我发现这特别需要放在对 app.UseEndpoints 的任何调用之前。 (5认同)

Sao*_*Biz 16

为了能够回复请求主体,@ Jean的回答帮助我提出了一个似乎运行良好的解决方案.我目前使用它来处理Global Exception Handler Middleware,但原理是一样的.

我创建了一个中间件,它基本上可以在请求体(而不是装饰器)上进行倒带.

using Microsoft.AspNetCore.Http.Internal;
[...]
public class EnableRequestRewindMiddleware
{
    private readonly RequestDelegate _next;

    public EnableRequestRewindMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        context.Request.EnableRewind();
        await _next(context);
    }
}

public static class EnableRequestRewindExtension
{
    public static IApplicationBuilder UseEnableRequestRewind(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<EnableRequestRewindMiddleware>();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后可以在您的Startup.cs喜欢中使用它:

[...]
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    [...]
    app.UseEnableRequestRewind();
    [...]
}
Run Code Online (Sandbox Code Playgroud)

使用这种方法,我已经能够成功地回滚请求正文流.


Ali*_*Alp 12

我认为编写扩展方法是最有效的方法

 public static string PeekBody(this HttpRequest request)
        {
            try
            {
                request.EnableBuffering();
                var buffer = new byte[Convert.ToInt32(request.ContentLength)];
                request.Body.Read(buffer, 0, buffer.Length);
                return Encoding.UTF8.GetString(buffer);
            }
            finally
            {
                request.Body.Position = 0;
            }
        }
Run Code Online (Sandbox Code Playgroud)

您也可以使用Request.Body.Peeker Nuget Package(源代码

//Return string
var request = HttpContext.Request.PeekBody();

//Return in expected type
LoginRequest request = HttpContext.Request.PeekBody<LoginRequest>();

//Return in expected type asynchronously
LoginRequest request = await HttpContext.Request.PeekBodyAsync<LoginRequest>();
Run Code Online (Sandbox Code Playgroud)


And*_*iod 11

在ASP.Net Core 2.1中可以使用更清晰的解决方案。

过滤等级

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc.Filters;

public class ReadableBodyStreamAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        context.HttpContext.Request.EnableRewind();
    }
}
Run Code Online (Sandbox Code Playgroud)

在控制器中

[HttpPost]
[ReadableBodyStream]
public string SomePostMethod()
{
    using (StreamReader stream = new StreamReader(HttpContext.Request.Body))
    {
        string body = stream.ReadToEnd();
        // body = "param=somevalue&param2=someothervalue"
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 对于netcore3.0,它将是[.EnableBuffering()](/sf/answers/4066276091/)而不是`.EnableRewind()` (6认同)
  • 这仅在将“AuthorizeAttribute”更改为“Attribute”(在 ASP.Net Core 3.1 中)后对我有用。 (3认同)

Ale*_*xei 11

我在使用 ASP.NET Core 2.1 时遇到了类似的问题:

  • 我需要一个自定义中间件来读取 POSTed 数据并对其执行一些安全检查
  • 使用授权过滤器并不实用,因为大量操作都会受到影响
  • 我必须允许在操作中绑定对象([FromBody] someObject)。感谢SaoBiz您指出这个解决方案。

因此,显而易见的解决方案是允许请求可回滚,但要确保在读取正文后,绑定仍然有效。

启用RequestRewind中间件

public class EnableRequestRewindMiddleware
{
    private readonly RequestDelegate _next;

    ///<inheritdoc/>
    public EnableRequestRewindMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public async Task Invoke(HttpContext context)
    {
        context.Request.EnableBuffering(); // this used to be EnableRewind
        await _next(context);
    }
}
Run Code Online (Sandbox Code Playgroud)

启动.cs

(将其放在配置方法的开头)

app.UseMiddleware<EnableRequestRewindMiddleware>();
Run Code Online (Sandbox Code Playgroud)

其他一些中间件

这是中间件的一部分,需要解压 POST 信息以进行检查。

using (var stream = new MemoryStream())
{
    // make sure that body is read from the beginning
    context.Request.Body.Seek(0, SeekOrigin.Begin);
    context.Request.Body.CopyTo(stream);
    string requestBody = Encoding.UTF8.GetString(stream.ToArray());

    // this is required, otherwise model binding will return null
    context.Request.Body.Seek(0, SeekOrigin.Begin);
}
Run Code Online (Sandbox Code Playgroud)


nas*_*ski 11

最近我遇到了一个非常优雅的解决方案,它接受你不知道结构的随机 JSON:

    [HttpPost]
    public JsonResult Test([FromBody] JsonElement json)
    {
        return Json(json);
    }
Run Code Online (Sandbox Code Playgroud)

就是那么容易。

  • 谢谢你,它确实有效。我使用了 JsonElement 的 GetRawText() 方法并收到了我的 JSON 文本。 (6认同)
  • 如果 DTO 在创建时进行一些处理(例如设置默认值或其他内容),则不会为您提供实际的请求正文。 (2认同)

小智 9

这是一个有点旧的线程,但是自从我来到这里后,我想我会发布我的发现,以便他们可以帮助其他人。

首先,我遇到了同样的问题,我想获取 Request.Body 并用它做一些事情(记录/审计)。但除此之外,我希望端点看起来相同。

因此,EnableBuffering() 调用似乎可以解决问题。然后你可以在 body 上做一个 Seek(0,xxx) 并重新读取内容等。

然而,这导致了我的下一个问题。访问端点时,我会收到“不允许同步操作”异常。因此,解决方法是在选项中设置属性 AllowSynchronousIO = true。有很多方法可以做到这一点(但这里的细节并不重要..)

那么,下一个问题是,当我去阅读 Request.Body 时,它已经被处理掉了。啊。那么,什么给?

我在 endpiont 调用中使用 Newtonsoft.JSON 作为我的 [FromBody] 解析器。这就是同步读取的原因,并且在完成时关闭流。解决方案?在进行 JSON 解析之前读取流?当然,这有效,我最终得到了这个:

 /// <summary>
/// quick and dirty middleware that enables buffering the request body
/// </summary>
/// <remarks>
/// this allows us to re-read the request body's inputstream so that we can capture the original request as is
/// </remarks>
public class ReadRequestBodyIntoItemsAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        if (context == null) return;

        // NEW! enable sync IO beacuse the JSON reader apparently doesn't use async and it throws an exception otherwise
        var syncIOFeature = context.HttpContext.Features.Get<IHttpBodyControlFeature>();
        if (syncIOFeature != null)
        {
            syncIOFeature.AllowSynchronousIO = true;

            var req = context.HttpContext.Request;

            req.EnableBuffering();

            // read the body here as a workarond for the JSON parser disposing the stream
            if (req.Body.CanSeek)
            {
                req.Body.Seek(0, SeekOrigin.Begin);

                // if body (stream) can seek, we can read the body to a string for logging purposes
                using (var reader = new StreamReader(
                     req.Body,
                     encoding: Encoding.UTF8,
                     detectEncodingFromByteOrderMarks: false,
                     bufferSize: 8192,
                     leaveOpen: true))
                {
                    var jsonString = reader.ReadToEnd();

                    // store into the HTTP context Items["request_body"]
                    context.HttpContext.Items.Add("request_body", jsonString);
                }

                // go back to beginning so json reader get's the whole thing
                req.Body.Seek(0, SeekOrigin.Begin);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

所以现在,我可以在具有 [ReadRequestBodyIntoItems] 属性的端点中使用 HttpContext.Items["request_body"] 访问正文。

但是,伙计,这似乎有太多的障碍无法跳过。所以这就是我结束的地方,我真的很高兴。

我的端点开始是这样的:

[HttpPost("")]
[ReadRequestBodyIntoItems]
[Consumes("application/json")]
public async Task<IActionResult> ReceiveSomeData([FromBody] MyJsonObjectType value)
{
    val bodyString = HttpContext.Items["request_body"];
    // use the body, process the stuff...
}
Run Code Online (Sandbox Code Playgroud)

但是只更改签名要简单得多,如下所示:

[HttpPost("")]
[Consumes("application/json")]
public async Task<IActionResult> ReceiveSomeData()
{
    using (var reader = new StreamReader(
           Request.Body,
           encoding: Encoding.UTF8,
           detectEncodingFromByteOrderMarks: false
    ))
    {
        var bodyString = await reader.ReadToEndAsync();

        var value = JsonConvert.DeserializeObject<MyJsonObjectType>(bodyString);

        // use the body, process the stuff...
    }
}
Run Code Online (Sandbox Code Playgroud)

我真的很喜欢这个,因为它只读取一次正文流,而且我可以控制反序列化。当然,如果 ASP.NET core 为我做了这个魔术就很好,但在这里我不会浪费时间读取流两次(可能每次都缓冲),并且代码非常清晰和干净。

如果您需要在许多端点上使用此功能,也许中间件方法可能更简洁,或者您至少可以将主体提取封装到扩展函数中,以使代码更简洁。

无论如何,我没有找到任何涉及这个问题所有 3 个方面的来源,因此这篇文章。希望这对某人有所帮助!

顺便说一句:这是使用 ASP .NET Core 3.1。


A.R*_*R.F 6

对于 read of Body,您可以异步读取。

使用如下async方法:

public async Task<IActionResult> GetBody()
{
      string body="";
      using (StreamReader stream = new StreamReader(Request.Body))
      {
           body = await stream.ReadToEndAsync();
      }
    return Json(body);
}
Run Code Online (Sandbox Code Playgroud)

用邮递员测试:

在此处输入图片说明

它的工作很好,在测试Asp.net core版本2.0 , 2.1 , 2.2, 3.0

我希望有用。


ruz*_*stu 5

我能够在像这样的 asp.net core 3.1 应用程序中读取请求正文(与启用缓冲的简单中间件一起使用 - 启用倒带似乎适用于早期的 .Net Core 版本):

var reader = await Request.BodyReader.ReadAsync();
Request.Body.Position = 0;
var buffer = reader.Buffer;
var body = Encoding.UTF8.GetString(buffer.FirstSpan);
Request.Body.Position = 0;
Run Code Online (Sandbox Code Playgroud)