Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit f0cab0f

Browse files
committed
[Fixes #2044] Implement an equivalent to AsyncTimeOutAttribute
1 parent 9f9dcbe commit f0cab0f

File tree

5 files changed

+165
-2
lines changed

5 files changed

+165
-2
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Threading.Tasks;
6+
using Microsoft.AspNet.Mvc.Core;
7+
using Microsoft.Framework.Internal;
8+
9+
namespace Microsoft.AspNet.Mvc
10+
{
11+
/// <summary>
12+
/// Represents an attribute that is used to set the timeout value, in milliseconds,
13+
/// which when elapsed will cause the current request to be aborted.
14+
/// </summary>
15+
[AttributeUsage(
16+
AttributeTargets.Class | AttributeTargets.Method,
17+
AllowMultiple = false,
18+
Inherited = true)]
19+
public class AsyncTimeoutAttribute : Attribute, IAsyncResourceFilter
20+
{
21+
/// <summary>
22+
/// Initializes a new instance of <see cref="AsyncTimeoutAttribute"/>.
23+
/// </summary>
24+
/// <param name="duration">The duration in milliseconds.</param>
25+
public AsyncTimeoutAttribute(int duration)
26+
{
27+
if (duration < -1)
28+
{
29+
throw new ArgumentException(
30+
Resources.FormatAsyncTimeoutAttribute_InvalidTimeout(duration, nameof(duration)));
31+
}
32+
33+
Duration = duration;
34+
}
35+
36+
/// <summary>
37+
/// The timeout duration in milliseconds.
38+
/// </summary>
39+
public int Duration { get; }
40+
41+
public async Task OnResourceExecutionAsync(
42+
[NotNull] ResourceExecutingContext context,
43+
[NotNull] ResourceExecutionDelegate next)
44+
{
45+
var httpContext = context.HttpContext;
46+
47+
// Get a task that will complete after a time delay.
48+
var timeDelayTask = Task.Delay(Duration, cancellationToken: httpContext.RequestAborted);
49+
50+
// Task representing later stages of the pipeline.
51+
var pipelineTask = next();
52+
53+
// Get the first task which completed.
54+
var completedTask = await Task.WhenAny(pipelineTask, timeDelayTask);
55+
56+
if (completedTask == pipelineTask)
57+
{
58+
// Task completed within timeout, but it could be in faulted or canceled state.
59+
// Allow the following line to throw exception and be handled somewhere else.
60+
await completedTask;
61+
}
62+
else
63+
{
64+
// Pipeline task did not complete within timeout, so abort the request.
65+
httpContext.Abort();
66+
}
67+
}
68+
}
69+
}

src/Microsoft.AspNet.Mvc.Core/Filters/ResourceExecutionDelegate.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
namespace Microsoft.AspNet.Mvc
77
{
88
/// <summary>
9-
/// A delegate which asyncronously returns a <see cref="ResourceExecutedContext"/>.
9+
/// A delegate which asynchronously returns a <see cref="ResourceExecutedContext"/>.
1010
/// </summary>
1111
/// <returns>A <see cref="ResourceExecutedContext"/>.</returns>
1212
public delegate Task<ResourceExecutedContext> ResourceExecutionDelegate();

src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs

Lines changed: 25 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.AspNet.Mvc.Core/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,4 +448,7 @@
448448
<data name="ModelType_WrongType" xml:space="preserve">
449449
<value>The model's runtime type '{0}' is not assignable to the type '{1}'.</value>
450450
</data>
451+
<data name="AsyncTimeoutAttribute_InvalidTimeout" xml:space="preserve">
452+
<value>The value '{0}' for argument '{1}' is invalid. The timeout value must be non-negative.</value>
453+
</data>
451454
</root>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
#if ASPNET50
5+
using System.Threading.Tasks;
6+
using Microsoft.AspNet.Http;
7+
using Microsoft.AspNet.Routing;
8+
using Moq;
9+
using Xunit;
10+
11+
namespace Microsoft.AspNet.Mvc.Test
12+
{
13+
public class AsyncTimeoutAttributeTest
14+
{
15+
[Fact]
16+
public async Task RequestIsAborted_AfterTimeoutDurationElapses()
17+
{
18+
// Arrange
19+
var httpContext = new Mock<HttpContext>();
20+
var asyncTimeoutAttribute = new AsyncTimeoutAttribute(1 * 1000); // 1 second
21+
var resourceExecutingContext = GetResourceExecutingContext(httpContext.Object);
22+
23+
// Act
24+
await asyncTimeoutAttribute.OnResourceExecutionAsync(
25+
resourceExecutingContext,
26+
async () =>
27+
{
28+
// Imagine here the rest of pipeline(ex: model-binding->action filters-action) being executed
29+
await Task.Delay(10 * 1000); // 10 seconds
30+
return null;
31+
});
32+
33+
// Assert
34+
httpContext.Verify(hc => hc.Abort(), Times.Once);
35+
}
36+
37+
[Fact]
38+
public async Task RequestIsNotAborted_BeforeTimeoutDurationElapses()
39+
{
40+
// Arrange
41+
var httpContext = new Mock<HttpContext>();
42+
var asyncTimeoutAttribute = new AsyncTimeoutAttribute(10 * 1000); // 10 seconds
43+
var resourceExecutingContext = GetResourceExecutingContext(httpContext.Object);
44+
45+
// Act
46+
await asyncTimeoutAttribute.OnResourceExecutionAsync(
47+
resourceExecutingContext,
48+
async () =>
49+
{
50+
// Imagine here the rest of pipeline(ex: model-binding->action filters-action) being executed
51+
await Task.Delay(1 * 1000); // 1 second
52+
return null;
53+
});
54+
55+
// Assert
56+
httpContext.Verify(hc => hc.Abort(), Times.Never);
57+
}
58+
59+
private ResourceExecutingContext GetResourceExecutingContext(HttpContext httpContext)
60+
{
61+
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
62+
63+
return new ResourceExecutingContext(actionContext, new IFilter[] { });
64+
}
65+
}
66+
}
67+
#endif

0 commit comments

Comments
 (0)