J'essaie de lire le corps de la requête dans la méthode OnActionExecuting
, mais j'obtiens toujours null
pour le corps.
var request = context.HttpContext.Request;
var stream = new StreamReader(request.Body);
var body = stream.ReadToEnd();
J'ai essayé de définir explicitement la position du flux sur 0 mais cela ne fonctionnait pas non plus. Puisque c'est ASP.NET CORE, les choses sont peu différentes, je pense. Je peux voir tous les exemples ici en référence aux anciennes versions de webapi.
Y a-t-il une autre façon de faire cela?
Dans ASP.Net Core, il semble compliqué de lire la requête body plusieurs fois. Toutefois, si vous tentez votre première tentative correctement, les choses devraient bien se passer.
J'ai lu plusieurs redressements, par exemple en substituant le flux corporel, mais je pense que ce qui suit est le plus propre:
Les points les plus importants étant
[MODIFIER]
Comme le souligne Murad, vous pouvez également tirer parti de l’extension .Net Core 2.1: EnableBuffering
Il stocke les demandes volumineuses sur le disque au lieu de le conserver en mémoire, évitant ainsi les problèmes de flux importants stockés en mémoire ( fichiers, images, ...). Vous pouvez modifier le dossier temporaire en définissant la variable d’environnement ASPNETCORE_TEMP
. Les fichiers sont supprimés une fois la demande terminée.
Dans un AuthorizationFilter , vous pouvez effectuer les opérations suivantes:
// 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
}
}
Ensuite, vous pouvez utiliser le corps à nouveau dans le gestionnaire de demandes.
Dans votre cas, si vous obtenez un résultat nul, cela signifie probablement que le corps a déjà été lu à une étape antérieure. Dans ce cas, vous devrez peut-être utiliser un middleware (voir ci-dessous).
Cependant, soyez prudent si vous manipulez des flux volumineux, ce comportement implique que tout soit chargé en mémoire, cela ne devrait pas être déclenché en cas de téléchargement de fichier.
Le mien ressemble à ceci (encore une fois, si vous téléchargez/téléversez des fichiers volumineux, vous devez le désactiver pour éviter les problèmes de mémoire):
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>();
}
}
Pour pouvoir revenir en arrière dans le corps de la requête, la réponse de @ Jean m'a aidée à trouver une solution qui semble bien fonctionner. J'utilise actuellement ceci pour le gestionnaire de logiciel d'exception global, mais le principe est le même.
J'ai créé un middleware qui permet fondamentalement le rembobinage sur le corps de la demande (au lieu d'un décorateur).
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>();
}
}
Ceci peut alors être utilisé dans votre Startup.cs
comme ceci:
[...]
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
[...]
app.UseEnableRequestRewind();
[...]
}
En utilisant cette approche, j'ai pu rembobiner le flux de corps de la demande avec succès.
Une solution plus claire, fonctionne dans ASP.Net Core 2.1.
Classe de filtre
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();
}
}
Dans un contrôleur
[HttpPost]
[ReadableBodyStream]
public string SomePostMethod()
{
using (StreamReader stream = new StreamReader(HttpContext.Request.Body))
{
string body = stream.ReadToEnd();
// body = "param=somevalue¶m2=someothervalue"
}
}
La méthode IHttpContextAccessor
fonctionne si vous souhaitez emprunter cette voie.
TLDR;
Injecter le IHttpContextAccessor
Rewind - HttpContextAccessor.HttpContext.Request.Body.Seek(0, System.IO.SeekOrigin.Begin);
Lire - System.IO.StreamReader sr = new System.IO.StreamReader(HttpContextAccessor.HttpContext.Request.Body); JObject asObj = JObject.Parse(sr.ReadToEnd());
Plus - Un exemple concis, non compilant, des éléments que vous devez vous assurer sont en place pour vous permettre de utilisable IHttpContextAccessor
. Les réponses ont indiqué correctement que vous devrez revenir au début lorsque vous essayez de lire le corps de la demande. Les propriétés CanSeek
, Position
du flux de corps de la requête sont utiles pour le vérifier.
// First -- Make the accessor DI available
//
// Add an IHttpContextAccessor to your ConfigureServices method, found by default
// in your Startup.cs file:
// Extraneous junk removed for some brevity:
public void ConfigureServices(IServiceCollection services)
{
// Typical items found in ConfigureServices:
services.AddMvc(config => { config.Filters.Add(typeof(ExceptionFilterAttribute)); });
// ...
// Add or ensure that an IHttpContextAccessor is available within your Dependency Injection container
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
// Second -- Inject the accessor
//
// Elsewhere in the constructor of a class in which you want
// to access the incoming Http request, typically
// in a controller class of yours:
public class MyResourceController : Controller
{
public ILogger<PricesController> Logger { get; }
public IHttpContextAccessor HttpContextAccessor { get; }
public CommandController(
ILogger<CommandController> logger,
IHttpContextAccessor httpContextAccessor)
{
Logger = logger;
HttpContextAccessor = httpContextAccessor;
}
// ...
// Lastly -- a typical use
[Route("command/resource-a/{id}")]
[HttpPut]
public ObjectResult PutUpdate([FromRoute] string id, [FromBody] ModelObject requestModel)
{
if (HttpContextAccessor.HttpContext.Request.Body.CanSeek)
{
HttpContextAccessor.HttpContext.Request.Body.Seek(0, System.IO.SeekOrigin.Begin);
System.IO.StreamReader sr = new System.IO.StreamReader(HttpContextAccessor.HttpContext.Request.Body);
JObject asObj = JObject.Parse(sr.ReadToEnd());
var keyVal = asObj.ContainsKey("key-a");
}
}
}
J'ai eu un problème similaire lors de l'utilisation d'ASP.NET Core 2.1:
SaoBiz
d’avoir signalé cette solution.La solution évidente est donc de permettre à la demande d'être rembobinable, mais assurez-vous qu'après la lecture du corps, la liaison fonctionne toujours.
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.EnableRewind();
await _next(context);
}
}
(placez-le au début de la méthode Configure)
app.UseMiddleware<EnableRequestRewindMiddleware>();
Cela fait partie du middleware qui nécessite de décompresser les informations POSTed pour vérifier des éléments.
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);
}