Почему один экземпляр запроса веб-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.
Ответы
Ответ 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 и ключ проверки подлинности в заголовок, а не на содержимое.
В этом случае вы можете читать их столько раз, сколько хотите!