web-dev-qa-db-fra.com

Pourquoi le corps d'une demande d'API Web est-il lu une fois?

Mon objectif est d'authentifier les demandes d'API Web à l'aide d'un AuthorizationFilter ou DelegatingHandler. Je souhaite rechercher l'identifiant client et le jeton d'authentification à quelques endroits, y compris le corps de la demande. Au début, cela semblait facile, je pourrais faire quelque chose comme ça

var task = _message.Content.ReadAsAsync<Credentials>();

task.Wait();

if (task.Result != null)
{
    // check if credentials are valid
}

Le problème est que le contenu HttpContent ne peut être lu qu'une seule fois. Si je le fais dans un gestionnaire ou un filtre, le contenu ne sera pas disponible dans ma méthode d'action. J'ai trouvé quelques réponses ici sur StackOverflow, comme celui-ci: Lisez le contenu de HttpContent dans le contrôleur WebApi qui explique que c'est intentionnellement de cette façon, mais ils ne disent pas POURQUOI. Cela semble être une limitation assez sévère qui m'empêche d'utiliser l'un des codes d'analyse du contenu de l'API Web géniaux dans les filtres ou les gestionnaires.

Est-ce une limitation technique? Est-ce qu'il essaie de m'empêcher de faire une TRES MAUVAISE CHOSE que je ne vois pas?

AUTOPSIE:

J'ai jeté un coup d'œil à la source suggérée par Filip. ReadAsStreamAsync renvoie le flux interne et rien ne vous empêche d'appeler Seek si le flux le prend en charge . Dans mes tests, si j'ai appelé ReadAsAsync, alors ceci:

message.Content.ReadAsStreamAsync().ContinueWith(t => t.Result.Seek(0, SeekOrigin.Begin)).Wait();

Le processus de liaison de modèle automatique fonctionnerait correctement s'il atteignait ma méthode d'action. Je n'ai pas utilisé cela cependant, j'ai opté pour quelque chose de plus direct:

var buffer = new MemoryStream(_message.Content.ReadAsByteArrayAsync().WaitFor());
var formatters = _message.GetConfiguration().Formatters;
var reader = formatters.FindReader(typeof(Credentials), _message.Content.Headers.ContentType);
var credentials = reader.ReadFromStreamAsync(typeof(Credentials), buffer, _message.Content, null).WaitFor() as Credentials;

Avec une méthode d'extension (je suis dans .NET 4.0 sans mot-clé wait)

public static class TaskExtensions
{
    public static T WaitFor<T>(this Task<T> task)
    {
        task.Wait();
        if (task.IsCanceled) { throw new ApplicationException(); }
        if (task.IsFaulted) { throw task.Exception; }
        return task.Result;
    }
}

Une dernière prise, HttpContent a une taille de tampon maximale codée en dur:

internal const int DefaultMaxBufferSize = 65536;

Par conséquent, si votre contenu doit être plus volumineux, vous devez appeler manuellement LoadIntoBufferAsync avec une taille supérieure avant d'appeler ReadAsByteArrayAsync.

23
MichaC

La réponse que vous avez indiquée n’est pas tout à fait exacte.

Vous pouvez toujours lire sous forme de chaîne (ReadAsStringAsync) ou sous forme d'octet [] (ReadAsByteArrayAsync) car ils tamponnent la demande en interne.

Par exemple, le gestionnaire factice ci-dessous:

public class MyHandler : DelegatingHandler
{
    protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        var body = await request.Content.ReadAsStringAsync();
        //deserialize from string i.e. using JSON.NET

        return base.SendAsync(request, cancellationToken);
    }
}

Même chose pour byte []:

public class MessageHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var requestMessage = await request.Content.ReadAsByteArrayAsync();
        //do something with requestMessage - but you will have to deserialize from byte[]

        return base.SendAsync(request, cancellationToken);
    }
}

Chacun n'entraînera pas la nullité du contenu posté lorsqu'il atteindra le contrôleur.

24
Filip W

Je mettrais le clientId et la clé d'authentification dans l'en-tête plutôt que le contenu.

De quelle manière, vous pouvez les lire autant de fois que vous le souhaitez!

2
The Light