147 votes

Comment lire le corps de la requête dans un contrôleur webapi asp.net core ?

J'essaie de lire le corps de la requête dans la section 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 à 0, mais cela n'a pas fonctionné non plus. Comme il s'agit d'ASP.NET Core, les choses sont un peu différentes, je pense. Je vois que tous les échantillons ici font référence à d'anciennes versions de l'API Web.

Existe-t-il un autre moyen de procéder ?

8 votes

Attention, si le corps de la requête a déjà été lu avant pendant le pipeline de la requête, alors il est vide lorsque vous essayez de le lire une deuxième fois.

1 votes

0 votes

@Fabio Merci pour l'info, peut-on définir la position et la relire ?

136voto

Jean Points 1536

En ASP.Net Core, il semble compliqué de lire plusieurs fois le corps de la requête, cependant si votre première tentative le fait de la bonne manière, vous devriez vous en sortir pour les tentatives suivantes.

J'ai lu plusieurs retournements de situation, par exemple en substituant le flux du corps, mais je pense que le suivant est le plus propre :

Les points les plus importants étant

  1. pour indiquer à la demande que vous lirez son corps deux fois ou plus,
  2. de ne pas fermer le flux du corps, et
  3. pour le rembobiner à sa position initiale afin que le processus interne ne se perde pas.

[EDIT]

Comme l'a souligné Murad, vous pouvez également tirer parti de l'extension .Net Core 2.1 : EnableBuffering Il stocke les grosses requêtes sur le disque au lieu de les garder en mémoire, évitant ainsi les problèmes de gros flux stockés en mémoire (fichiers, images, ...). Vous pouvez changer le dossier temporaire en définissant ASPNETCORE_TEMP et les fichiers sont supprimés dès que la demande est terminée.

Dans un AuthorizationFilter vous pouvez faire ce qui suit :

// 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
    }
}

Vous pouvez ensuite réutiliser le corps dans le gestionnaire de demande.

Dans votre cas, si vous obtenez un résultat nul, cela signifie probablement que le corps a déjà été lu à un stade antérieur. Dans ce cas, vous devrez peut-être utiliser un middleware (voir ci-dessous).

Cependant, faites attention si vous manipulez de grands flux, ce comportement implique que tout est chargé en mémoire, cela ne devrait pas être déclenché dans le cas d'un téléchargement de fichier.

Vous pouvez l'utiliser comme Middleware

Le mien ressemble à ceci (encore une fois, si vous téléchargez de gros fichiers, il faut désactiver cette fonction 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>();
    }

}

1 votes

Le flux est toujours vide même si je rembobine à la position 0.

2 votes

Avez-vous utilisé req.EnableRewind(); ? J'utilise le code ci-dessus et cela fonctionne bien.

2 votes

J'ai utilisé req.EnableRewind() ; cela ne fonctionne pas. J'obtiens Position = 0, body length = 26, mais la lecture du flux 'body' aboutit à une chaîne vide.

47voto

Andriod Points 123

Une solution plus claire, fonctionnant en ASP.Net Core 2.1 / 3.1

Classe de filtre

using Microsoft.AspNetCore.Authorization;
// For ASP.NET 2.1
using Microsoft.AspNetCore.Http.Internal;
// For ASP.NET 3.1
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;

public class ReadableBodyStreamAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        // For ASP.NET 2.1
        // context.HttpContext.Request.EnableRewind();
        // For ASP.NET 3.1
        // context.HttpContext.Request.EnableBuffering();
    }
}

Dans un contrôleur

[HttpPost]
[ReadableBodyStream]
public string SomePostMethod()
{
    //Note: if you're late and body has already been read, you may need this next line
    //Note2: if "Note" is true and Body was read using StreamReader too, then it may be necessary to set "leaveOpen: true" for that stream.
    HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);

    using (StreamReader stream = new StreamReader(HttpContext.Request.Body))
    {
        string body = stream.ReadToEnd();
        // body = "param=somevalue&param2=someothervalue"
    }
}

7 votes

Pour netcore3.0, ce serait EnableBuffering() au lieu de .EnableRewind()

1 votes

Merci @mr5 - J'ai mis à jour ma réponse

0 votes

J'ai trouvé ceci en corrigeant quelques mises à jour de .net Core 2.2 --> Core 3.1 qui ont cassé la manière EnableRewind(). Je pense que cela nécessite une ligne de code supplémentaire, sans laquelle je ne pourrais pas relire le corps : HttpContext.Request.Body.Seek(0, SeekOrigin.Begin) ;

24voto

SaoBiz Points 819

Pour pouvoir rembobiner le corps de la requête, la réponse de @Jean m'a permis de trouver une solution qui semble bien fonctionner. Je l'utilise actuellement pour le middleware Global Exception Handler mais le principe est le même.

J'ai créé un middleware qui permet essentiellement le rembobinage sur le corps de la requête (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>();
    }
}

Vous pouvez ensuite l'utiliser dans votre Startup.cs comme ça :

[...]
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    [...]
    app.UseEnableRequestRewind();
    [...]
}

En utilisant cette approche, j'ai pu rembobiner le flux du corps de la demande avec succès.

1 votes

Cela a bien fonctionné pour moi @SaoBiz Merci ! une coquille, changer le 2ème ce à constructeur sur UseEnableRequestRewind(this IApplicationBuilder builder) .

0 votes

@RichardLogwood Content que ça ait aidé ! Merci d'avoir trouvé la coquille ! Corrigé. :)

0 votes

Cela m'a vraiment aidé - et cela fonctionne, mais dans la version 5.0 (ou 3.1, je crois), vous devez remplacer context.Request.EnableRewind() par context.Request.EnableBuffering(). Cependant, je me suis ensuite rendu compte que la réponse de Stephen Wilkinson "A quick way..." est beaucoup plus succincte pour moi.

1voto

Randy Larson Points 2053

Le site IHttpContextAccessor Cette méthode fonctionne si vous souhaitez suivre cette voie.

TLDR ;

  • Injectez le IHttpContextAccessor

  • Rembobinage -- 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 de -- Une tentative d'exemple concis, sans compilation, des éléments dont vous devez vous assurer qu'ils sont en place afin d'obtenir un résultat utilisable. IHttpContextAccessor . Les réponses ont souligné à juste titre que vous devrez chercher à revenir au début lorsque vous tenterez de lire le corps de la requête. Le site CanSeek , Position sur le flux de corps de la demande sont utiles pour vérifier cela.

Docs .NET Core DI

// 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");
        }
    }
}

-4voto

Kasun Koswattha Points 423

Apparemment, nous pouvons utiliser IHttpContextAccessor pour accéder au contexte http dans les contrôleurs. Il suffit de l'injecter dans la classe de démarrage et de l'obtenir dans vos contrôleurs.

  services.AddScoped<IHttpContextAccessor, HttpContextAccessor>();

En utilisant ceci, vous pouvez accéder au contexte même dans le constructeur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X