ModelBinder Request.Content.ReadAsStringAsync performance - asp.net-web-api

I have a custom ModelBinder, which, when I load test my app, and run Ants profiler on, identified reading the Request.Content as string as a hotspot:
public class QueryModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var body = actionContext.Request.Content.ReadAsStringAsync().Result;
Is there a more efficient way of doing this?
Or am I reading the ANTS profiler incorrectly?

How big is the content? Note that you might be seeing a lot of time because you are calling this network call in sync rather than async.
You can potentially read the string earlier async and stash it in the request property.
Alternatively you can write a formatter instead, and then decorate your parameter with [FromBody].
The recommended approach here is to use a FromBody and a formatter, since it naturally fits with the WebAPI architecture:
For that you would write a media type formatter:
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();
}
}
Register it in webapiconfig.cs
config.Formatters.Add(new StringFormatter());
And consume in an action
public string Get([FromBody]string myString)
{
return myString;
}
The other design (not as recommended because of coupling between the filter and the binder):
Implement a model binder (this is super Naive):
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;
}
}
Add an authroization filter (they run ahead of modelbinding), to you can async access the action. This also works on a delegating handler:
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);
}
}
}
Register it in WebApi.Config or apply it to the controller:
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;
}
}
(Thanks for #Kiran Challa for looking over my shoulder, and suggesting the Authorization filter)
EDIT: One thing to always remember with relatively large strings (consuming more than 85KB so about 40K Chars) can go into the Large Object heap, which will wreak havoc on your site performance. If you thing this is common enough, break the input down into something like a string builder/array of strings or something similar without contiguous memory. See Why Large Object Heap and why do we care?

Related

Setting [BindNever] during the action execution filter flow

Does anyone know how I can mark an argument on ActionDescriptor.Parameters to behave in a similar way the [BindNever] is behaving?
I want to always exclude a specific argument from a specific type without keep decorating it on the Controller.
Essentially I would like to be able to add my injected to my functions somehow how similar to the way its done with CancellationToken
public class TestController : ControllerBase
{
[HttpGet(Name = "Get")]
public IActionResult Get([BindNever] IInjectedInterface injected)
{
//Injected can be used in this method
return Ok();
}
[HttpPost(Name = "Post")]
public IActionResult Post([BindNever] IInjectedInterface injected, FormModel formModel)
{
//Injected doesn't work here. There is an error that
/*System.InvalidOperationException: 'Action 'WebApplication3.Controllers.TestController.Post (WebApplication3)'
has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body.
Inspect the following parameters, and use 'FromQueryAttribute' to specify bound from query, 'FromRouteAttribute' to specify bound from route,
and 'FromBodyAttribute' for parameters to be bound from body:
IInjectedInterface injected
FormModel formModel'
*/
return Ok();
}
}
public class ActionExecutionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var injectedParam = context.ActionDescriptor.Parameters.SingleOrDefault(x => x.ParameterType == typeof(IInjectedInterface));
if (injectedParam != null)
{
context.ActionArguments[injectedParam.Name] = new Injected(99);
}
await next.Invoke();
}
private class Injected : IInjectedInterface
{
public Injected(int someData)
{
SomeData = someData;
}
public int SomeData { get; }
}
}
I was able to solve it. Apparently you need to add the following lines on your program.cs to avoid the model binder related errors.
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(IInjectedInterface)));
options.ModelMetadataDetailsProviders.Add(
new BindingSourceMetadataProvider(typeof(IInjectedInterface), BindingSource.Special));

How to specify response type in ASP.NET Core middleware

My controllers return unified RequestResult:
public Task<RequestResult> SomeAction()
{
...
return new RequestResult(RequestResultType.NotFound);
}
public class RequestResult
{
public RequestResultType Type { get;set; }
... //actual data
}
public enum RequestResultType
{
Success = 1,
NotFound = 2
}
So basically RequestResult combines actual Action data and error type (if it happened). Now I need to specify Response Type at some point in case if Action returned Error. My best guess here is to use Middleware:
public class ResponseTypeMiddleware
{
private readonly RequestDelegate next;
public ResponseTypeMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
await next(context);
var response = context.Response.Body; //how to access object?
}
}
but I can't figure out what to do with it. What I'd perfectly like to do is to check if response is of type RequestResult, then specify ResponseType equal BadRequest. But I don't see how I can do it here as what I have is just a stream. May be I can hijack into pipeline earlier, before result was serialized (Controller?).
P. S. The reason why I don't use Controller.BadRequest directly in Action is that my Action's logic is implemented via CQRS command/query handlers, so I don't have direct access to Controller.
As you are going to process controller's action result (MVC), the best way is to use ActionFilter or ResultFilter here, instead of Middleware. Filters in ASP.NET Core are a part of MVC and so know about controllers, actions and so on. Middleware is a more common conception - it is an additional chain in application request-response pipeline.
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// do something before the action executes
}
public void OnActionExecuted(ActionExecutedContext context)
{
// do something after the action executes
// get or set controller action result here
var result = context.Result as RequestResult;
}
}

AsyncTaskCodeActivity and lost context after await

AsyncTaskCodeActivity fails when the context parameter is accessed after the first await is performed. For example:
public class TestAsyncTaskCodeActivity : AsyncTaskCodeActivity<int>
{
protected async override Task<int> ExecuteAsync(AsyncCodeActivityContext context, CancellationToken cancellationToken)
{
await Task.Delay(50);
// context has already been disposed and the next line throws
// ObjectDisposedException with the message:
// An ActivityContext can only be accessed within the scope of the function it was passed into.
context.Track(new CustomTrackingRecord("test"));
// more awaits can happen here
return 3;
}
}
Is there any simple way to preserve the context so it can be used also after awaiting something?
Ah.
When I wrote AsyncTaskCodeActivity<T>, I assumed that the AsyncCodeActivityContext was in fact going to be the same instance at the beginning and end of the asynchronous method, and be available all the way through. This is not the case (which is a bit odd - not sure why the WF team made that decision).
Instead, the AsyncCodeActivityContext can only be accessed at the beginning and end of the activity. Awkward, indeed.
The updated code below will allow you to access the context at the beginning (e.g., reading In variables) and then access the context again at the end. I also introduce an optional TState, which can be used for storing activity state (which the activity can access throughout its execution). Let me know if this fits your needs; I haven't tested it.
public abstract class AsyncTaskCodeActivity<T, TState> : AsyncCodeActivity<T>
{
protected sealed override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
TState activityState = PreExecute(context);
context.UserState = activityState;
var task = ExecuteAsync(activityState);
return AsyncFactory<T>.ToBegin(task, callback, state);
}
protected sealed override T EndExecute(AsyncCodeActivityContext context, IAsyncResult asyncResult)
{
var result = AsyncFactory<T>.ToEnd(asyncResult);
return PostExecute(context, (TState)context.UserState, result);
}
protected virtual TState PreExecute(AsyncCodeActivityContext context)
{
return default(TState);
}
protected abstract Task<T> ExecuteAsync(TState activityState);
protected virtual T PostExecute(AsyncCodeActivityContext context, TState activityState, T result)
{
return result;
}
}
public abstract class AsyncTaskCodeActivity<T> : AsyncTaskCodeActivity<T, object>
{
}

WebApi 2.2 basecontroller with endpoints that can be overridden keeping routing

I am having some trouble with webapi 2.2 routing. I have installed v5.2 into my project and have modified my config to use a Custom DirectRouteProvider
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
(inherit: true);
}
}
Now I have setup a base controller with methods that I want to allow to be overridden for example
[HttpGet]
[Route("")]
[EnableQuery]
public async virtual Task<IHttpActionResult> Get()
{
var data = await DomainService.GetQueryable();
if (data != null)
{
return Ok(data);
}
return NotFound();
}
If I run the project and call an endpoint like http://localhost:3000/somecontroller the above Get() method is called.
If within somecontroller I try
[HttpGet]
[EnableQuery]
[Route("")]
public async override Task<IHttpActionResult> Get()
{
var data = await _skillRepository.ParentSkillsAsync();
if (data != null)
{
return Ok(data);
}
return NotFound();
}
I get an error of
Multiple actions were found that match the request
Is there anyway to do what I am trying here? is frustrating trying to get this to work.
Thank you
Dan - I was having the same trouble myself and here's how I solved it.
I'm not sure if this is the best solution, but it's working for me.
On my route attribute I added the Order property with a value of 3 into the action on the base class.
The default value for the Order property is 0, so the value of the derived class would be 0.
The base class needs to have an Order property that is higher than the derived class.
So, in your example, I believe this would allow your code to work like you want it to:
Action in base class
[HttpGet]
[Route("", Order=3)]
[EnableQuery]
public async virtual Task<IHttpActionResult> Get()
{
var data = await DomainService.GetQueryable();
if (data != null)
{
return Ok(data);
}
return NotFound();
}
Action in derived class:
[HttpGet]
[EnableQuery]
[Route("")]
public async override Task<IHttpActionResult> Get()
{
var data = await _skillRepository.ParentSkillsAsync();
if (data != null)
{
return Ok(data);
}
return NotFound();
}

How Get Length of response in ASP.net Web API controller?

In a WEBAPI filter, im trying to calculate response size.
A similar process works for MVC controllers.
Inside actionExecutedContext.Response. i cant see a filter?
So I tried this filter below but this doesnt work.
How can i get the length of a WEBApi response ?
I could stick this in Global.ASAX and it works, but then every http call is logged...
So an API filter would be ideal. Is there something obviously wrong here ?
public class BosAPIFilter : System.Web.Http.Filters.ActionFilterAttribute{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) {
base.OnActionExecuted(actionExecutedContext);
var httpContext = actionExecutedContext.Request.Properties["MS_HttpContext"] as HttpContextWrapper;
if (httpContext != null) {
actionExecutedContext.Response.
httpContext.Response.Filter = new ResponseStreamHandler(httpContext.Response.Filter);
var handler = httpContext.Response.Filter as ResponseStreamHandler;
var adminService = new AdminServices();
adminService.HttpTrace(httpContext, handler);
}
}
public class ResponseStreamHandler : MemoryStream {
private readonly Stream _responseStream;
public long ResponseSize { get; private set; }
public ResponseStreamHandler(Stream responseStream) {
this._responseStream = responseStream;
ResponseSize = 0;
}
public override void Write(byte[] buffer, int offset, int count) {
this.ResponseSize += count;
this._responseStream.Write(buffer, offset, count);
}
// ReSharper disable once RedundantOverridenMember
public override void Flush() { base.Flush(); }
}
In ASP.NET Web API pipeline, action filters run before the result you return from the action method gets serialized. If you look at actionExecutedContext.Response.Content inside the filter, it will be System.Net.Http.ObjectContent (depending on your action method). So, you can calculate the response size only later in the pipeline. You can use a message handler to do this but then the granularity is not at the action method level. The lowest granularity you can get is at a route level. One way you get around this is to set a flag in the request dictionary from the filter and log from the handler only when the flag is set.

Resources