From 527d04bc87561bc1c8a129418838436d3cae3088 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Tue, 24 Feb 2015 17:29:24 -0800 Subject: [PATCH 1/3] [Fixes #2044] Implement an equivalent to AsyncTimeOutAttribute --- .../Filters/AsyncTimeoutAttribute.cs | 69 +++++++++++++++++++ .../Filters/ResourceExecutionDelegate.cs | 2 +- .../Properties/Resources.Designer.cs | 26 ++++++- src/Microsoft.AspNet.Mvc.Core/Resources.resx | 3 + .../Filters/AsyncTimeoutAttributeTest.cs | 67 ++++++++++++++++++ 5 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Core/Filters/AsyncTimeoutAttribute.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/Filters/AsyncTimeoutAttributeTest.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/AsyncTimeoutAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/AsyncTimeoutAttribute.cs new file mode 100644 index 0000000000..9573e3a76c --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/AsyncTimeoutAttribute.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.Core; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// Represents an attribute that is used to set the timeout value, in milliseconds, + /// which when elapsed will cause the current request to be aborted. + /// + [AttributeUsage( + AttributeTargets.Class | AttributeTargets.Method, + AllowMultiple = false, + Inherited = true)] + public class AsyncTimeoutAttribute : Attribute, IAsyncResourceFilter + { + /// + /// Initializes a new instance of . + /// + /// The duration in milliseconds. + public AsyncTimeoutAttribute(int duration) + { + if (duration < -1) + { + throw new ArgumentException( + Resources.FormatAsyncTimeoutAttribute_InvalidTimeout(duration, nameof(duration))); + } + + Duration = duration; + } + + /// + /// The timeout duration in milliseconds. + /// + public int Duration { get; } + + public async Task OnResourceExecutionAsync( + [NotNull] ResourceExecutingContext context, + [NotNull] ResourceExecutionDelegate next) + { + var httpContext = context.HttpContext; + + // Get a task that will complete after a time delay. + var timeDelayTask = Task.Delay(Duration, cancellationToken: httpContext.RequestAborted); + + // Task representing later stages of the pipeline. + var pipelineTask = next(); + + // Get the first task which completed. + var completedTask = await Task.WhenAny(pipelineTask, timeDelayTask); + + if (completedTask == pipelineTask) + { + // Task completed within timeout, but it could be in faulted or canceled state. + // Allow the following line to throw exception and be handled somewhere else. + await completedTask; + } + else + { + // Pipeline task did not complete within timeout, so abort the request. + httpContext.Abort(); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ResourceExecutionDelegate.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ResourceExecutionDelegate.cs index 773d369e22..44382aa2e3 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/ResourceExecutionDelegate.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ResourceExecutionDelegate.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNet.Mvc { /// - /// A delegate which asyncronously returns a . + /// A delegate which asynchronously returns a . /// /// A . public delegate Task ResourceExecutionDelegate(); diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index 292432e9ff..b693c21476 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -1707,13 +1707,37 @@ internal static string FormatCacheProfileNotFound(object p0) } /// - /// The model type '{0}' does not match the '{1}' type parameter. + /// The model's runtime type '{0}' is not assignable to the type '{1}'. + /// + internal static string ModelType_WrongType + { + get { return GetString("ModelType_WrongType"); } + } + + /// + /// The model's runtime type '{0}' is not assignable to the type '{1}'. /// internal static string FormatModelType_WrongType(object p0, object p1) { return string.Format(CultureInfo.CurrentCulture, GetString("ModelType_WrongType"), p0, p1); } + /// + /// The value '{0}' for argument '{1}' is invalid. The timeout value must be non-negative. + /// + internal static string AsyncTimeoutAttribute_InvalidTimeout + { + get { return GetString("AsyncTimeoutAttribute_InvalidTimeout"); } + } + + /// + /// The value '{0}' for argument '{1}' is invalid. The timeout value must be non-negative. + /// + internal static string FormatAsyncTimeoutAttribute_InvalidTimeout(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("AsyncTimeoutAttribute_InvalidTimeout"), p0, p1); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx index 20099dea90..c614db7ad3 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx @@ -448,4 +448,7 @@ The model's runtime type '{0}' is not assignable to the type '{1}'. + + The value '{0}' for argument '{1}' is invalid. The timeout value must be non-negative. + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AsyncTimeoutAttributeTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AsyncTimeoutAttributeTest.cs new file mode 100644 index 0000000000..f4b6a94376 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AsyncTimeoutAttributeTest.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#if ASPNET50 +using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Routing; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Test +{ + public class AsyncTimeoutAttributeTest + { + [Fact] + public async Task RequestIsAborted_AfterTimeoutDurationElapses() + { + // Arrange + var httpContext = new Mock(); + var asyncTimeoutAttribute = new AsyncTimeoutAttribute(1 * 1000); // 1 second + var resourceExecutingContext = GetResourceExecutingContext(httpContext.Object); + + // Act + await asyncTimeoutAttribute.OnResourceExecutionAsync( + resourceExecutingContext, + async () => + { + // Imagine here the rest of pipeline(ex: model-binding->action filters-action) being executed + await Task.Delay(10 * 1000); // 10 seconds + return null; + }); + + // Assert + httpContext.Verify(hc => hc.Abort(), Times.Once); + } + + [Fact] + public async Task RequestIsNotAborted_BeforeTimeoutDurationElapses() + { + // Arrange + var httpContext = new Mock(); + var asyncTimeoutAttribute = new AsyncTimeoutAttribute(10 * 1000); // 10 seconds + var resourceExecutingContext = GetResourceExecutingContext(httpContext.Object); + + // Act + await asyncTimeoutAttribute.OnResourceExecutionAsync( + resourceExecutingContext, + async () => + { + // Imagine here the rest of pipeline(ex: model-binding->action filters-action) being executed + await Task.Delay(1 * 1000); // 1 second + return null; + }); + + // Assert + httpContext.Verify(hc => hc.Abort(), Times.Never); + } + + private ResourceExecutingContext GetResourceExecutingContext(HttpContext httpContext) + { + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); + + return new ResourceExecutingContext(actionContext, new IFilter[] { }); + } + } +} +#endif \ No newline at end of file From 88ce9985d168f10a68f86e9d0e7b607e3de19a32 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Wed, 25 Feb 2015 13:59:03 -0800 Subject: [PATCH 2/3] Addressed feedback and also added TimeoutCancellationToken feature and also added functional tests now. --- .../Filters/AsyncTimeoutAttribute.cs | 51 +++++------ .../ITimeoutCancellationTokenFeature.cs | 19 +++++ .../CancellationTokenModelBinder.cs | 46 ++++++++++ .../Properties/Resources.Designer.cs | 8 +- src/Microsoft.AspNet.Mvc.Core/Resources.resx | 2 +- .../TimeoutCancellationTokenFeature.cs | 36 ++++++++ src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs | 2 +- .../Filters/AsyncTimeoutAttributeTest.cs | 40 ++------- .../AsyncTimeoutAttributeTest.cs | 84 +++++++++++++++++++ .../CancellationTokenModelBinderTests.cs | 7 +- .../project.json | 3 +- .../MvcOptionsSetupTest.cs | 2 +- .../Controllers/AsyncTimeoutController.cs | 54 ++++++++++++ .../AsyncTimeoutOnControllerController.cs | 44 ++++++++++ 14 files changed, 324 insertions(+), 74 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Core/ITimeoutCancellationTokenFeature.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/ModelBinders/CancellationTokenModelBinder.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/TimeoutCancellationTokenFeature.cs create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/AsyncTimeoutAttributeTest.cs create mode 100644 test/WebSites/BasicWebSite/Controllers/AsyncTimeoutController.cs create mode 100644 test/WebSites/BasicWebSite/Controllers/AsyncTimeoutOnControllerController.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/AsyncTimeoutAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/AsyncTimeoutAttribute.cs index 9573e3a76c..fc509f8d9b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/AsyncTimeoutAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/AsyncTimeoutAttribute.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Core; using Microsoft.Framework.Internal; @@ -9,8 +10,8 @@ namespace Microsoft.AspNet.Mvc { /// - /// Represents an attribute that is used to set the timeout value, in milliseconds, - /// which when elapsed will cause the current request to be aborted. + /// Used to create a which gets invoked when the + /// supplied timeout value, in milliseconds, elapses. /// [AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, @@ -21,49 +22,37 @@ public class AsyncTimeoutAttribute : Attribute, IAsyncResourceFilter /// /// Initializes a new instance of . /// - /// The duration in milliseconds. - public AsyncTimeoutAttribute(int duration) + /// The duration in milliseconds. + public AsyncTimeoutAttribute(int durationInMilliseconds) { - if (duration < -1) + if (durationInMilliseconds <= 0) { - throw new ArgumentException( - Resources.FormatAsyncTimeoutAttribute_InvalidTimeout(duration, nameof(duration))); + throw new ArgumentOutOfRangeException( + nameof(durationInMilliseconds), + durationInMilliseconds, + Resources.AsyncTimeoutAttribute_InvalidTimeout); } - Duration = duration; + DurationInMilliseconds = durationInMilliseconds; } /// /// The timeout duration in milliseconds. /// - public int Duration { get; } + public int DurationInMilliseconds { get; } public async Task OnResourceExecutionAsync( [NotNull] ResourceExecutingContext context, [NotNull] ResourceExecutionDelegate next) { - var httpContext = context.HttpContext; - - // Get a task that will complete after a time delay. - var timeDelayTask = Task.Delay(Duration, cancellationToken: httpContext.RequestAborted); - - // Task representing later stages of the pipeline. - var pipelineTask = next(); - - // Get the first task which completed. - var completedTask = await Task.WhenAny(pipelineTask, timeDelayTask); - - if (completedTask == pipelineTask) - { - // Task completed within timeout, but it could be in faulted or canceled state. - // Allow the following line to throw exception and be handled somewhere else. - await completedTask; - } - else - { - // Pipeline task did not complete within timeout, so abort the request. - httpContext.Abort(); - } + // Set the feature which provides the cancellation token to later stages + // in the pipeline. This cancellation token gets invoked when the timeout value + // elapses. One can register to this cancellation token to get notified when the + // timeout occurs to take any action. + context.HttpContext.SetFeature( + new TimeoutCancellationTokenFeature(DurationInMilliseconds)); + + await next(); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ITimeoutCancellationTokenFeature.cs b/src/Microsoft.AspNet.Mvc.Core/ITimeoutCancellationTokenFeature.cs new file mode 100644 index 0000000000..2cbeefdfdf --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ITimeoutCancellationTokenFeature.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// Provides a which gets invoked after a specified timeout value. + /// + public interface ITimeoutCancellationTokenFeature + { + /// + /// Gets a which gets invoked + /// after a specified timeout value. + /// + CancellationToken TimeoutCancellationToken { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/CancellationTokenModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/CancellationTokenModelBinder.cs new file mode 100644 index 0000000000..d0a5c4aa2d --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/CancellationTokenModelBinder.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + /// + /// Represents a model binder which can bind models of type . + /// + /// + /// The provided by this binder is invoked after a supplied timeout value. + /// This binder depends on the to get the token. This feature + /// is typically set through the . + /// + public class TimeoutCancellationTokenModelBinder : IModelBinder + { + /// + public Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext.ModelType == typeof(CancellationToken)) + { + var httpContext = bindingContext.OperationBindingContext.HttpContext; + + var timeoutTokenFeature = httpContext.GetFeature(); + if (timeoutTokenFeature != null) + { + return Task.FromResult(new ModelBindingResult( + model: timeoutTokenFeature.TimeoutCancellationToken, + key: bindingContext.ModelName, + isModelSet: true)); + } + else + { + return Task.FromResult(new ModelBindingResult( + model: CancellationToken.None, + key: bindingContext.ModelName, + isModelSet: false)); + } + } + + return Task.FromResult(null); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index b693c21476..e801957eb6 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -1723,7 +1723,7 @@ internal static string FormatModelType_WrongType(object p0, object p1) } /// - /// The value '{0}' for argument '{1}' is invalid. The timeout value must be non-negative. + /// The timeout value must be greater than 0. /// internal static string AsyncTimeoutAttribute_InvalidTimeout { @@ -1731,11 +1731,11 @@ internal static string AsyncTimeoutAttribute_InvalidTimeout } /// - /// The value '{0}' for argument '{1}' is invalid. The timeout value must be non-negative. + /// The timeout value must be greater than 0. /// - internal static string FormatAsyncTimeoutAttribute_InvalidTimeout(object p0, object p1) + internal static string FormatAsyncTimeoutAttribute_InvalidTimeout() { - return string.Format(CultureInfo.CurrentCulture, GetString("AsyncTimeoutAttribute_InvalidTimeout"), p0, p1); + return GetString("AsyncTimeoutAttribute_InvalidTimeout"); } private static string GetString(string name, params string[] formatterNames) diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx index c614db7ad3..427da7580e 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx @@ -449,6 +449,6 @@ The model's runtime type '{0}' is not assignable to the type '{1}'. - The value '{0}' for argument '{1}' is invalid. The timeout value must be non-negative. + The timeout value must be greater than 0. \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/TimeoutCancellationTokenFeature.cs b/src/Microsoft.AspNet.Mvc.Core/TimeoutCancellationTokenFeature.cs new file mode 100644 index 0000000000..0251be054f --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/TimeoutCancellationTokenFeature.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using Microsoft.AspNet.Mvc.Core; + +namespace Microsoft.AspNet.Mvc +{ + /// + public class TimeoutCancellationTokenFeature : ITimeoutCancellationTokenFeature + { + /// + /// Initializes a new instance of . + /// + /// The duration in milliseconds. + public TimeoutCancellationTokenFeature(int durationInMilliseconds) + { + if (durationInMilliseconds <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(durationInMilliseconds), + durationInMilliseconds, + Resources.AsyncTimeoutAttribute_InvalidTimeout); + } + + var timeoutCancellationTokenSource = new CancellationTokenSource(); + timeoutCancellationTokenSource.CancelAfter(millisecondsDelay: durationInMilliseconds); + + TimeoutCancellationToken = timeoutCancellationTokenSource.Token; + } + + /// + public CancellationToken TimeoutCancellationToken { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs index 749dcf7340..d0ba9b4aa4 100644 --- a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs @@ -34,7 +34,7 @@ public static void ConfigureMvcOptions(MvcOptions options) options.ModelBinders.Add(new HeaderModelBinder()); options.ModelBinders.Add(new TypeConverterModelBinder()); options.ModelBinders.Add(new TypeMatchModelBinder()); - options.ModelBinders.Add(new CancellationTokenModelBinder()); + options.ModelBinders.Add(new TimeoutCancellationTokenModelBinder()); options.ModelBinders.Add(new ByteArrayModelBinder()); options.ModelBinders.Add(new FormFileModelBinder()); options.ModelBinders.Add(new FormCollectionModelBinder()); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AsyncTimeoutAttributeTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AsyncTimeoutAttributeTest.cs index f4b6a94376..d9b41dec76 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AsyncTimeoutAttributeTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AsyncTimeoutAttributeTest.cs @@ -4,6 +4,7 @@ #if ASPNET50 using System.Threading.Tasks; using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Core; using Microsoft.AspNet.Routing; using Moq; using Xunit; @@ -13,47 +14,22 @@ namespace Microsoft.AspNet.Mvc.Test public class AsyncTimeoutAttributeTest { [Fact] - public async Task RequestIsAborted_AfterTimeoutDurationElapses() + public async Task SetsTimeoutCancellationTokenFeature_OnExecution() { // Arrange - var httpContext = new Mock(); + var httpContext = new DefaultHttpContext(); var asyncTimeoutAttribute = new AsyncTimeoutAttribute(1 * 1000); // 1 second - var resourceExecutingContext = GetResourceExecutingContext(httpContext.Object); - - // Act - await asyncTimeoutAttribute.OnResourceExecutionAsync( - resourceExecutingContext, - async () => - { - // Imagine here the rest of pipeline(ex: model-binding->action filters-action) being executed - await Task.Delay(10 * 1000); // 10 seconds - return null; - }); - - // Assert - httpContext.Verify(hc => hc.Abort(), Times.Once); - } + var resourceExecutingContext = GetResourceExecutingContext(httpContext); - [Fact] - public async Task RequestIsNotAborted_BeforeTimeoutDurationElapses() - { - // Arrange - var httpContext = new Mock(); - var asyncTimeoutAttribute = new AsyncTimeoutAttribute(10 * 1000); // 10 seconds - var resourceExecutingContext = GetResourceExecutingContext(httpContext.Object); - // Act await asyncTimeoutAttribute.OnResourceExecutionAsync( resourceExecutingContext, - async () => - { - // Imagine here the rest of pipeline(ex: model-binding->action filters-action) being executed - await Task.Delay(1 * 1000); // 1 second - return null; - }); + () => Task.FromResult(null)); // Assert - httpContext.Verify(hc => hc.Abort(), Times.Never); + var timeoutFeature = resourceExecutingContext.HttpContext.GetFeature(); + Assert.NotNull(timeoutFeature); + Assert.NotNull(timeoutFeature.TimeoutCancellationToken); } private ResourceExecutingContext GetResourceExecutingContext(HttpContext httpContext) diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/AsyncTimeoutAttributeTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/AsyncTimeoutAttributeTest.cs new file mode 100644 index 0000000000..33ee142d27 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/AsyncTimeoutAttributeTest.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using BasicWebSite; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.TestHost; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + public class AsyncTimeoutAttributeTest + { + private readonly IServiceProvider _provider = TestHelper.CreateServices(nameof(BasicWebSite)); + private readonly Action _app = new Startup().Configure; + + [Theory] + [InlineData("http://localhost/AsyncTimeout/ActionWithTimeoutAttribute")] + [InlineData("http://localhost/AsyncTimeoutOnController/ActionWithTimeoutAttribute")] + public async Task AsyncTimeOutAttribute_IsDecorated_AndCancellationTokenIsBound(string url) + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + var expected = "CancellationToken is present"; + + // Act + var response = await client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var data = await response.Content.ReadAsStringAsync(); + Assert.Equal(expected, data); + } + + [Fact] + public async Task AsyncTimeOutAttribute_IsNotDecorated_AndCancellationToken_IsNone() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + var expected = "CancellationToken is not present"; + + // Act + var response = await client.GetAsync("http://localhost/AsyncTimeout/ActionWithNoTimeoutAttribute"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var data = await response.Content.ReadAsStringAsync(); + Assert.Equal(expected, data); + } + + [Theory] + [InlineData("http://localhost/AsyncTimeout")] + [InlineData("http://localhost/AsyncTimeoutOnController")] + public async Task TimeoutIsTriggered(string baseUrl) + { + // Arrange + var expected = "Hello World!"; + var expectedCorrelationId = Guid.NewGuid().ToString(); + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + client.DefaultRequestHeaders.Add("CorrelationId", expectedCorrelationId); + + // Act + var response = await client.GetAsync(string.Format("{0}/LongRunningAction", baseUrl)); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var data = await response.Content.ReadAsStringAsync(); + Assert.Equal(expected, data); + + response = await client.GetAsync(string.Format("{0}/TimeoutTriggerLogs", baseUrl)); + data = await response.Content.ReadAsStringAsync(); + var timeoutTriggerLogs = JsonConvert.DeserializeObject>(data); + Assert.NotNull(timeoutTriggerLogs); + Assert.Contains(expectedCorrelationId, timeoutTriggerLogs); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CancellationTokenModelBinderTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CancellationTokenModelBinderTests.cs index 3a67cc32ce..59ddc314c3 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CancellationTokenModelBinderTests.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CancellationTokenModelBinderTests.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.Http.Core; +using Microsoft.AspNet.Mvc.ModelBinding; using Xunit; namespace Microsoft.AspNet.Mvc.ModelBinding.Test @@ -16,7 +17,7 @@ public async Task CancellationTokenModelBinder_ReturnsTrue_ForCancellationTokenT { // Arrange var bindingContext = GetBindingContext(typeof(CancellationToken)); - var binder = new CancellationTokenModelBinder(); + var binder = new TimeoutCancellationTokenModelBinder(); // Act var bound = await binder.BindModelAsync(bindingContext); @@ -34,7 +35,7 @@ public async Task CancellationTokenModelBinder_ReturnsFalse_ForNonCancellationTo { // Arrange var bindingContext = GetBindingContext(t); - var binder = new CancellationTokenModelBinder(); + var binder = new TimeoutCancellationTokenModelBinder(); // Act var bound = await binder.BindModelAsync(bindingContext); @@ -53,7 +54,7 @@ private static ModelBindingContext GetBindingContext(Type modelType) ValueProvider = new SimpleHttpValueProvider(), OperationBindingContext = new OperationBindingContext { - ModelBinder = new CancellationTokenModelBinder(), + ModelBinder = new TimeoutCancellationTokenModelBinder(), MetadataProvider = metadataProvider, HttpContext = new DefaultHttpContext(), } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/project.json b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/project.json index f92fc47452..52c7c80d85 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/project.json +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/project.json @@ -4,7 +4,8 @@ }, "dependencies": { "Microsoft.AspNet.Http.Core": "1.0.0-*", - "Microsoft.AspNet.Mvc.ModelBinding": "", + "Microsoft.AspNet.Mvc.ModelBinding": "6.0.0-*", + "Microsoft.AspNet.Mvc.Core": "6.0.0-*", "Microsoft.AspNet.Testing": "1.0.0-*", "Moq": "4.2.1312.1622", "xunit.runner.kre": "1.0.0-*" diff --git a/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs b/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs index 3f16a5ad2d..156c826f37 100644 --- a/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs +++ b/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs @@ -46,7 +46,7 @@ public void Setup_SetsUpModelBinders() Assert.Equal(typeof(HeaderModelBinder), mvcOptions.ModelBinders[i++].OptionType); Assert.Equal(typeof(TypeConverterModelBinder), mvcOptions.ModelBinders[i++].OptionType); Assert.Equal(typeof(TypeMatchModelBinder), mvcOptions.ModelBinders[i++].OptionType); - Assert.Equal(typeof(CancellationTokenModelBinder), mvcOptions.ModelBinders[i++].OptionType); + Assert.Equal(typeof(TimeoutCancellationTokenModelBinder), mvcOptions.ModelBinders[i++].OptionType); Assert.Equal(typeof(ByteArrayModelBinder), mvcOptions.ModelBinders[i++].OptionType); Assert.Equal(typeof(FormFileModelBinder), mvcOptions.ModelBinders[i++].OptionType); Assert.Equal(typeof(FormCollectionModelBinder), mvcOptions.ModelBinders[i++].OptionType); diff --git a/test/WebSites/BasicWebSite/Controllers/AsyncTimeoutController.cs b/test/WebSites/BasicWebSite/Controllers/AsyncTimeoutController.cs new file mode 100644 index 0000000000..fd0690ef17 --- /dev/null +++ b/test/WebSites/BasicWebSite/Controllers/AsyncTimeoutController.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc; + +namespace BasicWebSite +{ + public class AsyncTimeoutController : Controller + { + private static List TimeoutTriggerLog = new List(); + + [AsyncTimeout(1000)] + public string ActionWithTimeoutAttribute(CancellationToken timeoutToken) + { + if (timeoutToken != CancellationToken.None) + { + return "CancellationToken is present"; + } + + return "CancellationToken is not present"; + } + + public string ActionWithNoTimeoutAttribute(CancellationToken timeoutToken) + { + if (timeoutToken != CancellationToken.None) + { + return "CancellationToken is present"; + } + + return "CancellationToken is not present"; + } + + [AsyncTimeout(500)] + public async Task LongRunningAction(CancellationToken timeoutToken) + { + timeoutToken.Register((requestCorrelationId) => + { + TimeoutTriggerLog.Add(requestCorrelationId.ToString()); + }, Request.Headers.Get("CorrelationId")); + + await Task.Delay(3 * 1000); + + return "Hello World!"; + } + + public List TimeoutTriggerLogs() + { + return TimeoutTriggerLog; + } + } +} \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Controllers/AsyncTimeoutOnControllerController.cs b/test/WebSites/BasicWebSite/Controllers/AsyncTimeoutOnControllerController.cs new file mode 100644 index 0000000000..724e5a10f1 --- /dev/null +++ b/test/WebSites/BasicWebSite/Controllers/AsyncTimeoutOnControllerController.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc; + +namespace BasicWebSite.Controllers +{ + [AsyncTimeout(1000)] + public class AsyncTimeoutOnControllerController : Controller + { + private static List TimeoutTriggerLog = new List(); + + public string ActionWithTimeoutAttribute(CancellationToken timeoutToken) + { + if (timeoutToken != CancellationToken.None) + { + return "CancellationToken is present"; + } + + return "CancellationToken is not present"; + } + + [AsyncTimeout(500)] + public async Task LongRunningAction(CancellationToken timeoutToken) + { + timeoutToken.Register((requestCorrelationId) => + { + TimeoutTriggerLog.Add(requestCorrelationId.ToString()); + }, Request.Headers.Get("CorrelationId")); + + await Task.Delay(3 * 1000); + + return "Hello World!"; + } + + public List TimeoutTriggerLogs() + { + return TimeoutTriggerLog; + } + } +} \ No newline at end of file From 253cf22f5cf541101bf289f057bf40b1e4716621 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Fri, 27 Feb 2015 11:05:15 -0800 Subject: [PATCH 3/3] Reverted changes --- .../Filters/AsyncTimeoutAttribute.cs | 35 +++++--- .../ITimeoutCancellationTokenFeature.cs | 19 ----- .../CancellationTokenModelBinder.cs | 46 ---------- .../Properties/Resources.Designer.cs | 4 +- src/Microsoft.AspNet.Mvc.Core/Resources.resx | 2 +- .../TimeoutCancellationTokenFeature.cs | 36 -------- src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs | 2 +- .../Filters/AsyncTimeoutAttributeTest.cs | 40 +++++++-- .../AsyncTimeoutAttributeTest.cs | 84 ------------------- .../CancellationTokenModelBinderTests.cs | 7 +- .../project.json | 3 +- .../MvcOptionsSetupTest.cs | 2 +- .../Controllers/AsyncTimeoutController.cs | 54 ------------ .../AsyncTimeoutOnControllerController.cs | 44 ---------- 14 files changed, 65 insertions(+), 313 deletions(-) delete mode 100644 src/Microsoft.AspNet.Mvc.Core/ITimeoutCancellationTokenFeature.cs delete mode 100644 src/Microsoft.AspNet.Mvc.Core/ModelBinders/CancellationTokenModelBinder.cs delete mode 100644 src/Microsoft.AspNet.Mvc.Core/TimeoutCancellationTokenFeature.cs delete mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/AsyncTimeoutAttributeTest.cs delete mode 100644 test/WebSites/BasicWebSite/Controllers/AsyncTimeoutController.cs delete mode 100644 test/WebSites/BasicWebSite/Controllers/AsyncTimeoutOnControllerController.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/AsyncTimeoutAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/AsyncTimeoutAttribute.cs index fc509f8d9b..13a67240f0 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/AsyncTimeoutAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/AsyncTimeoutAttribute.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Core; using Microsoft.Framework.Internal; @@ -10,8 +9,8 @@ namespace Microsoft.AspNet.Mvc { /// - /// Used to create a which gets invoked when the - /// supplied timeout value, in milliseconds, elapses. + /// Used to set the timeout value, in milliseconds, + /// which when elapsed will cause the current request to be aborted. /// [AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, @@ -45,14 +44,28 @@ public async Task OnResourceExecutionAsync( [NotNull] ResourceExecutingContext context, [NotNull] ResourceExecutionDelegate next) { - // Set the feature which provides the cancellation token to later stages - // in the pipeline. This cancellation token gets invoked when the timeout value - // elapses. One can register to this cancellation token to get notified when the - // timeout occurs to take any action. - context.HttpContext.SetFeature( - new TimeoutCancellationTokenFeature(DurationInMilliseconds)); - - await next(); + var httpContext = context.HttpContext; + + // Get a task that will complete after a time delay. + var timeDelayTask = Task.Delay(DurationInMilliseconds, cancellationToken: httpContext.RequestAborted); + + // Task representing later stages of the pipeline. + var pipelineTask = next(); + + // Get the first task which completed. + var completedTask = await Task.WhenAny(pipelineTask, timeDelayTask); + + if (completedTask == pipelineTask) + { + // Task completed within timeout, but it could be in faulted or canceled state. + // Allow the following line to throw exception and be handled somewhere else. + await completedTask; + } + else + { + // Pipeline task did not complete within timeout, so abort the request. + httpContext.Abort(); + } } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ITimeoutCancellationTokenFeature.cs b/src/Microsoft.AspNet.Mvc.Core/ITimeoutCancellationTokenFeature.cs deleted file mode 100644 index 2cbeefdfdf..0000000000 --- a/src/Microsoft.AspNet.Mvc.Core/ITimeoutCancellationTokenFeature.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading; - -namespace Microsoft.AspNet.Mvc -{ - /// - /// Provides a which gets invoked after a specified timeout value. - /// - public interface ITimeoutCancellationTokenFeature - { - /// - /// Gets a which gets invoked - /// after a specified timeout value. - /// - CancellationToken TimeoutCancellationToken { get; } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/CancellationTokenModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/CancellationTokenModelBinder.cs deleted file mode 100644 index d0a5c4aa2d..0000000000 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/CancellationTokenModelBinder.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.AspNet.Mvc.ModelBinding -{ - /// - /// Represents a model binder which can bind models of type . - /// - /// - /// The provided by this binder is invoked after a supplied timeout value. - /// This binder depends on the to get the token. This feature - /// is typically set through the . - /// - public class TimeoutCancellationTokenModelBinder : IModelBinder - { - /// - public Task BindModelAsync(ModelBindingContext bindingContext) - { - if (bindingContext.ModelType == typeof(CancellationToken)) - { - var httpContext = bindingContext.OperationBindingContext.HttpContext; - - var timeoutTokenFeature = httpContext.GetFeature(); - if (timeoutTokenFeature != null) - { - return Task.FromResult(new ModelBindingResult( - model: timeoutTokenFeature.TimeoutCancellationToken, - key: bindingContext.ModelName, - isModelSet: true)); - } - else - { - return Task.FromResult(new ModelBindingResult( - model: CancellationToken.None, - key: bindingContext.ModelName, - isModelSet: false)); - } - } - - return Task.FromResult(null); - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index e801957eb6..1c5ce46f32 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -1723,7 +1723,7 @@ internal static string FormatModelType_WrongType(object p0, object p1) } /// - /// The timeout value must be greater than 0. + /// The timeout value must be non-negative. /// internal static string AsyncTimeoutAttribute_InvalidTimeout { @@ -1731,7 +1731,7 @@ internal static string AsyncTimeoutAttribute_InvalidTimeout } /// - /// The timeout value must be greater than 0. + /// The timeout value must be non-negative. /// internal static string FormatAsyncTimeoutAttribute_InvalidTimeout() { diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx index 427da7580e..c2b65e478a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx @@ -449,6 +449,6 @@ The model's runtime type '{0}' is not assignable to the type '{1}'. - The timeout value must be greater than 0. + The timeout value must be non-negative. \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/TimeoutCancellationTokenFeature.cs b/src/Microsoft.AspNet.Mvc.Core/TimeoutCancellationTokenFeature.cs deleted file mode 100644 index 0251be054f..0000000000 --- a/src/Microsoft.AspNet.Mvc.Core/TimeoutCancellationTokenFeature.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Threading; -using Microsoft.AspNet.Mvc.Core; - -namespace Microsoft.AspNet.Mvc -{ - /// - public class TimeoutCancellationTokenFeature : ITimeoutCancellationTokenFeature - { - /// - /// Initializes a new instance of . - /// - /// The duration in milliseconds. - public TimeoutCancellationTokenFeature(int durationInMilliseconds) - { - if (durationInMilliseconds <= 0) - { - throw new ArgumentOutOfRangeException( - nameof(durationInMilliseconds), - durationInMilliseconds, - Resources.AsyncTimeoutAttribute_InvalidTimeout); - } - - var timeoutCancellationTokenSource = new CancellationTokenSource(); - timeoutCancellationTokenSource.CancelAfter(millisecondsDelay: durationInMilliseconds); - - TimeoutCancellationToken = timeoutCancellationTokenSource.Token; - } - - /// - public CancellationToken TimeoutCancellationToken { get; } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs index d0ba9b4aa4..749dcf7340 100644 --- a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs @@ -34,7 +34,7 @@ public static void ConfigureMvcOptions(MvcOptions options) options.ModelBinders.Add(new HeaderModelBinder()); options.ModelBinders.Add(new TypeConverterModelBinder()); options.ModelBinders.Add(new TypeMatchModelBinder()); - options.ModelBinders.Add(new TimeoutCancellationTokenModelBinder()); + options.ModelBinders.Add(new CancellationTokenModelBinder()); options.ModelBinders.Add(new ByteArrayModelBinder()); options.ModelBinders.Add(new FormFileModelBinder()); options.ModelBinders.Add(new FormCollectionModelBinder()); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AsyncTimeoutAttributeTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AsyncTimeoutAttributeTest.cs index d9b41dec76..f4b6a94376 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AsyncTimeoutAttributeTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Filters/AsyncTimeoutAttributeTest.cs @@ -4,7 +4,6 @@ #if ASPNET50 using System.Threading.Tasks; using Microsoft.AspNet.Http; -using Microsoft.AspNet.Http.Core; using Microsoft.AspNet.Routing; using Moq; using Xunit; @@ -14,22 +13,47 @@ namespace Microsoft.AspNet.Mvc.Test public class AsyncTimeoutAttributeTest { [Fact] - public async Task SetsTimeoutCancellationTokenFeature_OnExecution() + public async Task RequestIsAborted_AfterTimeoutDurationElapses() { // Arrange - var httpContext = new DefaultHttpContext(); + var httpContext = new Mock(); var asyncTimeoutAttribute = new AsyncTimeoutAttribute(1 * 1000); // 1 second - var resourceExecutingContext = GetResourceExecutingContext(httpContext); + var resourceExecutingContext = GetResourceExecutingContext(httpContext.Object); + + // Act + await asyncTimeoutAttribute.OnResourceExecutionAsync( + resourceExecutingContext, + async () => + { + // Imagine here the rest of pipeline(ex: model-binding->action filters-action) being executed + await Task.Delay(10 * 1000); // 10 seconds + return null; + }); + + // Assert + httpContext.Verify(hc => hc.Abort(), Times.Once); + } + [Fact] + public async Task RequestIsNotAborted_BeforeTimeoutDurationElapses() + { + // Arrange + var httpContext = new Mock(); + var asyncTimeoutAttribute = new AsyncTimeoutAttribute(10 * 1000); // 10 seconds + var resourceExecutingContext = GetResourceExecutingContext(httpContext.Object); + // Act await asyncTimeoutAttribute.OnResourceExecutionAsync( resourceExecutingContext, - () => Task.FromResult(null)); + async () => + { + // Imagine here the rest of pipeline(ex: model-binding->action filters-action) being executed + await Task.Delay(1 * 1000); // 1 second + return null; + }); // Assert - var timeoutFeature = resourceExecutingContext.HttpContext.GetFeature(); - Assert.NotNull(timeoutFeature); - Assert.NotNull(timeoutFeature.TimeoutCancellationToken); + httpContext.Verify(hc => hc.Abort(), Times.Never); } private ResourceExecutingContext GetResourceExecutingContext(HttpContext httpContext) diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/AsyncTimeoutAttributeTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/AsyncTimeoutAttributeTest.cs deleted file mode 100644 index 33ee142d27..0000000000 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/AsyncTimeoutAttributeTest.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading.Tasks; -using BasicWebSite; -using Microsoft.AspNet.Builder; -using Microsoft.AspNet.TestHost; -using Newtonsoft.Json; -using Xunit; - -namespace Microsoft.AspNet.Mvc.FunctionalTests -{ - public class AsyncTimeoutAttributeTest - { - private readonly IServiceProvider _provider = TestHelper.CreateServices(nameof(BasicWebSite)); - private readonly Action _app = new Startup().Configure; - - [Theory] - [InlineData("http://localhost/AsyncTimeout/ActionWithTimeoutAttribute")] - [InlineData("http://localhost/AsyncTimeoutOnController/ActionWithTimeoutAttribute")] - public async Task AsyncTimeOutAttribute_IsDecorated_AndCancellationTokenIsBound(string url) - { - // Arrange - var server = TestServer.Create(_provider, _app); - var client = server.CreateClient(); - var expected = "CancellationToken is present"; - - // Act - var response = await client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var data = await response.Content.ReadAsStringAsync(); - Assert.Equal(expected, data); - } - - [Fact] - public async Task AsyncTimeOutAttribute_IsNotDecorated_AndCancellationToken_IsNone() - { - // Arrange - var server = TestServer.Create(_provider, _app); - var client = server.CreateClient(); - var expected = "CancellationToken is not present"; - - // Act - var response = await client.GetAsync("http://localhost/AsyncTimeout/ActionWithNoTimeoutAttribute"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var data = await response.Content.ReadAsStringAsync(); - Assert.Equal(expected, data); - } - - [Theory] - [InlineData("http://localhost/AsyncTimeout")] - [InlineData("http://localhost/AsyncTimeoutOnController")] - public async Task TimeoutIsTriggered(string baseUrl) - { - // Arrange - var expected = "Hello World!"; - var expectedCorrelationId = Guid.NewGuid().ToString(); - var server = TestServer.Create(_provider, _app); - var client = server.CreateClient(); - client.DefaultRequestHeaders.Add("CorrelationId", expectedCorrelationId); - - // Act - var response = await client.GetAsync(string.Format("{0}/LongRunningAction", baseUrl)); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var data = await response.Content.ReadAsStringAsync(); - Assert.Equal(expected, data); - - response = await client.GetAsync(string.Format("{0}/TimeoutTriggerLogs", baseUrl)); - data = await response.Content.ReadAsStringAsync(); - var timeoutTriggerLogs = JsonConvert.DeserializeObject>(data); - Assert.NotNull(timeoutTriggerLogs); - Assert.Contains(expectedCorrelationId, timeoutTriggerLogs); - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CancellationTokenModelBinderTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CancellationTokenModelBinderTests.cs index 59ddc314c3..3a67cc32ce 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CancellationTokenModelBinderTests.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/CancellationTokenModelBinderTests.cs @@ -5,7 +5,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.Http.Core; -using Microsoft.AspNet.Mvc.ModelBinding; using Xunit; namespace Microsoft.AspNet.Mvc.ModelBinding.Test @@ -17,7 +16,7 @@ public async Task CancellationTokenModelBinder_ReturnsTrue_ForCancellationTokenT { // Arrange var bindingContext = GetBindingContext(typeof(CancellationToken)); - var binder = new TimeoutCancellationTokenModelBinder(); + var binder = new CancellationTokenModelBinder(); // Act var bound = await binder.BindModelAsync(bindingContext); @@ -35,7 +34,7 @@ public async Task CancellationTokenModelBinder_ReturnsFalse_ForNonCancellationTo { // Arrange var bindingContext = GetBindingContext(t); - var binder = new TimeoutCancellationTokenModelBinder(); + var binder = new CancellationTokenModelBinder(); // Act var bound = await binder.BindModelAsync(bindingContext); @@ -54,7 +53,7 @@ private static ModelBindingContext GetBindingContext(Type modelType) ValueProvider = new SimpleHttpValueProvider(), OperationBindingContext = new OperationBindingContext { - ModelBinder = new TimeoutCancellationTokenModelBinder(), + ModelBinder = new CancellationTokenModelBinder(), MetadataProvider = metadataProvider, HttpContext = new DefaultHttpContext(), } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/project.json b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/project.json index 52c7c80d85..f92fc47452 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/project.json +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/project.json @@ -4,8 +4,7 @@ }, "dependencies": { "Microsoft.AspNet.Http.Core": "1.0.0-*", - "Microsoft.AspNet.Mvc.ModelBinding": "6.0.0-*", - "Microsoft.AspNet.Mvc.Core": "6.0.0-*", + "Microsoft.AspNet.Mvc.ModelBinding": "", "Microsoft.AspNet.Testing": "1.0.0-*", "Moq": "4.2.1312.1622", "xunit.runner.kre": "1.0.0-*" diff --git a/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs b/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs index 156c826f37..3f16a5ad2d 100644 --- a/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs +++ b/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs @@ -46,7 +46,7 @@ public void Setup_SetsUpModelBinders() Assert.Equal(typeof(HeaderModelBinder), mvcOptions.ModelBinders[i++].OptionType); Assert.Equal(typeof(TypeConverterModelBinder), mvcOptions.ModelBinders[i++].OptionType); Assert.Equal(typeof(TypeMatchModelBinder), mvcOptions.ModelBinders[i++].OptionType); - Assert.Equal(typeof(TimeoutCancellationTokenModelBinder), mvcOptions.ModelBinders[i++].OptionType); + Assert.Equal(typeof(CancellationTokenModelBinder), mvcOptions.ModelBinders[i++].OptionType); Assert.Equal(typeof(ByteArrayModelBinder), mvcOptions.ModelBinders[i++].OptionType); Assert.Equal(typeof(FormFileModelBinder), mvcOptions.ModelBinders[i++].OptionType); Assert.Equal(typeof(FormCollectionModelBinder), mvcOptions.ModelBinders[i++].OptionType); diff --git a/test/WebSites/BasicWebSite/Controllers/AsyncTimeoutController.cs b/test/WebSites/BasicWebSite/Controllers/AsyncTimeoutController.cs deleted file mode 100644 index fd0690ef17..0000000000 --- a/test/WebSites/BasicWebSite/Controllers/AsyncTimeoutController.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNet.Mvc; - -namespace BasicWebSite -{ - public class AsyncTimeoutController : Controller - { - private static List TimeoutTriggerLog = new List(); - - [AsyncTimeout(1000)] - public string ActionWithTimeoutAttribute(CancellationToken timeoutToken) - { - if (timeoutToken != CancellationToken.None) - { - return "CancellationToken is present"; - } - - return "CancellationToken is not present"; - } - - public string ActionWithNoTimeoutAttribute(CancellationToken timeoutToken) - { - if (timeoutToken != CancellationToken.None) - { - return "CancellationToken is present"; - } - - return "CancellationToken is not present"; - } - - [AsyncTimeout(500)] - public async Task LongRunningAction(CancellationToken timeoutToken) - { - timeoutToken.Register((requestCorrelationId) => - { - TimeoutTriggerLog.Add(requestCorrelationId.ToString()); - }, Request.Headers.Get("CorrelationId")); - - await Task.Delay(3 * 1000); - - return "Hello World!"; - } - - public List TimeoutTriggerLogs() - { - return TimeoutTriggerLog; - } - } -} \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Controllers/AsyncTimeoutOnControllerController.cs b/test/WebSites/BasicWebSite/Controllers/AsyncTimeoutOnControllerController.cs deleted file mode 100644 index 724e5a10f1..0000000000 --- a/test/WebSites/BasicWebSite/Controllers/AsyncTimeoutOnControllerController.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNet.Mvc; - -namespace BasicWebSite.Controllers -{ - [AsyncTimeout(1000)] - public class AsyncTimeoutOnControllerController : Controller - { - private static List TimeoutTriggerLog = new List(); - - public string ActionWithTimeoutAttribute(CancellationToken timeoutToken) - { - if (timeoutToken != CancellationToken.None) - { - return "CancellationToken is present"; - } - - return "CancellationToken is not present"; - } - - [AsyncTimeout(500)] - public async Task LongRunningAction(CancellationToken timeoutToken) - { - timeoutToken.Register((requestCorrelationId) => - { - TimeoutTriggerLog.Add(requestCorrelationId.ToString()); - }, Request.Headers.Get("CorrelationId")); - - await Task.Delay(3 * 1000); - - return "Hello World!"; - } - - public List TimeoutTriggerLogs() - { - return TimeoutTriggerLog; - } - } -} \ No newline at end of file