ModelBinder Request.Content.ReadAsStringAsync производительность
У меня есть собственный ModelBinder, который, когда я загружаю тестирование своего приложения и запускаю Ants profiler, идентифицирует чтение Request.Content в виде строки как горячей точки:
public class QueryModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var body = actionContext.Request.Content.ReadAsStringAsync().Result;
Есть ли более эффективный способ сделать это? Или я неправильно читаю профилировщик ANTS?
1 ответ
Насколько большой контент? Обратите внимание, что вы могли бы видеть много времени, потому что вы вызываете этот сетевой вызов синхронно, а не асинхронно.
Вы можете прочитать строку ранее async и спрятать ее в свойстве запроса.
В качестве альтернативы вы можете вместо этого написать средство форматирования, а затем украсить свой параметр с помощью [FromBody].
Рекомендованный подход здесь заключается в использовании FromBody и форматера, поскольку он естественным образом соответствует архитектуре WebAPI:
Для этого вы должны написать средство форматирования медиа-типа:
public class StringFormatter : MediaTypeFormatter
{
public StringFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/mystring"));
}
public override bool CanReadType(Type type)
{
return (type == typeof (string));
}
public override bool CanWriteType(Type type)
{
return false;
}
public override async Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger,
CancellationToken cancellationToken)
{
if (!CanReadType(type))
{
throw new InvalidOperationException();
}
return await content.ReadAsStringAsync();
}
}
Зарегистрируйте это в webapiconfig.cs
config.Formatters.Add(new StringFormatter());
И потреблять в действии
public string Get([FromBody]string myString)
{
return myString;
}
Другая конструкция (не так, как рекомендуется из-за связи между фильтром и связующим веществом):
Реализуем модель связующего (это супер наивно):
public class MyStringModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
// this is a Naive comparison of media type
if (actionContext.Request.Content.Headers.ContentType.MediaType == "application/mystring")
{
bindingContext.Model = actionContext.Request.Properties["MyString"] as string;
return true;
}
return false;
}
}
Добавьте фильтр авторизации (они запускаются перед привязкой модели), чтобы вы могли асинхронно получать доступ к действию. Это также работает с делегирующим обработчиком:
public class MyStringFilter : AuthorizationFilterAttribute
{
public override async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
if (actionContext.Request.Content.Headers.ContentType.MediaType == "application/mystring")
{
var myString = await actionContext.Request.Content.ReadAsStringAsync();
actionContext.Request.Properties.Add("MyString", myString);
}
}
}
Зарегистрируйте его в WebApi.Config или примените к контроллеру:
WebApiConfig.cs
config.Filters.Add(new MyStringFilter());
ValuesController.cs
[MyStringFilter] // this is optional, you can register it globally as well
public class ValuesController : ApiController
{
// specifying the type here is optional, but I'm using it because it avoids having to specify the prefix
public string Get([ModelBinder(typeof(MyStringModelBinder))]string myString = null)
{
return myString;
}
}
(Спасибо за @Kiran Challa за то, что я посмотрел через плечо и предложил фильтр авторизации)
РЕДАКТИРОВАТЬ: Одна вещь, которую всегда нужно помнить, с относительно большими строками (потребляющими более 85 КБ, то есть около 40 КБ символов) может попасть в кучу больших объектов, что приведет к снижению производительности вашего сайта. Если вы считаете, что это достаточно часто, разбейте ввод на что-то вроде строителя строк / массива строк или чего-то подобного без смежной памяти. Посмотрите, почему куча больших объектов и почему мы заботимся?