122 votes

Comment lire le corps de la demande dans un contrôleur web de base asp.net?

J'essaie de lire le corps de la demande avec 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 faisant référence aux anciennes versions de webapi.
Y a-t-il une autre façon de faire cela?

121voto

Jean Points 1536

Dans ASP.Net de Base, il semble compliqué de lire plusieurs fois le corps demande, toutefois, si votre première tentative n'a le droit chemin, vous devriez être bien pour les prochaines tentatives.

J'ai lu plusieurs de redressement, par exemple en remplaçant le flux de corps, mais je pense que ce qui suit est le plus propre:

Les points les plus importants étant

  1. pour laisser la demande de savoir que vous allez lire son corps deux fois ou plusieurs fois,
  2. pour ne pas fermer le flux de corps, et
  3. rembobiner à sa position initiale de sorte que le processus interne ne se perd pas.

[MODIFIER]

Comme l'a souligné Mourad, vous pouvez également profiter de la .Net de Base 2.1 extension: EnableBuffering Il stocke de grandes demandes sur le disque au lieu de le garder en mémoire, en évitant les grands groupes de questions stockées dans la mémoire (fichiers, images, ...). Vous pouvez modifier le dossier temporaire par la mise en ASPNETCORE_TEMP variable d'environnement, et les fichiers sont supprimés une fois que la demande est plus.

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 de nouveau dans le gestionnaire de requêtes.

Dans votre cas, si vous obtenez un résultat null, cela signifie probablement que le corps a déjà été lu lors d'une étape antérieure. Dans ce cas, vous pouvez avoir besoin d'utiliser un middleware (voir ci-dessous).

Attention cependant si vous traiter de gros ruisseaux, ce comportement implique que tout ce qui est chargé dans la mémoire, cela ne devrait pas être déclenchée en cas de téléchargement de fichiers.

Vous pouvez l'utiliser comme un Middleware

Le mien ressemble à ça (encore une fois, si vous télécharger des fichiers de grande taille, ce doit être désactivé 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>();
    }

}

37voto

Andriod Points 123

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&param2=someothervalue"
    }
}
 

20voto

SaoBiz Points 819

Pour être en mesure de revenir en arrière le corps de la requête, @Jean la réponse m'a aidé à trouver une solution qui semble bien fonctionner. J'utilise actuellement ce pour le Gestionnaire Global d'Exception Middleware, mais le principe est le même.

J'ai créé un middleware que, fondamentalement permet 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>();
    }
}

Cela peut ensuite être utilisé dans votre Startup.cs comme:

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

En utilisant cette approche, j'ai été en mesure de revenir en arrière le corps de la requête de flux avec succès.

1voto

Randy Larson Points 2053

L' IHttpContextAccessor méthode ne fonctionnera si vous voulez aller dans cette voie.

TLDR;

  • Injecter de l' 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());

De plus , Une tentative de concise, non de la compilation, par exemple des éléments que vous aurez besoin pour s'assurer sont en place afin d'arriver à un utilisables IHttpContextAccessor. Des réponses ont souligné à juste titre que vous aurez besoin de chercher en arrière au début lorsque vous essayez de lire le corps de la requête. L' CanSeek, Position propriétés sur le corps de la requête de flux utile pour vérifier cette.

.NET de Base DI Docs

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

-5voto

Kasun Koswattha Points 423
<p>Apparemment, nous pouvons utiliser IHttpContextAccessor pour accéder aux contexte http dans les contrôleurs. Juste besoin d’injecter dans la classe de démarrage et d’obtenir dans vos contrôleurs.<pre><code></code></pre><p>en utilisant cela, que vous pouvez accéder au contexte de même dans le constructeur.</p></p>

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