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中,读取身体请求的次数似乎很复杂,但是如果你的第一次尝试以正确的方式进行,那么下次尝试你应该没问题.
我通过替换正文流来阅读几个转变,但我认为以下是最干净的:
最重要的一点是
[编辑]
正如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)
小智 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具有重载,允许您在使用临时文件之前限制它在内存中的缓冲量,以及对您的缓冲的总体限制。请注意,如果请求超过此限制,则会引发异常并且该请求将永远不会到达您的处理程序。
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¶m2=someothervalue"
}
}
Run Code Online (Sandbox Code Playgroud)
Ale*_*xei 11
我在使用 ASP.NET Core 2.1 时遇到了类似的问题:
SaoBiz您指出这个解决方案。因此,显而易见的解决方案是允许请求可回滚,但要确保在读取正文后,绑定仍然有效。
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)
(将其放在配置方法的开头)
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)
就是那么容易。
小智 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。
对于 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。
我希望有用。
我能够在像这样的 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)
| 归档时间: |
|
| 查看次数: |
67869 次 |
| 最近记录: |