Like MVC WebApi runs on the asynchronous ASP.NET pipeline, meaning execution timeout is unsupported.
In MVC I use the [AsyncTimeout] filter, WebApi does
Building on the suggestion by Mendhak, it is possible to do what you want, though not exactly the way you'd like to do it without jumping through quite a few hoops. Doing it without a filter might look something like this:
public class ValuesController : ApiController
{
public async Task Get( )
{
var work = this.ActualWork( 5000 );
var timeout = this.Timeout( 2000 );
var finishedTask = await Task.WhenAny( timeout, work );
if( finishedTask == timeout )
{
return this.Request.CreateResponse( HttpStatusCode.RequestTimeout );
}
else
{
return this.Request.CreateResponse( HttpStatusCode.OK, work.Result );
}
}
private async Task ActualWork( int sleepTime )
{
await Task.Delay( sleepTime );
return "work results";
}
private async Task Timeout( int timeoutValue )
{
await Task.Delay( timeoutValue );
}
}
Here you will receive a timeout because the actual "work" we're doing will take longer than the timeout.
To do what you want with an attribute is possible, though not ideal. It's the same basic idea as before, but the filter could actually be used to execute the action via reflection. I don't think I would recommend this route, but in this contrived example, you can see how it might be done:
public class TimeoutFilter : ActionFilterAttribute
{
public int Timeout { get; set; }
public TimeoutFilter( )
{
this.Timeout = int.MaxValue;
}
public TimeoutFilter( int timeout )
{
this.Timeout = timeout;
}
public override async Task OnActionExecutingAsync( HttpActionContext actionContext, CancellationToken cancellationToken )
{
var controller = actionContext.ControllerContext.Controller;
var controllerType = controller.GetType( );
var action = controllerType.GetMethod( actionContext.ActionDescriptor.ActionName );
var tokenSource = new CancellationTokenSource( );
var timeout = this.TimeoutTask( this.Timeout );
object result = null;
var work = Task.Run( ( ) =>
{
result = action.Invoke( controller, actionContext.ActionArguments.Values.ToArray( ) );
}, tokenSource.Token );
var finishedTask = await Task.WhenAny( timeout, work );
if( finishedTask == timeout )
{
tokenSource.Cancel( );
actionContext.Response = actionContext.Request.CreateResponse( HttpStatusCode.RequestTimeout );
}
else
{
actionContext.Response = actionContext.Request.CreateResponse( HttpStatusCode.OK, result );
}
}
private async Task TimeoutTask( int timeoutValue )
{
await Task.Delay( timeoutValue );
}
}
This could then be used like this:
[TimeoutFilter( 10000 )]
public string Get( )
{
Thread.Sleep( 5000 );
return "Results";
}
This works for simple types (e.g. string), giving us: in Firefox, though as you can see, the serialization is not ideal. Using custom types with this exact code will be a bit problematic as far as serialization goes, but with some work, this could probably be useful in some specific scenarios. That the action parameters come in the form of a dictionary instead of an array could also pose some issues in terms of the parameter ordering. Obviously having real support for this would be better.
As far as the vNext stuff goes, they may well be planning to add the ability to do server-side timeouts for Web API since MVC and API controllers are being unified. If they do, it will likely not be through the System.Web.Mvc.AsyncTimeoutAttribute class, as they are explicitly removing dependencies on System.Web.
As of today, it doesn't appear that adding a System.Web.Mvc entry to the project.json file works, but this may well change. If it does, while you wouldn't be able to use the new cloud-optimized framework with such code, you might be able to use the AsyncTimeout attribute on code that is only intended to run with the full .NET framework.
For what it's worth, this is what I tried adding to project.json. Perhaps a specific version would have made it happier?
"frameworks": {
"net451": {
"dependencies": {
"System.Web.Mvc": ""
}
}
}
A reference to it does show up in the Solution Explorer's references list, but it does so with a yellow exclamation point indicating a problem. The application itself returns 500 errors while this reference remains.