web-dev-qa-db-fra.com

Corps de demande d'accès brut

J'essaie d'accéder au corps/flux d'entrée bruts d'une requête dans ASP.net 5. Auparavant, je pouvais réinitialiser la position du flux d'entrée à 0 et le lire dans un flux de mémoire, mais lorsque j'essaie à partir du contexte, le flux d'entrée est nul ou génère une erreur (System.NotSupportedException => "La méthode spécifiée n'est pas prise en charge.").

Dans le premier exemple ci-dessous, je peux accéder à la demande brute dans un contrôleur si je déclare le type d'objet de paramètre de la méthode du contrôleur comme dynamique. Pour diverses raisons, ce n'est pas une solution et je dois quand même accéder au corps de la requête brute dans un filtre d'authentification.

Cet exemple fonctionne, mais n'est pas une solution raisonnable:

[HttpPost("requestme")]
public string GetRequestBody([FromBody] dynamic body)
{   
    return body.ToString();
}

Erreur de projection:

[HttpPost("requestme")]
public string GetRequestBody()
{
    var m = new MemoryStream();
    Request.Body.CopyTo(m);

    var contentLength = m.Length;

    var b = System.Text.Encoding.UTF8.GetString(m.ToArray());

    return b;
}

Erreur de projection:

[HttpPost("requestme")]
public string GetRequestBody()
{
    Request.Body.Position = 0;
    var input = new StreamReader(Request.Body).ReadToEnd();

    return input;
}

Erreur de projection:

[HttpPost("requestme")]
public string GetRequestBody()
{
    Request.Body.Position = 0;
    var input = new MemoryStream();
    Request.Body.CopyTo(input);

    var inputString = System.Text.Encoding.UTF8.GetString(input.ToArray());

    return inputString;
}

Je dois accéder au corps de requête brut de chaque requête qui entre pour une API que je construis.

Toute aide ou direction serait grandement appréciée!

MODIFIER:

Voici le code dans lequel je voudrais lire le corps de la demande.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Http;

namespace API.Filters
{
    public class CustomAuthorizationAttribute : Attribute, IAuthorizationFilter
    {
        public CustomAuthorizationAttribute()
        { }

        public void OnAuthorization(AuthorizationContext context)
        {
            if (context == null)
                throw new ArgumentNullException("OnAuthorization AuthorizationContext context can not be null.");
            else
            {
                if (this.AuthorizeCore(context.HttpContext) == false)
                {
                    // Do Other Stuff To Check Auth
                }
                else
                {
                    context.Result = new HttpUnauthorizedResult();
                }
            }
        }

        protected virtual bool AuthorizeCore(HttpContext httpContext)
        {
            var result = false;

            using (System.IO.MemoryStream m = new System.IO.MemoryStream())
            {
                try
                {
                    if (httpContext.Request.Body.CanSeek == true)
                        httpContext.Request.Body.Position = 0;

                    httpContext.Request.Body.CopyTo(m);

                    var bodyString = System.Text.Encoding.UTF8.GetString(m.ToArray());

                    return CheckBody(bodyString); // Initial Auth Check returns true/false <-- Not Shown In Code Here on Stack Overflow
                }
                catch (Exception ex)
                {
                    Logger.WriteLine(ex.Message);
                }
            }
                return false;
        }
    }
}

Ce code serait accessible lorsqu'un appel est fait vers une méthode de contrôleur marquée avec l'attribut CustomAuthorization de la sorte.

[Filters.CustomAuthorizationAuthorization]
[HttpPost]
public ActionResult Post([FromBody]UserModel Profile)
{
    // Process Profile
}
9
Badger Dev

L'implémentation de Request.Body dépend de l'action du contrôleur. 

Si l'action contient des paramètres, elle est implémentée par Microsoft.AspNet.WebUtilities.FileBufferingReadStream, qui prend en charge la recherche (Request.Body.CanSeek == true). Ce type prend également en charge la définition du Request.Body.Position.

Cependant, si votre action ne contient aucun paramètre, elle est implémentée par Microsoft.AspNet.Loader.IIS.FeatureModel.RequestBody, ce qui permet à non de prendre en charge (Request.Body.CanSeek == false). Cela signifie que vous ne pouvez pas ajuster la propriété Position et vous pouvez simplement commencer à lire le flux.

Cette différence est probablement liée au fait que MVC doit extraire les valeurs de paramètres du corps de la demande et qu'il doit donc lire la demande.

Dans votre cas, votre action n'a aucun paramètre. Donc, le Microsoft.AspNet.Loader.IIS.FeatureModel.RequestBody est utilisé, ce qui lève une exception si vous essayez de définir la propriété Position.


Solution : ne définissez pas la position ou vérifiez si vous avez en fait peut définir la position en premier:

if (Request.Body.CanSeek)
{
    // Reset the position to zero to read from the beginning.
    Request.Body.Position = 0;
}

var input = new StreamReader(Request.Body).ReadToEnd();
8
Henk Mollema

Les exceptions que vous voyez dans vos trois derniers extraits sont la conséquence directe d'essayer de lire le corps de la demande plusieurs fois - une fois par MVC 6 et une fois dans votre code personnalisé - lors de l'utilisation d'un hôte en streaming comme IIS ou WebListener. Vous pouvez voir cette SO question pour plus d'informations: Lire le corps deux fois dans Asp.Net 5 .

Cela dit, je ne m'attendrais à ce que cela se produise lors de l'utilisation de application/x-www-form-urlencoded, car il serait dangereux pour MVC de commencer à lire le flux de demandes avec de longues demandes, telles que le téléchargement de fichiers. Si ce n'est pas le cas, c'est probablement un bogue de MVC à signaler sur https://github.com/aspnet/Mvc .

Pour résoudre ce problème, vous devriez jeter un oeil à cette réponse SO, qui explique comment utiliser context.Request.ReadFormAsync ou ajouter une mise en mémoire tampon manuelle: Lire le corps deux fois dans Asp.Net 5

app.Use(next => async context => {
    // Keep the original stream in a separate
    // variable to restore it later if necessary.
    var stream = context.Request.Body;

    // Optimization: don't buffer the request if
    // there was no stream or if it is rewindable.
    if (stream == Stream.Null || stream.CanSeek) {
        await next(context);

        return;
    }

    try {
        using (var buffer = new MemoryStream()) {
            // Copy the request stream to the memory stream.
            await stream.CopyToAsync(buffer);

            // Rewind the memory stream.
            buffer.Position = 0L;

            // Replace the request stream by the memory stream.
            context.Request.Body = buffer;

            // Invoke the rest of the pipeline.
            await next(context);
        }
    }

    finally {
        // Restore the original stream.
        context.Request.Body = stream;
    }
});
6
Pinpoint

Je viens d'avoir ce même problème. Supprimez les paramètres de la signature de la méthode, puis lisez le flux Request.Body comme vous le souhaitez.

2
joe

Vous devez appeler Request.EnableRewind () pour permettre le rebobinage du flux afin de pouvoir le lire.

string bodyAsString;
Request.EnableRewind();
using (var streamReader = new StreamReader(Request.Body, Encoding.UTF8))
{
    bodyAsString = streamReader.ReadToEnd();
}
0
Steve Peirce