diff --git a/Mvc.sln b/Mvc.sln
index f1bc715fb6..a47da8f452 100644
--- a/Mvc.sln
+++ b/Mvc.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.22410.0
+VisualStudioVersion = 14.0.22416.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
EndProject
@@ -112,6 +112,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CompositeViewEngineWebSite"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ValueProvidersWebSite", "test\WebSites\ValueProvidersWebSite\ValueProvidersWebSite.kproj", "{14F79E79-AE79-48FA-95DE-D794EF4EABB3}"
EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ActionResultsWebSite", "test\WebSites\ActionResultsWebSite\ActionResultsWebSite.kproj", "{0A6BB4C0-48D3-4E7F-952B-B8917345E075}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -608,6 +610,18 @@ Global
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|x86.ActiveCfg = Release|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|x86.Build.0 = Release|Any CPU
+ {0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Debug|x86.Build.0 = Debug|Any CPU
+ {0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Release|x86.ActiveCfg = Release|Any CPU
+ {0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -662,5 +676,6 @@ Global
{B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{A853B2BA-4449-4908-A416-5A3C027FC22B} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{14F79E79-AE79-48FA-95DE-D794EF4EABB3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
+ {0A6BB4C0-48D3-4E7F-952B-B8917345E075} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
EndGlobalSection
EndGlobal
diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/BadRequestResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/BadRequestResult.cs
new file mode 100644
index 0000000000..052d026a67
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/BadRequestResult.cs
@@ -0,0 +1,20 @@
+// 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.
+
+namespace Microsoft.AspNet.Mvc
+{
+ ///
+ /// A that when
+ /// executed will produce a Bad Request (400) response.
+ ///
+ public class BadRequestResult : HttpStatusCodeResult
+ {
+ ///
+ /// Creates a new instance.
+ ///
+ public BadRequestResult()
+ : base(400)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtActionResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtActionResult.cs
new file mode 100644
index 0000000000..764b9bbc62
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtActionResult.cs
@@ -0,0 +1,71 @@
+// 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 Microsoft.AspNet.Mvc.Core;
+using Microsoft.Framework.DependencyInjection;
+
+namespace Microsoft.AspNet.Mvc
+{
+ ///
+ /// An that returns a Created (201) response with a Location header.
+ ///
+ public class CreatedAtActionResult : ObjectResult
+ {
+ ///
+ /// Initializes a new instance of the with the values
+ /// provided.
+ ///
+ /// The name of the action to use for generating the URL.
+ /// The name of the controller to use for generating the URL.
+ /// The route data to use for generating the URL.
+ /// The value to format in the entity body.
+ public CreatedAtActionResult(string actionName,
+ string controllerName,
+ object routeValues,
+ object value)
+ : base(value)
+ {
+ ActionName = actionName;
+ ControllerName = controllerName;
+ RouteValues = TypeHelper.ObjectToDictionary(routeValues);
+ StatusCode = 201;
+ }
+
+ ///
+ /// Gets or sets the used to generate URLs.
+ ///
+ public IUrlHelper UrlHelper { get; set; }
+
+ ///
+ /// Gets the name of the action to use for generating the URL.
+ ///
+ public string ActionName { get; private set; }
+
+ ///
+ /// Gets the name of the controller to use for generating the URL.
+ ///
+ public string ControllerName { get; private set; }
+
+ ///
+ /// Gets the route data to use for generating the URL.
+ ///
+ public IDictionary RouteValues { get; private set; }
+
+ ///
+ protected override void OnFormatting([NotNull] ActionContext context)
+ {
+ var urlHelper = UrlHelper ?? context.HttpContext.RequestServices.GetRequiredService();
+
+ var url = urlHelper.Action(ActionName, ControllerName, RouteValues);
+
+ if (string.IsNullOrEmpty(url))
+ {
+ throw new InvalidOperationException(Resources.NoRoutesMatched);
+ }
+
+ context.HttpContext.Response.Headers.Add("Location", new string[] { url });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtRouteResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtRouteResult.cs
new file mode 100644
index 0000000000..d1e99b447d
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtRouteResult.cs
@@ -0,0 +1,74 @@
+// 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 Microsoft.AspNet.Mvc.Core;
+using Microsoft.Framework.DependencyInjection;
+
+namespace Microsoft.AspNet.Mvc
+{
+ ///
+ /// An that returns a Created (201) response with a Location header.
+ ///
+ public class CreatedAtRouteResult : ObjectResult
+ {
+ ///
+ /// Initializes a new instance of the class with the values
+ /// provided.
+ ///
+ /// The route data to use for generating the URL.
+ /// The value to format in the entity body.
+ public CreatedAtRouteResult(object routeValues, object value)
+ : this(routeName: null, routeValues: routeValues, value: value)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with the values
+ /// provided.
+ ///
+ /// The name of the route to use for generating the URL.
+ /// The route data to use for generating the URL.
+ /// The value to format in the entity body.
+ public CreatedAtRouteResult(string routeName,
+ object routeValues,
+ object value)
+ : base(value)
+ {
+ RouteName = routeName;
+ RouteValues = TypeHelper.ObjectToDictionary(routeValues);
+ StatusCode = 201;
+ }
+
+ ///
+ /// Gets or sets the used to generate URLs.
+ ///
+ public IUrlHelper UrlHelper { get; set; }
+
+ ///
+ /// Gets the name of the route to use for generating the URL.
+ ///
+ public string RouteName { get; private set; }
+
+ ///
+ /// Gets the route data to use for generating the URL.
+ ///
+ public IDictionary RouteValues { get; private set; }
+
+ ///
+ protected override void OnFormatting([NotNull] ActionContext context)
+ {
+ var urlHelper = UrlHelper ?? context.HttpContext.RequestServices.GetRequiredService();
+
+ var url = urlHelper.RouteUrl(RouteName, RouteValues);
+
+ if (string.IsNullOrEmpty(url))
+ {
+ throw new InvalidOperationException(Resources.NoRoutesMatched);
+ }
+
+ context.HttpContext.Response.Headers.Add("Location", new string[] { url });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedResult.cs
new file mode 100644
index 0000000000..42f14a53d5
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedResult.cs
@@ -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;
+
+namespace Microsoft.AspNet.Mvc
+{
+ ///
+ /// An that returns a Created (201) response with a Location header.
+ ///
+ public class CreatedResult : ObjectResult
+ {
+ ///
+ /// Initializes a new instance of the class with the values
+ /// provided.
+ ///
+ /// The location at which the content has been created.
+ /// The value to format in the entity body.
+ public CreatedResult([NotNull] string location, object value)
+ : base(value)
+ {
+ Location = location;
+ StatusCode = 201;
+ }
+
+ ///
+ /// Gets the location at which the content has been created.
+ ///
+ public string Location { get; private set; }
+
+ ///
+ protected override void OnFormatting([NotNull] ActionContext context)
+ {
+ context.HttpContext.Response.Headers.Add("Location", new string[] { Location });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs
index ce3b91e1d5..e8aec35b4a 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs
@@ -20,6 +20,11 @@ namespace Microsoft.AspNet.Mvc
public Type DeclaredType { get; set; }
+ ///
+ /// Gets or sets the HTTP status code.
+ ///
+ public int? StatusCode { get; set; }
+
public ObjectResult(object value)
{
Value = value;
@@ -45,6 +50,12 @@ namespace Microsoft.AspNet.Mvc
return;
}
+ if (StatusCode != null)
+ {
+ context.HttpContext.Response.StatusCode = (int)StatusCode;
+ }
+
+ OnFormatting(context);
await selectedFormatter.WriteAsync(formatterContext);
}
@@ -216,5 +227,12 @@ namespace Microsoft.AspNet.Mvc
return formatters;
}
+
+ ///
+ /// This method is called before the formatter writes to the output stream.
+ ///
+ protected virtual void OnFormatting([NotNull] ActionContext context)
+ {
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs
index 2dc1125d70..ea16319d0b 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs
@@ -617,6 +617,128 @@ namespace Microsoft.AspNet.Mvc
return new HttpNotFoundResult();
}
+ ///
+ /// Creates an that produces a Bad Request (400) response.
+ ///
+ /// The created for the response.
+ [NonAction]
+ public virtual BadRequestResult HttpBadRequest()
+ {
+ return new BadRequestResult();
+ }
+
+ ///
+ /// Creates a object that produces a Created (201) response.
+ ///
+ /// The URI at which the content has been created.
+ /// The content value to format in the entity body.
+ /// The created for the response.
+ [NonAction]
+ public virtual CreatedResult Created([NotNull] string uri, object value)
+ {
+ return new CreatedResult(uri, value);
+ }
+
+ ///
+ /// Creates a object that produces a Created (201) response.
+ ///
+ /// The URI at which the content has been created.
+ /// The content value to format in the entity body.
+ /// The created for the response.
+ [NonAction]
+ public virtual CreatedResult Created([NotNull] Uri uri, object value)
+ {
+ string location;
+ if (uri.IsAbsoluteUri)
+ {
+ location = uri.AbsoluteUri;
+ }
+ else
+ {
+ location = uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
+ }
+ return new CreatedResult(location, value);
+ }
+
+ ///
+ /// Creates a object that produces a Created (201) response.
+ ///
+ /// The name of the action to use for generating the URL.
+ /// The content value to format in the entity body.
+ /// The created for the response.
+ [NonAction]
+ public virtual CreatedAtActionResult CreatedAtAction(string actionName, object value)
+ {
+ return CreatedAtAction(actionName, routeValues: null, value: value);
+ }
+
+ ///
+ /// Creates a object that produces a Created (201) response.
+ ///
+ /// The name of the action to use for generating the URL.
+ /// The route data to use for generating the URL.
+ /// The content value to format in the entity body.
+ /// The created for the response.
+ [NonAction]
+ public virtual CreatedAtActionResult CreatedAtAction(string actionName, object routeValues, object value)
+ {
+ return CreatedAtAction(actionName, controllerName: null, routeValues: routeValues, value: value);
+ }
+
+ ///
+ /// Creates a object that produces a Created (201) response.
+ ///
+ /// The name of the action to use for generating the URL.
+ /// The name of the controller to use for generating the URL.
+ /// The route data to use for generating the URL.
+ /// The content value to format in the entity body.
+ /// The created for the response.
+ [NonAction]
+ public virtual CreatedAtActionResult CreatedAtAction(string actionName,
+ string controllerName,
+ object routeValues,
+ object value)
+ {
+ return new CreatedAtActionResult(actionName, controllerName, routeValues, value);
+ }
+
+ ///
+ /// Creates a object that produces a Created (201) response.
+ ///
+ /// The name of the route to use for generating the URL.
+ /// The content value to format in the entity body.
+ /// The created for the response.
+ [NonAction]
+ public virtual CreatedAtRouteResult CreatedAtRoute(string routeName, object value)
+ {
+ return CreatedAtRoute(routeName, routeValues: null, value: value);
+ }
+
+ ///
+ /// Creates a object that produces a Created (201) response.
+ ///
+ /// The route data to use for generating the URL.
+ /// The content value to format in the entity body.
+ /// The created for the response.
+ [NonAction]
+ public virtual CreatedAtRouteResult CreatedAtRoute(object routeValues, object value)
+ {
+ return CreatedAtRoute(routeName: null, routeValues: routeValues, value: value);
+ }
+
+ ///
+ /// Creates a object that produces a Created (201) response.
+ ///
+ /// The name of the route to use for generating the URL.
+ /// The route data to use for generating the URL.
+ /// The content value to format in the entity body.
+ /// The created for the response.
+ [NonAction]
+ public virtual CreatedAtRouteResult CreatedAtRoute(string routeName, object routeValues, object value)
+ {
+ return new CreatedAtRouteResult(routeName, routeValues, value);
+ }
+
///
/// Called before the action method is invoked.
///
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/BadRequestResultTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/BadRequestResultTests.cs
new file mode 100644
index 0000000000..fbb5cbb18c
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/BadRequestResultTests.cs
@@ -0,0 +1,20 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNet.Mvc
+{
+ public class BadRequestResultTests
+ {
+ [Fact]
+ public void BadRequestResult_InitializesStatusCode()
+ {
+ // Arrange & act
+ var badRequest = new BadRequestResult();
+
+ // Assert
+ Assert.Equal(400, badRequest.StatusCode);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/CreatedAtActionResultTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/CreatedAtActionResultTests.cs
new file mode 100644
index 0000000000..fab57bcedd
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/CreatedAtActionResultTests.cs
@@ -0,0 +1,126 @@
+// 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.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Http;
+using Microsoft.AspNet.PipelineCore.Collections;
+using Microsoft.AspNet.Routing;
+using Microsoft.AspNet.Testing;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNet.Mvc
+{
+ public class CreatedAtActionResultTests
+ {
+ [Fact]
+ public async Task CreatedAtActionResult_ReturnsStatusCode_SetsLocationHeader()
+ {
+ // Arrange
+ var expectedUrl = "testAction";
+ var response = GetMockedHttpResponseObject();
+ var httpContext = GetHttpContext(response);
+ var actionContext = GetActionContext(httpContext);
+ var urlHelper = GetMockUrlHelper(expectedUrl);
+
+ // Act
+ var result = new CreatedAtActionResult(
+ actionName: expectedUrl,
+ controllerName: null,
+ routeValues: null,
+ value: null);
+
+ result.UrlHelper = urlHelper;
+ await result.ExecuteResultAsync(actionContext);
+
+ // Assert
+ Assert.Equal(201, response.StatusCode);
+ Assert.Equal(expectedUrl, response.Headers["Location"]);
+ }
+
+ [Fact]
+ public async Task CreatedAtActionResult_ThrowsOnNullUrl()
+ {
+ // Arrange
+ var response = GetMockedHttpResponseObject();
+ var httpContext = GetHttpContext(response);
+ var actionContext = GetActionContext(httpContext);
+ var urlHelper = GetMockUrlHelper(returnValue: null);
+
+ var result = new CreatedAtActionResult(
+ actionName: null,
+ controllerName: null,
+ routeValues: null,
+ value: null);
+
+ result.UrlHelper = urlHelper;
+
+ // Act & Assert
+ await ExceptionAssert.ThrowsAsync(
+ async () => await result.ExecuteResultAsync(actionContext),
+ "No route matches the supplied values.");
+ }
+
+ private static HttpResponse GetMockedHttpResponseObject()
+ {
+ var stream = new MemoryStream();
+ var httpResponse = new Mock();
+ httpResponse.SetupProperty(o => o.StatusCode);
+ httpResponse.Setup(o => o.Headers).Returns(
+ new HeaderDictionary(new Dictionary()));
+ httpResponse.SetupGet(o => o.Body).Returns(stream);
+ return httpResponse.Object;
+ }
+
+ private static ActionContext GetActionContext(HttpContext httpContext)
+ {
+ var routeData = new RouteData();
+ routeData.Routers.Add(Mock.Of());
+
+ return new ActionContext(httpContext,
+ routeData,
+ new ActionDescriptor());
+ }
+
+ private static HttpContext GetHttpContext(HttpResponse response)
+ {
+ var httpContext = new Mock();
+
+ httpContext.Setup(o => o.Response)
+ .Returns(response);
+ httpContext.Setup(o => o.RequestServices.GetService(typeof(IOutputFormattersProvider)))
+ .Returns(new TestOutputFormatterProvider());
+ httpContext.Setup(o => o.Request.PathBase)
+ .Returns(new PathString(""));
+
+ return httpContext.Object;
+ }
+
+ private static IUrlHelper GetMockUrlHelper(string returnValue)
+ {
+ var urlHelper = new Mock();
+ urlHelper.Setup(o => o.Action(It.IsAny(), It.IsAny(), It.IsAny