AcceptedResult(), AcceptedAtActionResult(), AcceptedAtRouteResult() support

Addresses #4937
This commit is contained in:
Jaspreet Bagga 2016-10-18 09:48:26 -07:00 committed by GitHub
parent e7c992ff06
commit c2fee4d74d
9 changed files with 1329 additions and 3 deletions

View File

@ -0,0 +1,94 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc
{
/// <summary>
/// An <see cref="ActionResult"/> that returns a Accepted (202) response with a Location header.
/// </summary>
public class AcceptedAtActionResult : ObjectResult
{
/// <summary>
/// Initializes a new instance of the <see cref="AcceptedAtActionResult"/> with the values
/// provided.
/// </summary>
/// <param name="actionName">The name of the action to use for generating the URL.</param>
/// <param name="controllerName">The name of the controller to use for generating the URL.</param>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <param name="value">The value to format in the entity body.</param>
public AcceptedAtActionResult(
string actionName,
string controllerName,
object routeValues,
object value)
: base(value)
{
ActionName = actionName;
ControllerName = controllerName;
RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues);
StatusCode = StatusCodes.Status202Accepted;
}
/// <summary>
/// Gets or sets the <see cref="IUrlHelper" /> used to generate URLs.
/// </summary>
public IUrlHelper UrlHelper { get; set; }
/// <summary>
/// Gets or sets the name of the action to use for generating the URL.
/// </summary>
public string ActionName { get; set; }
/// <summary>
/// Gets or sets the name of the controller to use for generating the URL.
/// </summary>
public string ControllerName { get; set; }
/// <summary>
/// Gets or sets the route data to use for generating the URL.
/// </summary>
public RouteValueDictionary RouteValues { get; set; }
/// <inheritdoc />
public override void OnFormatting(ActionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
base.OnFormatting(context);
var request = context.HttpContext.Request;
var urlHelper = UrlHelper;
if (urlHelper == null)
{
var services = context.HttpContext.RequestServices;
urlHelper = services.GetRequiredService<IUrlHelperFactory>().GetUrlHelper(context);
}
var url = urlHelper.Action(
ActionName,
ControllerName,
RouteValues,
request.Scheme,
request.Host.ToUriComponent());
if (string.IsNullOrEmpty(url))
{
throw new InvalidOperationException(Resources.NoRoutesMatched);
}
context.HttpContext.Response.Headers[HeaderNames.Location] = url;
}
}
}

View File

@ -0,0 +1,90 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc
{
/// <summary>
/// An <see cref="ActionResult"/> that returns a Accepted (202) response with a Location header.
/// </summary>
public class AcceptedAtRouteResult : ObjectResult
{
/// <summary>
/// Initializes a new instance of the <see cref="AcceptedAtRouteResult"/> class with the values
/// provided.
/// </summary>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <param name="value">The value to format in the entity body.</param>
public AcceptedAtRouteResult(object routeValues, object value)
: this(routeName: null, routeValues: routeValues, value: value)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AcceptedAtRouteResult"/> class with the values
/// provided.
/// </summary>
/// <param name="routeName">The name of the route to use for generating the URL.</param>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <param name="value">The value to format in the entity body.</param>
public AcceptedAtRouteResult(
string routeName,
object routeValues,
object value)
: base(value)
{
RouteName = routeName;
RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues);
StatusCode = StatusCodes.Status202Accepted;
}
/// <summary>
/// Gets or sets the <see cref="IUrlHelper" /> used to generate URLs.
/// </summary>
public IUrlHelper UrlHelper { get; set; }
/// <summary>
/// Gets or sets the name of the route to use for generating the URL.
/// </summary>
public string RouteName { get; set; }
/// <summary>
/// Gets or sets the route data to use for generating the URL.
/// </summary>
public RouteValueDictionary RouteValues { get; set; }
/// <inheritdoc />
public override void OnFormatting(ActionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
base.OnFormatting(context);
var urlHelper = UrlHelper;
if (urlHelper == null)
{
var services = context.HttpContext.RequestServices;
urlHelper = services.GetRequiredService<IUrlHelperFactory>().GetUrlHelper(context);
}
var url = urlHelper.Link(RouteName, RouteValues);
if (string.IsNullOrEmpty(url))
{
throw new InvalidOperationException(Resources.NoRoutesMatched);
}
context.HttpContext.Response.Headers[HeaderNames.Location] = url;
}
}
}

View File

@ -0,0 +1,98 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc
{
/// <summary>
/// An <see cref="ActionResult"/> that returns an Accepted (202) response with a Location header.
/// </summary>
public class AcceptedResult : ObjectResult
{
private string _location;
/// <summary>
/// Initializes a new instance of the <see cref="AcceptedResult"/> class with the values
/// provided.
/// </summary>
public AcceptedResult()
: base(value: null)
{
StatusCode = StatusCodes.Status202Accepted;
}
/// <summary>
/// Initializes a new instance of the <see cref="AcceptedResult"/> class with the values
/// provided.
/// </summary>
/// <param name="location">The location at which the status of requested content can be monitored.</param>
/// <param name="value">The value to format in the entity body.</param>
public AcceptedResult(string location, object value)
: base(value)
{
Location = location;
StatusCode = StatusCodes.Status202Accepted;
}
/// <summary>
/// Initializes a new instance of the <see cref="AcceptedResult"/> class with the values
/// provided.
/// </summary>
/// <param name="locationUri">The location at which the status of requested content can be monitored
/// It is an optional paramater and may be null</param>
/// <param name="value">The value to format in the entity body.</param>
public AcceptedResult(Uri locationUri, object value)
: base(value)
{
if (locationUri == null)
{
throw new ArgumentNullException(nameof(locationUri));
}
if (locationUri.IsAbsoluteUri)
{
Location = locationUri.AbsoluteUri;
}
else
{
Location = locationUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
}
StatusCode = StatusCodes.Status202Accepted;
}
/// <summary>
/// Gets or sets the location at which the status of the requested content can be monitored.
/// </summary>
public string Location
{
get
{
return _location;
}
set
{
_location = value;
}
}
/// <inheritdoc />
public override void OnFormatting(ActionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
base.OnFormatting(context);
if (!string.IsNullOrEmpty(Location))
{
context.HttpContext.Response.Headers[HeaderNames.Location] = Location;
}
}
}
}

View File

@ -910,6 +910,223 @@ namespace Microsoft.AspNetCore.Mvc
return new CreatedAtRouteResult(routeName, routeValues, value);
}
/// <summary>
/// Creates a <see cref="AcceptedResult"/> object that produces an Accepted (202) response.
/// </summary>
/// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
[NonAction]
public virtual AcceptedResult Accepted()
{
return new AcceptedResult();
}
/// <summary>
/// Creates a <see cref="AcceptedResult"/> object that produces an Accepted (202) response.
/// </summary>
/// <param name="value">The optional content value to format in the entity body; may be null.</param>
/// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
[NonAction]
public virtual AcceptedResult Accepted(object value)
{
return new AcceptedResult(location: null, value: value);
}
/// <summary>
/// Creates a <see cref="AcceptedResult"/> object that produces an Accepted (202) response.
/// </summary>
/// <param name="uri">The optional URI with the location at which the status of requested content can be monitored.
/// May be null.</param>
/// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
[NonAction]
public virtual AcceptedResult Accepted(Uri uri)
{
if (uri == null)
{
throw new ArgumentNullException(nameof(uri));
}
return new AcceptedResult(locationUri: uri, value: null);
}
/// <summary>
/// Creates a <see cref="AcceptedResult"/> object that produces an Accepted (202) response.
/// </summary>
/// <param name="uri">The optional URI with the location at which the status of requested content can be monitored.
/// May be null.</param>
/// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
[NonAction]
public virtual AcceptedResult Accepted(string uri)
{
return new AcceptedResult(location: uri, value: null);
}
/// <summary>
/// Creates a <see cref="AcceptedResult"/> object that produces an Accepted (202) response.
/// </summary>
/// <param name="uri">The URI with the location at which the status of requested content can be monitored.</param>
/// <param name="value">The optional content value to format in the entity body; may be null.</param>
/// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
[NonAction]
public virtual AcceptedResult Accepted(string uri, object value)
{
return new AcceptedResult(uri, value);
}
/// <summary>
/// Creates a <see cref="AcceptedResult"/> object that produces an Accepted (202) response.
/// </summary>
/// <param name="uri">The URI with the location at which the status of requested content can be monitored.</param>
/// <param name="value">The optional content value to format in the entity body; may be null.</param>
/// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
[NonAction]
public virtual AcceptedResult Accepted(Uri uri, object value)
{
if (uri == null)
{
throw new ArgumentNullException(nameof(uri));
}
return new AcceptedResult(locationUri: uri, value: value);
}
/// <summary>
/// Creates a <see cref="AcceptedAtActionResult"/> object that produces an Accepted (202) response.
/// </summary>
/// <param name="actionName">The name of the action to use for generating the URL.</param>
/// <returns>The created <see cref="AcceptedAtActionResult"/> for the response.</returns>
[NonAction]
public virtual AcceptedAtActionResult AcceptedAtAction(string actionName)
{
return AcceptedAtAction(actionName, routeValues: null, value: null);
}
/// <summary>
/// Creates a <see cref="AcceptedAtActionResult"/> object that produces an Accepted (202) response.
/// </summary>
/// <param name="actionName">The name of the action to use for generating the URL.</param>
/// <param name="controllerName">The name of the controller to use for generating the URL.</param>
/// <returns>The created <see cref="AcceptedAtActionResult"/> for the response.</returns>
[NonAction]
public virtual AcceptedAtActionResult AcceptedAtAction(string actionName, string controllerName)
{
return AcceptedAtAction(actionName, controllerName, routeValues: null, value: null);
}
/// <summary>
/// Creates a <see cref="AcceptedAtActionResult"/> object that produces an Accepted (202) response.
/// </summary>
/// <param name="actionName">The name of the action to use for generating the URL.</param>
/// <param name="value">The optional content value to format in the entity body; may be null.</param>
/// <returns>The created <see cref="AcceptedAtActionResult"/> for the response.</returns>
[NonAction]
public virtual AcceptedAtActionResult AcceptedAtAction(string actionName, object value)
{
return AcceptedAtAction(actionName, routeValues: null, value: value);
}
/// <summary>
/// Creates a <see cref="AcceptedAtActionResult"/> object that produces an Accepted (202) response.
/// </summary>
/// <param name="actionName">The name of the action to use for generating the URL.</param>
/// <param name="controllerName">The name of the controller to use for generating the URL.</param>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <returns>The created <see cref="AcceptedAtActionResult"/> for the response.</returns>
[NonAction]
public virtual AcceptedAtActionResult AcceptedAtAction(string actionName, string controllerName, object routeValues)
{
return AcceptedAtAction(actionName, controllerName, routeValues, value: null);
}
/// <summary>
/// Creates a <see cref="AcceptedAtActionResult"/> object that produces an Accepted (202) response.
/// </summary>
/// <param name="actionName">The name of the action to use for generating the URL.</param>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <param name="value">The optional content value to format in the entity body; may be null.</param>
/// <returns>The created <see cref="AcceptedAtActionResult"/> for the response.</returns>
[NonAction]
public virtual AcceptedAtActionResult AcceptedAtAction(string actionName, object routeValues, object value)
{
return AcceptedAtAction(actionName, controllerName: null, routeValues: routeValues, value: value);
}
/// <summary>
/// Creates a <see cref="AcceptedAtActionResult"/> object that produces an Accepted (202) response.
/// </summary>
/// <param name="actionName">The name of the action to use for generating the URL.</param>
/// <param name="controllerName">The name of the controller to use for generating the URL.</param>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <param name="value">The optional content value to format in the entity body; may be null.</param>
/// <returns>The created <see cref="AcceptedAtActionResult"/> for the response.</returns>
[NonAction]
public virtual AcceptedAtActionResult AcceptedAtAction(
string actionName,
string controllerName,
object routeValues,
object value)
{
return new AcceptedAtActionResult(actionName, controllerName, routeValues, value);
}
/// <summary>
/// Creates a <see cref="AcceptedAtRouteResult"/> object that produces an Accepted (202) response.
/// </summary>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <returns>The created <see cref="AcceptedAtRouteResult"/> for the response.</returns>
[NonAction]
public virtual AcceptedAtRouteResult AcceptedAtRoute(object routeValues)
{
return AcceptedAtRoute(routeName: null, routeValues: routeValues, value: null);
}
/// <summary>
/// Creates a <see cref="AcceptedAtRouteResult"/> object that produces an Accepted (202) response.
/// </summary>
/// <param name="routeName">The name of the route to use for generating the URL.</param>
/// <returns>The created <see cref="AcceptedAtRouteResult"/> for the response.</returns>
[NonAction]
public virtual AcceptedAtRouteResult AcceptedAtRoute(string routeName)
{
return AcceptedAtRoute(routeName, routeValues: null, value: null);
}
/// <summary>
/// Creates a <see cref="AcceptedAtRouteResult"/> object that produces an Accepted (202) response.
/// </summary>
/// <param name="routeName">The name of the route to use for generating the URL.</param>
///<param name="routeValues">The route data to use for generating the URL.</param>
/// <returns>The created <see cref="AcceptedAtRouteResult"/> for the response.</returns>
[NonAction]
public virtual AcceptedAtRouteResult AcceptedAtRoute(string routeName, object routeValues)
{
return AcceptedAtRoute(routeName, routeValues, value: null);
}
/// <summary>
/// Creates a <see cref="AcceptedAtRouteResult"/> object that produces an Accepted (202) response.
/// </summary>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <param name="value">The optional content value to format in the entity body; may be null.</param>
/// <returns>The created <see cref="AcceptedAtRouteResult"/> for the response.</returns>
[NonAction]
public virtual AcceptedAtRouteResult AcceptedAtRoute(object routeValues, object value)
{
return AcceptedAtRoute(routeName: null, routeValues: routeValues, value: value);
}
/// <summary>
/// Creates a <see cref="AcceptedAtRouteResult"/> object that produces an Accepted (202) response.
/// </summary>
/// <param name="routeName">The name of the route to use for generating the URL.</param>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <param name="value">The optional content value to format in the entity body; may be null.</param>
/// <returns>The created <see cref="AcceptedAtRouteResult"/> for the response.</returns>
[NonAction]
public virtual AcceptedAtRouteResult AcceptedAtRoute(string routeName, object routeValues, object value)
{
return new AcceptedAtRouteResult(routeName, routeValues, value);
}
/// <summary>
/// Creates a <see cref="ChallengeResult"/>.
/// </summary>

View File

@ -0,0 +1,188 @@
// Copyright (c) .NET Foundation. 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.Buffers;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Testing;
using Moq;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNetCore.Mvc
{
public class AcceptedAtActionResultTests
{
public static TheoryData<object> ValuesData
{
get
{
return new TheoryData<object>
{
null,
"Test string",
new object(),
};
}
}
[Theory]
[MemberData(nameof(ValuesData))]
public void Constructor_InitializesStatusCodeAndValue(object value)
{
// Arrange
var url = "testAction";
// Act
var result = new AcceptedAtActionResult(
actionName: url,
controllerName: null,
routeValues: null,
value: value);
// Assert
Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode);
Assert.Same(value, result.Value);
}
[Theory]
[MemberData(nameof(ValuesData))]
public async Task ExecuteResultAsync_SetsObjectValueOfFormatter(object value)
{
// Arrange
var url = "testAction";
var formatter = CreateMockFormatter();
var httpContext = GetHttpContext(formatter);
object actual = null;
formatter.Setup(f => f.WriteAsync(It.IsAny<OutputFormatterWriteContext>()))
.Callback((OutputFormatterWriteContext context) => actual = context.Object)
.Returns(Task.FromResult(0));
var actionContext = GetActionContext(httpContext);
var urlHelper = GetMockUrlHelper(url);
// Act
var result = new AcceptedAtActionResult(
actionName: url,
controllerName: null,
routeValues: null,
value: value);
result.UrlHelper = urlHelper;
await result.ExecuteResultAsync(actionContext);
// Assert
Assert.Same(value, actual);
}
[Fact]
public async Task ExecuteResultAsync_SetsStatusCodeAndLocationHeader()
{
// Arrange
var expectedUrl = "testAction";
var formatter = CreateMockFormatter();
var httpContext = GetHttpContext(formatter);
var actionContext = GetActionContext(httpContext);
var urlHelper = GetMockUrlHelper(expectedUrl);
// Act
var result = new AcceptedAtActionResult(
actionName: expectedUrl,
controllerName: null,
routeValues: null,
value: null);
result.UrlHelper = urlHelper;
await result.ExecuteResultAsync(actionContext);
// Assert
Assert.Equal(StatusCodes.Status202Accepted, httpContext.Response.StatusCode);
Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
}
[Fact]
public async Task ExecuteResultAsync_ThrowsIfActionUrlIsNull()
{
// Arrange
var formatter = CreateMockFormatter();
var httpContext = GetHttpContext(formatter);
var actionContext = GetActionContext(httpContext);
var urlHelper = GetMockUrlHelper(returnValue: null);
// Act
var result = new AcceptedAtActionResult(
actionName: null,
controllerName: null,
routeValues: null,
value: null);
result.UrlHelper = urlHelper;
// Assert
await ExceptionAssert.ThrowsAsync<InvalidOperationException>(() =>
result.ExecuteResultAsync(actionContext),
"No route matches the supplied values.");
}
private static ActionContext GetActionContext(HttpContext httpContext)
{
var routeData = new RouteData();
routeData.Routers.Add(Mock.Of<IRouter>());
return new ActionContext(
httpContext,
routeData,
new ActionDescriptor());
}
private static HttpContext GetHttpContext(Mock<IOutputFormatter> formatter)
{
var httpContext = new DefaultHttpContext();
httpContext.RequestServices = CreateServices(formatter);
return httpContext;
}
private static Mock<IOutputFormatter> CreateMockFormatter()
{
var formatter = new Mock<IOutputFormatter>
{
CallBase = true
};
formatter.Setup(f => f.CanWriteResult(It.IsAny<OutputFormatterWriteContext>())).Returns(true);
return formatter;
}
private static IServiceProvider CreateServices(Mock<IOutputFormatter> formatter)
{
var options = new TestOptionsManager<MvcOptions>();
options.Value.OutputFormatters.Add(formatter.Object);
var services = new ServiceCollection();
services.AddSingleton(new ObjectResultExecutor(
options,
new TestHttpResponseStreamWriterFactory(),
NullLoggerFactory.Instance));
return services.BuildServiceProvider();
}
private static IUrlHelper GetMockUrlHelper(string returnValue)
{
var urlHelper = new Mock<IUrlHelper>();
urlHelper.Setup(o => o.Action(It.IsAny<UrlActionContext>())).Returns(returnValue);
return urlHelper.Object;
}
}
}

View File

@ -0,0 +1,201 @@
// Copyright (c) .NET Foundation. 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.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Testing;
using Moq;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNetCore.Mvc
{
public class AcceptedAtRouteResultTests
{
public static TheoryData<object> ValuesData
{
get
{
return new TheoryData<object>
{
null,
"Test string",
new object(),
};
}
}
[Theory]
[MemberData(nameof(ValuesData))]
public void Constructor_InitializesStatusCodeAndValue(object value)
{
// Arrange & Act
var result = new AcceptedAtRouteResult(
routeName: null,
routeValues: null,
value: value);
// Assert
Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode);
Assert.Same(value, result.Value);
}
[Theory]
[MemberData(nameof(ValuesData))]
public async Task ExecuteResultAsync_SetsObjectValueOfFormatter(object value)
{
// Arrange
var url = "testAction";
var formatter = CreateMockFormatter();
var httpContext = GetHttpContext(formatter);
object actual = null;
formatter.Setup(f => f.WriteAsync(It.IsAny<OutputFormatterWriteContext>()))
.Callback((OutputFormatterWriteContext context) => actual = context.Object)
.Returns(Task.FromResult(0));
var actionContext = GetActionContext(httpContext);
var urlHelper = GetMockUrlHelper(url);
var routeValues = new RouteValueDictionary(new Dictionary<string, string>()
{
{ "test", "case" },
{ "sample", "route" }
});
// Act
var result = new AcceptedAtRouteResult(
routeName: "sample",
routeValues: routeValues,
value: value);
result.UrlHelper = urlHelper;
await result.ExecuteResultAsync(actionContext);
// Assert
Assert.Same(value, actual);
}
public static TheoryData<object> AcceptedAtRouteData
{
get
{
return new TheoryData<object>
{
null,
new Dictionary<string, string>()
{
{ "hello", "world" }
},
new RouteValueDictionary(
new Dictionary<string, string>()
{
{ "test", "case" },
{ "sample", "route" }
}),
};
}
}
[Theory]
[MemberData(nameof(AcceptedAtRouteData))]
public async Task ExecuteResultAsync_SetsStatusCodeAndLocationHeader(object values)
{
// Arrange
var expectedUrl = "testAction";
var formatter = CreateMockFormatter();
var httpContext = GetHttpContext(formatter);
var actionContext = GetActionContext(httpContext);
var urlHelper = GetMockUrlHelper(expectedUrl);
// Act
var result = new AcceptedAtRouteResult(routeValues: values, value: null);
result.UrlHelper = urlHelper;
await result.ExecuteResultAsync(actionContext);
// Assert
Assert.Equal(StatusCodes.Status202Accepted, httpContext.Response.StatusCode);
Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
}
[Fact]
public async Task ExecuteResultAsync_ThrowsIfRouteUrlIsNull()
{
// Arrange
var formatter = CreateMockFormatter();
var httpContext = GetHttpContext(formatter);
var actionContext = GetActionContext(httpContext);
var urlHelper = GetMockUrlHelper(returnValue: null);
// Act
var result = new AcceptedAtRouteResult(
routeName: null,
routeValues: new Dictionary<string, object>(),
value: null);
result.UrlHelper = urlHelper;
// Assert
await ExceptionAssert.ThrowsAsync<InvalidOperationException>(() =>
result.ExecuteResultAsync(actionContext),
"No route matches the supplied values.");
}
private static ActionContext GetActionContext(HttpContext httpContext)
{
var routeData = new RouteData();
routeData.Routers.Add(Mock.Of<IRouter>());
return new ActionContext(
httpContext,
routeData,
new ActionDescriptor());
}
private static HttpContext GetHttpContext(Mock<IOutputFormatter> formatter)
{
var httpContext = new DefaultHttpContext();
httpContext.RequestServices = CreateServices(formatter);
return httpContext;
}
private static Mock<IOutputFormatter> CreateMockFormatter()
{
var formatter = new Mock<IOutputFormatter>
{
CallBase = true
};
formatter.Setup(f => f.CanWriteResult(It.IsAny<OutputFormatterWriteContext>())).Returns(true);
return formatter;
}
private static IServiceProvider CreateServices(Mock<IOutputFormatter> formatter)
{
var options = new TestOptionsManager<MvcOptions>();
options.Value.OutputFormatters.Add(formatter.Object);
var services = new ServiceCollection();
services.AddSingleton(new ObjectResultExecutor(
options,
new TestHttpResponseStreamWriterFactory(),
NullLoggerFactory.Instance));
return services.BuildServiceProvider();
}
private static IUrlHelper GetMockUrlHelper(string returnValue)
{
var urlHelper = new Mock<IUrlHelper>();
urlHelper.Setup(o => o.Link(It.IsAny<string>(), It.IsAny<object>())).Returns(returnValue);
return urlHelper.Object;
}
}
}

View File

@ -0,0 +1,149 @@
// Copyright (c) .NET Foundation. 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.Buffers;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Testing;
using Moq;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Core.Test
{
public class AcceptedResultTests
{
public static TheoryData<object> ValuesData
{
get
{
return new TheoryData<object>
{
null,
"Test string",
new object(),
};
}
}
[Theory]
[MemberData(nameof(ValuesData))]
public void Constructor_InitializesStatusCodeAndValue(object value)
{
// Arrange & Act
var result = new AcceptedResult("testlocation", value);
// Assert
Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode);
Assert.Same(value, result.Value);
}
[Theory]
[MemberData(nameof(ValuesData))]
public async Task ExecuteResultAsync_SetsObjectValueOfFormatter(object value)
{
// Arrange
var location = "/test/";
var formatter = CreateMockFormatter();
var httpContext = GetHttpContext(formatter);
object actual = null;
formatter.Setup(f => f.WriteAsync(It.IsAny<OutputFormatterWriteContext>()))
.Callback((OutputFormatterWriteContext context) => actual = context.Object)
.Returns(Task.FromResult(0));
var actionContext = GetActionContext(httpContext);
// Act
var result = new AcceptedResult(location, value);
await result.ExecuteResultAsync(actionContext);
// Assert
Assert.Same(value, actual);
}
[Fact]
public async Task ExecuteResultAsync_SetsStatusCodeAndLocationHeader()
{
// Arrange
var location = "/test/";
var formatter = CreateMockFormatter();
var httpContext = GetHttpContext(formatter);
var actionContext = GetActionContext(httpContext);
// Act
var result = new AcceptedResult(location, "testInput");
await result.ExecuteResultAsync(actionContext);
// Assert
Assert.Equal(StatusCodes.Status202Accepted, httpContext.Response.StatusCode);
Assert.Equal(location, httpContext.Response.Headers["Location"]);
}
[Fact]
public async Task ExecuteResultAsync_OverwritesLocationHeader()
{
// Arrange
var location = "/test/";
var formatter = CreateMockFormatter();
var httpContext = GetHttpContext(formatter);
var actionContext = GetActionContext(httpContext);
httpContext.Response.Headers["Location"] = "/different/location/";
// Act
var result = new AcceptedResult(location, "testInput");
await result.ExecuteResultAsync(actionContext);
// Assert
Assert.Equal(StatusCodes.Status202Accepted, httpContext.Response.StatusCode);
Assert.Equal(location, httpContext.Response.Headers["Location"]);
}
private static ActionContext GetActionContext(HttpContext httpContext)
{
var routeData = new RouteData();
routeData.Routers.Add(Mock.Of<IRouter>());
return new ActionContext(
httpContext,
routeData,
new ActionDescriptor());
}
private static HttpContext GetHttpContext(Mock<IOutputFormatter> formatter)
{
var httpContext = new DefaultHttpContext();
httpContext.RequestServices = CreateServices(formatter);
return httpContext;
}
private static Mock<IOutputFormatter> CreateMockFormatter()
{
var formatter = new Mock<IOutputFormatter>
{
CallBase = true
};
formatter.Setup(f => f.CanWriteResult(It.IsAny<OutputFormatterWriteContext>())).Returns(true);
return formatter;
}
private static IServiceProvider CreateServices(Mock<IOutputFormatter> formatter)
{
var options = new TestOptionsManager<MvcOptions>();
options.Value.OutputFormatters.Add(formatter.Object);
var services = new ServiceCollection();
services.AddSingleton(new ObjectResultExecutor(
options,
new TestHttpResponseStreamWriterFactory(),
NullLoggerFactory.Instance));
return services.BuildServiceProvider();
}
}
}

View File

@ -566,6 +566,185 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
Assert.Equal(expected, result.RouteValues);
}
[Fact]
public void Accepted_SetsStatusCode()
{
// Arrange
var controller = new TestableController();
// Act
var result = controller.Accepted();
// Assert
Assert.IsType<AcceptedResult>(result);
Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode);
}
[Fact]
public void Accepted_SetsValue()
{
// Arrange
var controller = new TestableController();
var value = new object();
// Act
var result = controller.Accepted(value);
// Assert
Assert.IsType<AcceptedResult>(result);
Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode);
Assert.Same(value, result.Value);
}
[Fact]
public void Accepted_StringUri_SetsAcceptedLocation()
{
// Arrange
var controller = new TestableController();
var uri = "http://test/url";
// Act
var result = controller.Accepted(uri);
// Assert
Assert.IsType<AcceptedResult>(result);
Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode);
Assert.Same(uri, result.Location);
}
[Fact]
public void Accepted_AbsoluteUri_SetsAcceptedLocation()
{
// Arrange
var controller = new TestableController();
var uri = new Uri("http://test/url");
// Act
var result = controller.Accepted(uri);
// Assert
Assert.IsType<AcceptedResult>(result);
Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode);
Assert.Equal(uri.OriginalString, result.Location);
}
[Fact]
public void Accepted_RelativeUri_SetsAcceptedLocation()
{
// Arrange
var controller = new TestableController();
var uri = new Uri("/test/url", UriKind.Relative);
// Act
var result = controller.Accepted(uri);
// Assert
Assert.IsType<AcceptedResult>(result);
Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode);
Assert.Equal(uri.OriginalString, result.Location);
}
[Fact]
public void AcceptedAtAction_SetsActionName()
{
// Arrange
var controller = new TestableController();
// Act
var result = controller.AcceptedAtAction("SampleAction");
// Assert
Assert.IsType<AcceptedAtActionResult>(result);
Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode);
Assert.Equal("SampleAction", result.ActionName);
}
[Theory]
[InlineData("")]
[InlineData(null)]
[InlineData("SampleController")]
public void AcceptedAtAction_SetsActionController(string controllerName)
{
// Arrange
var controller = new TestableController();
// Act
var result = controller.AcceptedAtAction("SampleAction", controllerName);
// Assert
Assert.IsType<AcceptedAtActionResult>(result);
Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode);
Assert.Equal("SampleAction", result.ActionName);
Assert.Equal(controllerName, result.ControllerName);
}
[Fact]
public void AcceptedAtAction_SetsActionControllerRouteValues()
{
// Arrange
var controller = new TestableController();
var expected = new Dictionary<string, object>
{
{ "test", "case" },
{ "sample", "route" },
};
// Act
var result = controller.AcceptedAtAction(
"SampleAction",
"SampleController",
new RouteValueDictionary(expected));
// Assert
Assert.IsType<AcceptedAtActionResult>(result);
Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode);
Assert.Equal("SampleAction", result.ActionName);
Assert.Equal("SampleController", result.ControllerName);
Assert.Equal(expected, result.RouteValues);
}
[Fact]
public void AcceptedAtRoute_SetsRouteValues()
{
// Arrange
var controller = new TestableController();
var expected = new Dictionary<string, object>
{
{ "test", "case" },
{ "sample", "route" },
};
// Act
var result = controller.AcceptedAtRoute(new RouteValueDictionary(expected));
// Assert
Assert.IsType<AcceptedAtRouteResult>(result);
Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode);
Assert.Equal(expected, result.RouteValues);
}
[Fact]
public void AcceptedAtRoute_SetsRouteNameAndValues()
{
// Arrange
var controller = new TestableController();
var routeName = "SampleRoute";
var expected = new Dictionary<string, object>
{
{ "test", "case" },
{ "sample", "route" },
};
// Act
var result = controller.AcceptedAtRoute(routeName, new RouteValueDictionary(expected));
// Assert
Assert.IsType<AcceptedAtRouteResult>(result);
Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode);
Assert.Same(routeName, result.RouteName);
Assert.Equal(expected, result.RouteValues);
}
[Fact]
public void File_WithContents()
{
@ -1107,7 +1286,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
// Arrange
var modelName = "mymodel";
Func<ModelMetadata, bool> propertyFilter = (m) =>
Func<ModelMetadata, bool> propertyFilter = (m) =>
string.Equals(m.PropertyName, "Include1", StringComparison.OrdinalIgnoreCase) ||
string.Equals(m.PropertyName, "Include2", StringComparison.OrdinalIgnoreCase);
@ -1207,7 +1386,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
// Arrange
var modelName = "mymodel";
Func<ModelMetadata, bool> propertyFilter = (m) =>
Func<ModelMetadata, bool> propertyFilter = (m) =>
string.Equals(m.PropertyName, "Include1", StringComparison.OrdinalIgnoreCase) ||
string.Equals(m.PropertyName, "Include2", StringComparison.OrdinalIgnoreCase);
@ -1446,7 +1625,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
var binder = new StubModelBinder();
var controller = GetController(binder, valueProvider: null);
controller.ObjectValidator = new DefaultObjectValidator(
controller.MetadataProvider,
controller.MetadataProvider,
new[] { provider.Object });
// Act

View File

@ -103,6 +103,26 @@ namespace Microsoft.AspNetCore.Mvc
Assert.Equal(StatusCodes.Status201Created, createdResult.StatusCode);
}
[Theory]
[InlineData("/Accepted_1", "<html>AcceptedBody</html>")]
[InlineData("/Accepted_2", null)]
public void ControllerAccepted_InvokedInUnitTests(string uri, string content)
{
// Arrange
var controller = new TestabilityController();
// Act
var result = controller.Accepted_Action(uri, content);
// Assert
Assert.NotNull(result);
var acceptedResult = Assert.IsType<AcceptedResult>(result);
Assert.Equal(uri, acceptedResult.Location);
Assert.Equal(content, acceptedResult.Value);
Assert.Equal(StatusCodes.Status202Accepted, acceptedResult.StatusCode);
}
[Fact]
public void ControllerFileContent_InvokedInUnitTests()
{
@ -335,6 +355,42 @@ namespace Microsoft.AspNetCore.Mvc
Assert.Null(createdAtRouteResult.Value);
}
[Fact]
public void ControllerAcceptedAtRoute_InvokedInUnitTests()
{
// Arrange
var controller = new TestabilityController();
var routeName = "RouteName_1";
var routeValues = new Dictionary<string, object>() { { "route", "sample" } };
var value = new { Value = "Value_1" };
// Act
var result = controller.AcceptedAtRoute_Action(routeName, routeValues, value);
// Assert
Assert.NotNull(result);
var acceptedAtRouteResult = Assert.IsType<AcceptedAtRouteResult>(result);
Assert.Equal(routeName, acceptedAtRouteResult.RouteName);
Assert.Single(acceptedAtRouteResult.RouteValues);
Assert.Equal("sample", acceptedAtRouteResult.RouteValues["route"]);
Assert.Same(value,acceptedAtRouteResult.Value);
// Arrange
controller = new TestabilityController();
// Act
result = controller.AcceptedAtRoute_Action(null, null, null);
// Assert
Assert.NotNull(result);
acceptedAtRouteResult = Assert.IsType<AcceptedAtRouteResult>(result);
Assert.Null(acceptedAtRouteResult.RouteName);
Assert.Null(acceptedAtRouteResult.RouteValues);
Assert.Null(acceptedAtRouteResult.Value);
}
[Fact]
public void ControllerCreatedAtAction_InvokedInUnitTests()
{
@ -374,6 +430,45 @@ namespace Microsoft.AspNetCore.Mvc
Assert.Null(createdAtActionResult.RouteValues);
}
[Fact]
public void ControllerAcceptedAtAction_InvokedInUnitTests()
{
// Arrange
var controller = new TestabilityController();
var actionName = "ActionName_1";
var controllerName = "ControllerName_1";
var routeValues = new Dictionary<string, object>() { { "route", "sample" } };
var value = new { Value = "Value_1" };
// Act
var result = controller.AcceptedAtAction_Action(actionName, controllerName, routeValues, value);
// Assert
Assert.NotNull(result);
var acceptedAtActionResult = Assert.IsType<AcceptedAtActionResult>(result);
Assert.Equal(actionName, acceptedAtActionResult.ActionName);
Assert.Equal(controllerName, acceptedAtActionResult.ControllerName);
Assert.Single(acceptedAtActionResult.RouteValues);
Assert.Equal("sample", acceptedAtActionResult.RouteValues["route"]);
Assert.Same(value, acceptedAtActionResult.Value);
// Arrange
controller = new TestabilityController();
// Act
result = controller.AcceptedAtAction_Action(null, null, null, null);
// Assert
Assert.NotNull(result);
acceptedAtActionResult = Assert.IsType<AcceptedAtActionResult>(result);
Assert.Null(acceptedAtActionResult.ActionName);
Assert.Null(acceptedAtActionResult.ControllerName);
Assert.Null(acceptedAtActionResult.Value);
Assert.Null(acceptedAtActionResult.RouteValues);
}
[Fact]
public void ControllerRedirectToRoute_InvokedInUnitTests()
{
@ -628,6 +723,11 @@ namespace Microsoft.AspNetCore.Mvc
return Created(uri, data);
}
public IActionResult Accepted_Action(string uri, object data)
{
return Accepted(uri, data);
}
public IActionResult FileContent_Action(string content, string contentType, string fileName)
{
var contentArray = Encoding.UTF8.GetBytes(content);
@ -675,6 +775,16 @@ namespace Microsoft.AspNetCore.Mvc
return CreatedAtRoute(routeName, routeValues, value);
}
public IActionResult AcceptedAtAction_Action(string actionName, string controllerName, object routeValues, object value)
{
return AcceptedAtAction(actionName, controllerName, routeValues, value);
}
public IActionResult AcceptedAtRoute_Action(string routeName, object routeValues, object value)
{
return AcceptedAtRoute(routeName, routeValues, value);
}
public IActionResult HttpBadRequest_Action()
{
return BadRequest();