Подтвердить что ты не робот

Почему один экземпляр запроса веб-API читается один раз?

Моя цель - аутентифицировать запросы веб-API с помощью AuthorizationFilter или DelegatingHandler. Я хочу искать идентификатор клиента и токен аутентификации в нескольких местах, включая тело запроса. Сначала казалось, что это будет легко, я мог бы сделать что-то вроде этого

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

task.Wait();

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

Проблема в том, что HttpContent можно прочитать только один раз. Если я сделаю это в обработчике или в фильтре, то контент не будет доступен для меня в моем методе действий. Я нашел несколько ответов здесь, в StackOverflow, вроде этого: Прочитайте HttpContent в контроллере WebApi, которые объясняют, что это намеренно таким образом, но они не говорят ПОЧЕМУ. Это кажется довольно серьезным ограничением, которое мешает мне использовать любой классный код анализа содержимого веб-API в фильтрах или обработчиках.

Это техническое ограничение? Это пытается удержать меня от ОЧЕНЬ ПЛОХОЙ ВЕЩИ (tm), которую я не вижу?

POSTMORTEM:

Я взглянул на источник, как предположил Филип. ReadAsStreamAsync возвращает внутренний поток и ничего не мешает вам вызвать Seek , если поток поддерживает его. В моих тестах, если я вызвал ReadAsAsync, сделал следующее:

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

Процесс привязки автоматической модели будет работать отлично, когда он ударит мой метод действия. Я не использовал это, хотя, я выбрал что-то более прямое:

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;

С помощью метода расширения (я в .NET 4.0 без ключевого слова ожидания)

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

Один последний улов, HttpContent имеет размер жесткого кодированного максимального буфера:

internal const int DefaultMaxBufferSize = 65536;

Итак, если ваш контент будет больше, вам придется вручную вызвать LoadIntoBufferAsync с большим размером, прежде чем пытаться вызвать ReadAsByteArrayAsync.

4b9b3361

Ответ 1

Ответ, на который вы указали, не совсем точным.

Вы всегда можете читать как строку (ReadAsStringAsync) или как byte [] (ReadAsByteArrayAsync), поскольку они буферизуют внутренний запрос.

Например, обработчик макета ниже:

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

То же самое относится к байту []:

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

Каждый из них не будет приводить к тому, что опубликованный контент будет пустым, когда он достигнет контроллера.

Ответ 2

Я бы поставил clientId и ключ проверки подлинности в заголовок, а не на содержимое.

В этом случае вы можете читать их столько раз, сколько хотите!