From 85bd056780a19de8bbf2b6231b120dea1d3f0c4d Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 16 Jun 2014 07:55:57 -0700 Subject: [PATCH] CreateActionResult should work when action returns Task. Fixes #647 --- .../ReflectedActionInvoker.cs | 48 ++++++++--------- .../ReflectedActionInvokerTest.cs | 54 +++++++++---------- .../BasicTests.cs | 16 ++++++ .../Controllers/HomeController.cs | 13 ++++- 4 files changed, 77 insertions(+), 54 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs index f51ab7367c..a2c14c7fa9 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs @@ -101,23 +101,25 @@ namespace Microsoft.AspNet.Mvc { // optimize common path var actionResult = actionReturnValue as IActionResult; - if (actionResult != null) { return actionResult; } - if (actionReturnValue == null && typeof(IActionResult).IsAssignableFrom(declaredReturnType)) - { - throw new InvalidOperationException( - Resources.FormatActionResult_ActionReturnValueCannotBeNull(declaredReturnType)); - } - - if (declaredReturnType == typeof(void)) + if (declaredReturnType == typeof(void) || + declaredReturnType == typeof(Task)) { return new NoContentResult(); } + // Unwrap potential Task types. + var actualReturnType = TypeHelper.GetTaskInnerTypeOrNull(declaredReturnType) ?? declaredReturnType; + if (actionReturnValue == null && typeof(IActionResult).IsAssignableFrom(actualReturnType)) + { + throw new InvalidOperationException( + Resources.FormatActionResult_ActionReturnValueCannotBeNull(actualReturnType)); + } + return new ObjectContentResult(actionReturnValue); } @@ -275,7 +277,7 @@ namespace Microsoft.AspNet.Mvc { var parameterType = parameter.BodyParameterInfo.ParameterType; var modelMetadata = metadataProvider.GetMetadataForType( - modelAccessor: null, + modelAccessor: null, modelType: parameterType); var providerContext = new InputFormatterProviderContext( actionBindingContext.ActionContext.HttpContext, @@ -295,7 +297,7 @@ namespace Microsoft.AspNet.Mvc { var parameterType = parameter.ParameterBindingInfo.ParameterType; var modelMetadata = metadataProvider.GetMetadataForType( - modelAccessor: null, + modelAccessor: null, modelType: parameterType); var modelBindingContext = new ModelBindingContext @@ -400,12 +402,8 @@ namespace Microsoft.AspNet.Mvc _actionContext.Controller, _actionExecutingContext.ActionArguments); - var underlyingReturnType = - TypeHelper.GetTaskInnerTypeOrNull(actionMethodInfo.ReturnType) ?? - actionMethodInfo.ReturnType; - var actionResult = CreateActionResult( - underlyingReturnType, + actionMethodInfo.ReturnType, actionReturnValue); return actionResult; } @@ -459,8 +457,8 @@ namespace Microsoft.AspNet.Mvc { // Short-circuited by not calling next _resultExecutedContext = new ResultExecutedContext( - _resultExecutingContext, - _filters, + _resultExecutingContext, + _filters, _resultExecutingContext.Result) { Canceled = true, @@ -470,8 +468,8 @@ namespace Microsoft.AspNet.Mvc { // Short-circuited by setting Cancel == true _resultExecutedContext = new ResultExecutedContext( - _resultExecutingContext, - _filters, + _resultExecutingContext, + _filters, _resultExecutingContext.Result) { Canceled = true, @@ -486,8 +484,8 @@ namespace Microsoft.AspNet.Mvc { // Short-circuited by setting Cancel == true _resultExecutedContext = new ResultExecutedContext( - _resultExecutingContext, - _filters, + _resultExecutingContext, + _filters, _resultExecutingContext.Result) { Canceled = true, @@ -504,16 +502,16 @@ namespace Microsoft.AspNet.Mvc Contract.Assert(_resultExecutedContext == null); _resultExecutedContext = new ResultExecutedContext( - _resultExecutingContext, - _filters, + _resultExecutingContext, + _filters, _resultExecutingContext.Result); } } catch (Exception exception) { _resultExecutedContext = new ResultExecutedContext( - _resultExecutingContext, - _filters, + _resultExecutingContext, + _filters, _resultExecutingContext.Result) { ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception) diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionInvokerTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionInvokerTest.cs index 97015f36ac..9e3536855b 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionInvokerTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionInvokerTest.cs @@ -29,22 +29,6 @@ namespace Microsoft.AspNet.Mvc public int x; } - public static IEnumerable CreateActionResultData - { - get - { - yield return new object[] { new { x1 = 10, y1 = "Hello" } }; - yield return new object[] { 5 }; - yield return new object[] { "sample input" }; - - SampleStruct test; - test.x = 10; - yield return new object[] { test }; - - yield return new object[] { new Task(() => Console.WriteLine("Test task")) }; - } - } - [Fact] public async Task InvokeAction_DoesNotInvokeExceptionFilter_WhenActionDoesNotThrow() { @@ -1241,28 +1225,44 @@ namespace Microsoft.AspNet.Mvc } [Theory] - [InlineData(typeof(void), typeof(NoContentResult))] - [InlineData(typeof(string), typeof(ObjectContentResult))] - public void CreateActionResult_Types_ReturnsAppropriateResults(Type type, Type returnType) + [InlineData(typeof(void))] + [InlineData(typeof(Task))] + public void CreateActionResult_Types_ReturnsNoContentResultForTaskAndVoidReturnTypes(Type type) { // Arrange & Act var result = ReflectedActionInvoker.CreateActionResult(type, null).GetType(); // Assert - Assert.Equal(returnType, result); + Assert.Equal(typeof(NoContentResult), (result)); + } + + public static IEnumerable CreateActionResult_ReturnsObjectContentResultData + { + get + { + var anonymousObject = new { x1 = 10, y1 = "Hello" }; + yield return new object[] { anonymousObject.GetType(), anonymousObject, }; + yield return new object[] { typeof(int), 5 }; + yield return new object[] { typeof(string), "sample input" }; + + SampleStruct test; + test.x = 10; + yield return new object[] { test.GetType(), test }; + yield return new object[] { typeof(Task), 5 }; + yield return new object[] { typeof(Task), "Hello world" }; + } } [Theory] - [MemberData("CreateActionResultData")] - public void CreateActionResult_ReturnsObjectContentResult(object input) + [MemberData("CreateActionResult_ReturnsObjectContentResultData")] + public void CreateActionResult_ReturnsObjectContentResult(Type type, object input) { // Arrange & Act - var actualResult = ReflectedActionInvoker.CreateActionResult(input.GetType(), input) - as ObjectContentResult; - + var actualResult = ReflectedActionInvoker.CreateActionResult(type, input); + // Assert - Assert.NotNull(actualResult); - Assert.Equal(input, actualResult.Value); + var contentResult = Assert.IsType(actualResult); + Assert.Same(input, contentResult.Value); } private ReflectedActionInvoker CreateInvoker(IFilter filter, bool actionThrows = false) diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/BasicTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/BasicTests.cs index 768a1152d8..026bed8425 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/BasicTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/BasicTests.cs @@ -90,6 +90,22 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal(0, result.Body.Length); } + [Fact] + public async Task ReturningTaskFromAction_ProducesNoContentResult() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.Handler; + + // Act + var result = await client.GetAsync("http://localhost/Home/ActionReturningTask"); + + // Assert + Assert.Equal(204, result.StatusCode); + var body = await result.ReadBodyAsStringAsync(); + Assert.Equal("Hello world", body); + } + [Fact] public async Task ActionDescriptors_CreatedOncePerRequest() { diff --git a/test/WebSites/BasicWebSite/Controllers/HomeController.cs b/test/WebSites/BasicWebSite/Controllers/HomeController.cs index 7d095d370d..4f86d03dbb 100644 --- a/test/WebSites/BasicWebSite/Controllers/HomeController.cs +++ b/test/WebSites/BasicWebSite/Controllers/HomeController.cs @@ -1,4 +1,8 @@ -using Microsoft.AspNet.Mvc; +// 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.Tasks; +using Microsoft.AspNet.Mvc; namespace BasicWebSite.Controllers { @@ -7,7 +11,7 @@ namespace BasicWebSite.Controllers public IActionResult Index() { return View(); - } + } public IActionResult PlainView() { @@ -18,5 +22,10 @@ namespace BasicWebSite.Controllers { return new HttpStatusCodeResult(204); } + + public async Task ActionReturningTask() + { + await Context.Response.WriteAsync("Hello world"); + } } } \ No newline at end of file