Issue #242 - Introducing ObjectContentResult

This commit is contained in:
sornaks 2014-05-28 10:26:12 -07:00
parent 0467fd906b
commit dffc58dedc
12 changed files with 250 additions and 95 deletions

View File

@ -1,63 +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;
namespace Microsoft.AspNet.Mvc
{
public class ActionResultFactory : IActionResultFactory
{
private readonly IActionResultHelper _result;
public ActionResultFactory(IActionResultHelper result)
{
_result = result;
}
public IActionResult CreateActionResult(Type declaredReturnType, object actionReturnValue, ActionContext actionContext)
{
// optimize common path
var actionResult = actionReturnValue as IActionResult;
if (actionResult != null)
{
return actionResult;
}
if (declaredReturnType == null)
{
throw new InvalidOperationException("Declared type must be passed");
}
if (typeof(IActionResult).IsAssignableFrom(declaredReturnType) && actionReturnValue == null)
{
throw new InvalidOperationException("Cannot return null from an action method declaring IActionResult or HttpResponseMessage");
}
if (declaredReturnType.IsGenericParameter)
{
// This can happen if somebody declares an action method as:
// public T Get<T>() { }
throw new InvalidOperationException("HttpActionDescriptor_NoConverterForGenericParamterTypeExists");
}
if (declaredReturnType == typeof(void) || actionReturnValue == null)
{
return new NoContentResult();
}
var actionReturnString = actionReturnValue as string;
if (actionReturnString != null)
{
return new ContentResult
{
ContentType = "text/plain",
Content = actionReturnString,
};
}
return _result.Json(actionReturnValue);
}
}
}

View File

@ -0,0 +1,37 @@
// 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;
namespace Microsoft.AspNet.Mvc
{
public class ObjectContentResult : ActionResult
{
public object Value { get; set; }
public ObjectContentResult(object value)
{
Value = value;
}
public override async Task ExecuteResultAsync(ActionContext context)
{
ActionResult result;
var actionReturnString = Value as string;
if (actionReturnString != null)
{
result = new ContentResult
{
ContentType = "text/plain",
Content = actionReturnString,
};
}
else
{
result = new JsonResult(Value);
}
await result.ExecuteResultAsync(context);
}
}
}

View File

@ -1,12 +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;
namespace Microsoft.AspNet.Mvc
{
public interface IActionResultFactory
{
IActionResult CreateActionResult(Type declaredReturnType, object actionReturnValue, ActionContext actionContext);
}
}

View File

@ -31,13 +31,13 @@
<Compile Include="ActionMethodSelectorAttribute.cs" />
<Compile Include="ActionNameAttribute.cs" />
<Compile Include="ActionResult.cs" />
<Compile Include="ActionResultFactory.cs" />
<Compile Include="ActionResultHelper.cs" />
<Compile Include="ActionResults\ContentResult.cs" />
<Compile Include="ActionResults\EmptyResult.cs" />
<Compile Include="ActionResults\HttpStatusCodeResult.cs" />
<Compile Include="ActionResults\JsonResult.cs" />
<Compile Include="ActionResults\NoContentResult.cs" />
<Compile Include="ActionResults\ObjectContentResult.cs" />
<Compile Include="ActionResults\RedirectResult.cs" />
<Compile Include="ActionResults\RedirectToActionResult.cs" />
<Compile Include="ActionResults\RedirectToRouteResult.cs" />
@ -126,7 +126,6 @@
<Compile Include="IActionInvokerFactory.cs" />
<Compile Include="IActionInvokerProvider.cs" />
<Compile Include="IActionResult.cs" />
<Compile Include="IActionResultFactory.cs" />
<Compile Include="IActionResultHelper.cs" />
<Compile Include="IActionSelector.cs" />
<Compile Include="IControllerAssemblyProvider.cs" />

View File

@ -1002,6 +1002,22 @@ namespace Microsoft.AspNet.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("UnobtrusiveJavascript_ValidationTypeMustBeUnique"), p0);
}
/// </summary>
/// Cannot return null from an action method with a return type of '{0}'.
/// </summary>
internal static string ActionResult_ActionReturnValueCannotBeNull
{
get { return GetString("ActionResult_ActionReturnValueCannotBeNull"); }
}
/// <summary>
/// Cannot return null from an action method with a return type of '{0}'.
/// </summary>
internal static string FormatActionResult_ActionReturnValueCannotBeNull(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ActionResult_ActionReturnValueCannotBeNull"), p0);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -17,7 +17,6 @@ namespace Microsoft.AspNet.Mvc
{
private readonly ActionContext _actionContext;
private readonly ReflectedActionDescriptor _descriptor;
private readonly IActionResultFactory _actionResultFactory;
private readonly IControllerFactory _controllerFactory;
private readonly IActionBindingContextProvider _bindingProvider;
private readonly INestedProviderManager<FilterProviderContext> _filterProvider;
@ -37,14 +36,12 @@ namespace Microsoft.AspNet.Mvc
public ReflectedActionInvoker([NotNull] ActionContext actionContext,
[NotNull] ReflectedActionDescriptor descriptor,
[NotNull] IActionResultFactory actionResultFactory,
[NotNull] IControllerFactory controllerFactory,
[NotNull] IActionBindingContextProvider bindingContextProvider,
[NotNull] INestedProviderManager<FilterProviderContext> filterProvider)
{
_actionContext = actionContext;
_descriptor = descriptor;
_actionResultFactory = actionResultFactory;
_controllerFactory = controllerFactory;
_bindingProvider = bindingContextProvider;
_filterProvider = filterProvider;
@ -99,6 +96,31 @@ namespace Microsoft.AspNet.Mvc
}
}
// Marking as internal for Unit Testing purposes.
internal static IActionResult CreateActionResult([NotNull] Type declaredReturnType, object actionReturnValue)
{
// 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))
{
return new NoContentResult();
}
return new ObjectContentResult(actionReturnValue);
}
private IFilter[] GetFilters()
{
var filterProviderContext = new FilterProviderContext(
@ -372,10 +394,9 @@ namespace Microsoft.AspNet.Mvc
_actionExecutingContext.ActionArguments);
var underlyingReturnType = TypeHelper.GetTaskInnerTypeOrNull(actionMethodInfo.ReturnType) ?? actionMethodInfo.ReturnType;
var actionResult = _actionResultFactory.CreateActionResult(
var actionResult = CreateActionResult(
underlyingReturnType,
actionReturnValue,
_actionContext);
actionReturnValue);
return actionResult;
}

View File

@ -8,19 +8,16 @@ namespace Microsoft.AspNet.Mvc
{
public class ReflectedActionInvokerProvider : IActionInvokerProvider
{
private readonly IActionResultFactory _actionResultFactory;
private readonly IServiceProvider _serviceProvider;
private readonly IControllerFactory _controllerFactory;
private readonly IActionBindingContextProvider _bindingProvider;
private readonly INestedProviderManager<FilterProviderContext> _filterProvider;
public ReflectedActionInvokerProvider(IActionResultFactory actionResultFactory,
IControllerFactory controllerFactory,
public ReflectedActionInvokerProvider(IControllerFactory controllerFactory,
IActionBindingContextProvider bindingProvider,
INestedProviderManager<FilterProviderContext> filterProvider,
IServiceProvider serviceProvider)
{
_actionResultFactory = actionResultFactory;
_controllerFactory = controllerFactory;
_bindingProvider = bindingProvider;
_filterProvider = filterProvider;
@ -41,7 +38,6 @@ namespace Microsoft.AspNet.Mvc
context.Result = new ReflectedActionInvoker(
context.ActionContext,
actionDescriptor,
_actionResultFactory,
_controllerFactory,
_bindingProvider,
_filterProvider);

View File

@ -303,4 +303,7 @@
<data name="UnobtrusiveJavascript_ValidationTypeMustBeUnique" xml:space="preserve">
<value>Validation type names in unobtrusive client validation rules must be unique. The following validation type was seen more than once: {0}</value>
</data>
<data name="ActionResult_ActionReturnValueCannotBeNull" xml:space="preserve">
<value>Cannot return null from an action method with a return type of '{0}'.</value>
</data>
</root>

View File

@ -30,7 +30,6 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Transient<IActionSelector, DefaultActionSelector>();
yield return describe.Transient<IActionInvokerFactory, ActionInvokerFactory>();
yield return describe.Transient<IActionResultHelper, ActionResultHelper>();
yield return describe.Transient<IActionResultFactory, ActionResultFactory>();
yield return describe.Transient<IParameterDescriptorFactory, DefaultParameterDescriptorFactory>();
yield return describe.Transient<IControllerAssemblyProvider, DefaultControllerAssemblyProvider>();
yield return describe.Transient<IActionDiscoveryConventions, DefaultActionDiscoveryConventions>();

View File

@ -0,0 +1,92 @@
// 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.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Internal;
using Microsoft.AspNet.Routing;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults
{
public class ObjectContentResultTests
{
[Fact]
public void ObjectContentResult_Create_CallsContentResult_InitializesValue()
{
// Arrange
var input = "testInput";
var actionContext = CreateMockActionContext();
// Act
var result = new ObjectContentResult(input);
// Assert
Assert.Equal(input, result.Value);
}
[Fact]
public async Task ObjectContentResult_Execute_CallsContentResult_SetsContent()
{
// Arrange
var expectedContentType = "text/plain";
var input = "testInput";
var httpResponse = new Mock<HttpResponse>();
httpResponse.SetupSet(r => r.ContentType = expectedContentType).Verifiable();
httpResponse.Object.Body = new MemoryStream();
var actionContext = CreateMockActionContext(httpResponse.Object);
// Act
var result = new ObjectContentResult(input);
await result.ExecuteResultAsync(actionContext);
// Assert
httpResponse.VerifySet(r => r.ContentType = expectedContentType);
// The following verifies the correct Content was written to Body
httpResponse.Verify(o => o.WriteAsync(input), Times.Exactly(1));
}
[Fact]
public async Task ObjectContentResult_Execute_CallsJsonResult_SetsContent()
{
// Arrange
var expectedContentType = "application/json";
var nonStringValue = new { x1 = 10, y1 = "Hello" };
var httpResponse = Mock.Of<HttpResponse>();
httpResponse.Body = new MemoryStream();
var actionContext = CreateMockActionContext(httpResponse);
var tempStream = new MemoryStream();
using (var writer = new StreamWriter(tempStream, UTF8EncodingWithoutBOM.Encoding, 1024, leaveOpen: true))
{
var formatter = new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), false);
formatter.WriteObject(writer, nonStringValue);
}
// Act
var result = new ObjectContentResult(nonStringValue);
await result.ExecuteResultAsync(actionContext);
// Assert
Assert.Equal(expectedContentType, httpResponse.ContentType);
Assert.Equal(tempStream.ToArray(), ((MemoryStream)actionContext.HttpContext.Response.Body).ToArray());
}
private static ActionContext CreateMockActionContext(HttpResponse response = null)
{
var httpContext = new Mock<HttpContext>();
if (response != null)
{
httpContext.Setup(o => o.Response).Returns(response);
}
return new ActionContext(httpContext.Object, Mock.Of<IRouter>(), new Dictionary<string, object>(),
new ActionDescriptor());
}
}
}

View File

@ -23,6 +23,7 @@
<ItemGroup>
<Compile Include="ActionAttributeTests.cs" />
<Compile Include="ActionExecutorTests.cs" />
<Compile Include="ActionResults\ObjectContentResultTests.cs" />
<Compile Include="ActionResults\RedirectToActionResultTest.cs" />
<Compile Include="ActionResults\RedirectToRouteResultTest.cs" />
<Compile Include="ActionResults\RedirectResultTest.cs" />

View File

@ -21,6 +21,27 @@ namespace Microsoft.AspNet.Mvc
private readonly JsonResult _result = new JsonResult(new { message = "Hello, world!" });
private struct SampleStruct
{
public int x;
}
public static IEnumerable<object[]> 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()
{
@ -1190,6 +1211,57 @@ namespace Microsoft.AspNet.Mvc
resultFilter2.Verify(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()), Times.Once());
}
[Fact]
public void CreateActionResult_ReturnsSameActionResult()
{
// Arrange
var mockActionResult = new Mock<IActionResult>();
// Assert
var result = ReflectedActionInvoker.CreateActionResult(
mockActionResult.Object.GetType(), mockActionResult.Object);
// Act
Assert.Same(mockActionResult.Object, result);
}
[Fact]
[ReplaceCulture]
public void CreateActionResult_NullActionResultReturnValueThrows()
{
// Arrange, Act & Assert
ExceptionAssert.Throws<InvalidOperationException>(
() => ReflectedActionInvoker.CreateActionResult(typeof(IActionResult), null),
"Cannot return null from an action method with a return type of '"
+ typeof(IActionResult)
+ "'.");
}
[Theory]
[InlineData(typeof(void), typeof(NoContentResult))]
[InlineData(typeof(string), typeof(ObjectContentResult))]
public void CreateActionResult_Types_ReturnsAppropriateResults(Type type, Type returnType)
{
// Arrange & Act
var result = ReflectedActionInvoker.CreateActionResult(type, null).GetType();
// Assert
Assert.Equal(returnType, result);
}
[Theory]
[MemberData("CreateActionResultData")]
public void CreateActionResult_ReturnsObjectContentResult(object input)
{
// Arrange & Act
var actualResult = ReflectedActionInvoker.CreateActionResult(input.GetType(), input)
as ObjectContentResult;
// Assert
Assert.NotNull(actualResult);
Assert.Equal(input, actualResult.Value);
}
private ReflectedActionInvoker CreateInvoker(IFilter filter, bool actionThrows = false)
{
return CreateInvoker(new[] { filter }, actionThrows);
@ -1223,11 +1295,6 @@ namespace Microsoft.AspNet.Mvc
routeValues: null,
actionDescriptor: actionDescriptor);
var actionResultFactory = new Mock<IActionResultFactory>(MockBehavior.Strict);
actionResultFactory
.Setup(arf => arf.CreateActionResult(It.IsAny<Type>(), It.IsAny<object>(), It.IsAny<ActionContext>()))
.Returns<Type, object, ActionContext>((t, o, ac) => (IActionResult)o);
var controllerFactory = new Mock<IControllerFactory>(MockBehavior.Strict);
controllerFactory.Setup(c => c.CreateController(It.IsAny<ActionContext>())).Returns(this);
@ -1245,7 +1312,6 @@ namespace Microsoft.AspNet.Mvc
var invoker = new ReflectedActionInvoker(
actionContext,
actionDescriptor,
actionResultFactory.Object,
controllerFactory.Object,
actionBindingContextProvider.Object,
filterProvider.Object);