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

Commit 151cf44

Browse files
committed
Introduce ActionResult<T>
1 parent bac68ba commit 151cf44

File tree

7 files changed

+393
-14
lines changed

7 files changed

+393
-14
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) .NET Foundation. 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 Microsoft.AspNetCore.Mvc.Infrastructure;
6+
7+
namespace Microsoft.AspNetCore.Mvc
8+
{
9+
/// <summary>
10+
/// A type that wraps either an <typeparamref name="TValue"/> instance or an <see cref="ActionResult"/>.
11+
/// </summary>
12+
/// <typeparam name="TValue">The type of the result.</typeparam>
13+
public class ActionResult<TValue> : IConvertToActionResult
14+
{
15+
/// <summary>
16+
/// Initializes a new instance of <see cref="ActionResult{TValue}"/> using the specified <paramref name="value"/>.
17+
/// </summary>
18+
/// <param name="value">The value.</param>
19+
public ActionResult(TValue value)
20+
{
21+
Value = value;
22+
}
23+
24+
/// <summary>
25+
/// Intializes a new instance of <see cref="ActionResult{TValue}"/> using the specified <see cref="ActionResult"/>.
26+
/// </summary>
27+
/// <param name="result">The <see cref="ActionResult"/>.</param>
28+
public ActionResult(ActionResult result)
29+
{
30+
Result = result ?? throw new ArgumentNullException(nameof(result));
31+
}
32+
33+
/// <summary>
34+
/// Gets the <see cref="ActionResult"/>.
35+
/// </summary>
36+
public ActionResult Result { get; }
37+
38+
/// <summary>
39+
/// Gets the value.
40+
/// </summary>
41+
public TValue Value { get; }
42+
43+
public static implicit operator ActionResult<TValue>(TValue value)
44+
{
45+
return new ActionResult<TValue>(value);
46+
}
47+
48+
public static implicit operator ActionResult<TValue>(ActionResult result)
49+
{
50+
return new ActionResult<TValue>(result);
51+
}
52+
53+
IActionResult IConvertToActionResult.Convert()
54+
{
55+
return Result ?? new ObjectResult(Value)
56+
{
57+
DeclaredType = typeof(TValue),
58+
};
59+
}
60+
}
61+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.AspNetCore.Mvc.Infrastructure
5+
{
6+
/// <summary>
7+
/// Defines the contract to convert a type to an <see cref="IActionResult"/> during action invocation.
8+
/// </summary>
9+
public interface IConvertToActionResult
10+
{
11+
/// <summary>
12+
/// Converts the current instance to an instance of <see cref="IActionResult"/>.
13+
/// </summary>
14+
/// <returns>The converted <see cref="IActionResult"/>.</returns>
15+
IActionResult Convert();
16+
}
17+
}

src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.AspNetCore.Mvc.Abstractions;
1010
using Microsoft.AspNetCore.Mvc.Core;
1111
using Microsoft.AspNetCore.Mvc.Filters;
12+
using Microsoft.AspNetCore.Mvc.Infrastructure;
1213
using Microsoft.Extensions.Internal;
1314
using Microsoft.Extensions.Logging;
1415

@@ -311,6 +312,7 @@ private async Task InvokeActionMethodAsync()
311312

312313
var diagnosticSource = _diagnosticSource;
313314
var logger = _logger;
315+
var returnType = executor.MethodReturnType;
314316

315317
IActionResult result = null;
316318
try
@@ -321,7 +323,6 @@ private async Task InvokeActionMethodAsync()
321323
controller);
322324
logger.ActionMethodExecuting(controllerContext, orderedArguments);
323325

324-
var returnType = executor.MethodReturnType;
325326
if (returnType == typeof(void))
326327
{
327328
// Sync method returning void
@@ -346,6 +347,30 @@ private async Task InvokeActionMethodAsync()
346347
Resources.FormatActionResult_ActionReturnValueCannotBeNull(typeof(IActionResult)));
347348
}
348349
}
350+
else if (IsConvertibleToActionResult(executor))
351+
{
352+
IConvertToActionResult convertToActionResult;
353+
if (executor.IsMethodAsync)
354+
{
355+
// Async method returning awaitable-of-ActionResult<T> (e.g., Task<ActionResult<Person>>)
356+
// We have to use ExecuteAsync because we don't know the awaitable's type at compile time.
357+
convertToActionResult = (IConvertToActionResult)await executor.ExecuteAsync(controller, orderedArguments);
358+
}
359+
else
360+
{
361+
// Sync method returning ActionResult<T>
362+
convertToActionResult = (IConvertToActionResult)executor.Execute(controller, orderedArguments);
363+
}
364+
365+
result = convertToActionResult.Convert();
366+
367+
if (result == null)
368+
{
369+
throw new InvalidOperationException(
370+
Resources.FormatActionResult_ActionReturnValueCannotBeNull(typeof(IConvertToActionResult)));
371+
}
372+
373+
}
349374
else if (IsResultIActionResult(executor))
350375
{
351376
if (executor.IsMethodAsync)
@@ -370,10 +395,8 @@ private async Task InvokeActionMethodAsync()
370395
{
371396
// Sync method returning arbitrary object
372397
var resultAsObject = executor.Execute(controller, orderedArguments);
373-
result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject)
374-
{
375-
DeclaredType = returnType,
376-
};
398+
ConvertToActionResult(resultAsObject);
399+
377400
}
378401
else if (executor.AsyncResultType == typeof(void))
379402
{
@@ -385,10 +408,7 @@ private async Task InvokeActionMethodAsync()
385408
{
386409
// Async method returning awaitable-of-nonvoid
387410
var resultAsObject = await executor.ExecuteAsync(controller, orderedArguments);
388-
result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject)
389-
{
390-
DeclaredType = executor.AsyncResultType,
391-
};
411+
ConvertToActionResult(resultAsObject);
392412
}
393413

394414
_result = result;
@@ -402,6 +422,25 @@ private async Task InvokeActionMethodAsync()
402422
controllerContext,
403423
result);
404424
}
425+
426+
void ConvertToActionResult(object resultAsObject)
427+
{
428+
if (resultAsObject is IActionResult actionResult)
429+
{
430+
result = actionResult;
431+
}
432+
else if (resultAsObject is IConvertToActionResult convertToActionResult)
433+
{
434+
result = convertToActionResult.Convert();
435+
}
436+
else
437+
{
438+
result = new ObjectResult(resultAsObject)
439+
{
440+
DeclaredType = returnType,
441+
};
442+
}
443+
}
405444
}
406445

407446
private static bool IsResultIActionResult(ObjectMethodExecutor executor)
@@ -410,6 +449,12 @@ private static bool IsResultIActionResult(ObjectMethodExecutor executor)
410449
return typeof(IActionResult).IsAssignableFrom(resultType);
411450
}
412451

452+
private bool IsConvertibleToActionResult(ObjectMethodExecutor executor)
453+
{
454+
var resultType = executor.AsyncResultType ?? executor.MethodReturnType;
455+
return typeof(IConvertToActionResult).IsAssignableFrom(resultType);
456+
}
457+
413458
/// <remarks><see cref="ResourceInvoker.InvokeFilterPipelineAsync"/> for details on what the
414459
/// variables in this method represent.</remarks>
415460
protected override async Task InvokeInnerFilterAsync()
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (c) .NET Foundation. 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 Microsoft.AspNetCore.Mvc.Infrastructure;
5+
using Xunit;
6+
7+
namespace Microsoft.AspNetCore.Mvc
8+
{
9+
public class ActionResultOfTTest
10+
{
11+
[Fact]
12+
public void Convert_ReturnsResultIfSet()
13+
{
14+
// Arrange
15+
var expected = new OkResult();
16+
var actionResultOfT = new ActionResult<string>(expected);
17+
var convertToActionResult = (IConvertToActionResult)actionResultOfT;
18+
19+
// Act
20+
var result = convertToActionResult.Convert();
21+
22+
// Assert
23+
Assert.Same(expected, result);
24+
}
25+
26+
[Fact]
27+
public void Convert_ReturnsObjectResultWrappingValue()
28+
{
29+
// Arrange
30+
var value = new BaseItem();
31+
var actionResultOfT = new ActionResult<BaseItem>(value);
32+
var convertToActionResult = (IConvertToActionResult)actionResultOfT;
33+
34+
// Act
35+
var result = convertToActionResult.Convert();
36+
37+
// Assert
38+
var objectResult = Assert.IsType<ObjectResult>(result);
39+
Assert.Same(value, objectResult.Value);
40+
Assert.Equal(typeof(BaseItem), objectResult.DeclaredType);
41+
}
42+
43+
[Fact]
44+
public void Convert_InfersDeclaredTypeFromActionResultTypeParameter()
45+
{
46+
// Arrange
47+
var value = new DeriviedItem();
48+
var actionResultOfT = new ActionResult<BaseItem>(value);
49+
var convertToActionResult = (IConvertToActionResult)actionResultOfT;
50+
51+
// Act
52+
var result = convertToActionResult.Convert();
53+
54+
// Assert
55+
var objectResult = Assert.IsType<ObjectResult>(result);
56+
Assert.Same(value, objectResult.Value);
57+
Assert.Equal(typeof(BaseItem), objectResult.DeclaredType);
58+
}
59+
60+
private class BaseItem
61+
{
62+
}
63+
64+
private class DeriviedItem : BaseItem
65+
{
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)