diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/HttpNotFoundObjectResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/HttpNotFoundObjectResult.cs
new file mode 100644
index 0000000000..ebfa3fe3ee
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/HttpNotFoundObjectResult.cs
@@ -0,0 +1,23 @@
+// 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 Microsoft.AspNet.WebUtilities;
+
+namespace Microsoft.AspNet.Mvc
+{
+ ///
+ /// An that when executed will produce a Not Found (404) response.
+ ///
+ public class HttpNotFoundObjectResult : ObjectResult
+ {
+ ///
+ /// Creates a new instance.
+ ///
+ /// The value to format in the entity body.
+ public HttpNotFoundObjectResult(object value)
+ : base(value)
+ {
+ StatusCode = StatusCodes.Status404NotFound;
+ }
+ }
+}
\ 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 60cd3021ba..5a0030650a 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs
@@ -692,6 +692,16 @@ namespace Microsoft.AspNet.Mvc
return new HttpNotFoundResult();
}
+ ///
+ /// Creates an that produces a Not Found (404) response.
+ ///
+ /// The created for the response.
+ [NonAction]
+ public virtual HttpNotFoundObjectResult HttpNotFound(object value)
+ {
+ return new HttpNotFoundObjectResult(value);
+ }
+
///
/// Creates an that produces a Bad Request (400) response.
///
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/HttpNotFoundObjectResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/HttpNotFoundObjectResultTest.cs
new file mode 100644
index 0000000000..10da7cdd66
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/HttpNotFoundObjectResultTest.cs
@@ -0,0 +1,139 @@
+// 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.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Http;
+using Microsoft.AspNet.Http.Core;
+using Microsoft.AspNet.Routing;
+using Microsoft.AspNet.WebUtilities;
+using Microsoft.Framework.DependencyInjection;
+using Microsoft.Framework.DependencyInjection.Fallback;
+using Microsoft.Framework.OptionsModel;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNet.Mvc
+{
+ public class HttpNotFoundObjectResultTest
+ {
+ [Fact]
+ public void HttpNotFoundObjectResult_InitializesStatusCode()
+ {
+ // Arrange & act
+ var notFound = new HttpNotFoundObjectResult(null);
+
+ // Assert
+ Assert.Equal(StatusCodes.Status404NotFound, notFound.StatusCode);
+ }
+
+ [Fact]
+ public void HttpNotFoundObjectResult_InitializesStatusCodeAndResponseContent()
+ {
+ // Arrange & act
+ var notFound = new HttpNotFoundObjectResult("Test Content");
+
+ // Assert
+ Assert.Equal(StatusCodes.Status404NotFound, notFound.StatusCode);
+ Assert.Equal("Test Content", notFound.Value);
+ }
+
+ [Fact]
+ public async Task HttpNotFoundObjectResult_ExecuteSuccessful()
+ {
+ // Arrange
+ var input = "Test Content";
+ var stream = new MemoryStream();
+
+ var httpResponse = new Mock();
+ var tempContentType = string.Empty;
+ httpResponse.SetupProperty(o => o.ContentType);
+ httpResponse.SetupGet(r => r.Body).Returns(stream);
+
+ var actionContext = CreateMockActionContext(httpResponse.Object);
+ var notFound = new HttpNotFoundObjectResult(input);
+
+ // Act
+ await notFound.ExecuteResultAsync(actionContext);
+
+ // Assert
+ httpResponse.VerifySet(r => r.StatusCode = StatusCodes.Status404NotFound);
+ Assert.Equal(input.Length, httpResponse.Object.Body.Length);
+ }
+
+ private static ActionContext CreateMockActionContext(
+ HttpResponse response = null,
+ string requestAcceptHeader = "application/*",
+ string requestContentType = "application/json",
+ string requestAcceptCharsetHeader = "",
+ bool respectBrowserAcceptHeader = false)
+ {
+ var formatters = new IOutputFormatter[] { new StringOutputFormatter(), new JsonOutputFormatter() };
+ var httpContext = new Mock();
+ if (response != null)
+ {
+ httpContext.Setup(o => o.Response).Returns(response);
+ }
+
+ var content = "{name: 'Person Name', Age: 'not-an-age'}";
+ var contentBytes = Encoding.UTF8.GetBytes(content);
+
+ var request = new DefaultHttpContext().Request;
+ request.Headers["Accept-Charset"] = requestAcceptCharsetHeader;
+ request.Headers["Accept"] = requestAcceptHeader;
+ request.ContentType = requestContentType;
+ request.Body = new MemoryStream(contentBytes);
+
+ httpContext.Setup(o => o.Request).Returns(request);
+ httpContext.Setup(o => o.RequestServices).Returns(GetServiceProvider());
+ httpContext.Setup(o => o.RequestServices.GetService(typeof(IOutputFormattersProvider)))
+ .Returns(new TestOutputFormatterProvider(formatters));
+
+ var options = new Mock>();
+ options.SetupGet(o => o.Options)
+ .Returns(new MvcOptions()
+ {
+ RespectBrowserAcceptHeader = respectBrowserAcceptHeader
+ });
+ httpContext.Setup(o => o.RequestServices.GetService(typeof(IOptions)))
+ .Returns(options.Object);
+
+ return new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor());
+ }
+
+ private static IServiceProvider GetServiceProvider()
+ {
+ var optionsSetup = new MvcOptionsSetup();
+ var options = new MvcOptions();
+ optionsSetup.Configure(options);
+ var optionsAccessor = new Mock>();
+ optionsAccessor.SetupGet(o => o.Options).Returns(options);
+
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddInstance>(optionsAccessor.Object);
+ return serviceCollection.BuildServiceProvider();
+ }
+
+ private class TestOutputFormatterProvider : IOutputFormattersProvider
+ {
+ private readonly IEnumerable _formatters;
+
+ public TestOutputFormatterProvider(IEnumerable formatters)
+ {
+ _formatters = formatters;
+ }
+
+ public IReadOnlyList OutputFormatters
+ {
+ get
+ {
+ return _formatters.ToList();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs
index f200ccd6ec..0e442cdb1e 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs
@@ -657,6 +657,21 @@ namespace Microsoft.AspNet.Mvc.Test
Assert.Equal(StatusCodes.Status404NotFound, result.StatusCode);
}
+ [Fact]
+ public void HttpNotFound_SetsStatusCodeAndResponseContent()
+ {
+ // Arrange
+ var controller = new TestableController();
+
+ // Act
+ var result = controller.HttpNotFound("Test Content");
+
+ // Assert
+ Assert.IsType(result);
+ Assert.Equal(StatusCodes.Status404NotFound, result.StatusCode);
+ Assert.Equal("Test Content", result.Value);
+ }
+
[Fact]
public void BadRequest_SetsStatusCode()
{
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs
index 2933aa5130..e676754cdc 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs
@@ -214,6 +214,23 @@ namespace Microsoft.AspNet.Mvc
Assert.Equal(StatusCodes.Status404NotFound, httpNotFoundResult.StatusCode);
}
+ [Fact]
+ public void ControllerHttpNotFoundObject_InvokedInUnitTests()
+ {
+ // Arrange
+ var controller = new TestabilityController();
+
+ // Act
+ var result = controller.HttpNotFoundObject_Action("Test Content");
+
+ // Assert
+ Assert.NotNull(result);
+
+ var httpNotFoundObjectResult = Assert.IsType(result);
+ Assert.Equal(StatusCodes.Status404NotFound, httpNotFoundObjectResult.StatusCode);
+ Assert.Equal("Test Content", httpNotFoundObjectResult.Value);
+ }
+
[Fact]
public void ControllerHttpBadRequest_InvokedInUnitTests()
{
@@ -621,6 +638,11 @@ namespace Microsoft.AspNet.Mvc
{
return HttpNotFound();
}
+
+ public IActionResult HttpNotFoundObject_Action(object value)
+ {
+ return HttpNotFound(value);
+ }
}
private class MyModel
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionResultTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionResultTests.cs
index 2e926dc14b..813e9cd4c0 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionResultTests.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionResultTests.cs
@@ -284,5 +284,45 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Assert
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
}
+
+ [Fact]
+ public async Task HttpNotFoundObjectResult_NoResponseContent()
+ {
+ // Arrange
+ var server = TestServer.Create(_provider, _app);
+ var client = server.CreateClient();
+
+ var request = new HttpRequestMessage(
+ HttpMethod.Get,
+ "http://localhost/ActionResultsVerification/GetNotFoundObjectResult");
+
+ // Act
+ var response = await client.SendAsync(request);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync());
+ }
+
+ [Fact]
+ public async Task HttpNotFoundObjectResult_WithResponseContent()
+ {
+ // Arrange
+ var server = TestServer.Create(_provider, _app);
+ var client = server.CreateClient();
+ var input = "{\"SampleInt\":10}";
+
+ var request = new HttpRequestMessage(
+ HttpMethod.Post,
+ "http://localhost/ActionResultsVerification/GetNotFoundObjectResultWithContent");
+ request.Content = new StringContent(input, Encoding.UTF8, "application/json");
+
+ // Act
+ var response = await client.SendAsync(request);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ Assert.Equal("{\"SampleInt\":10,\"SampleString\":\"Foo\"}", await response.Content.ReadAsStringAsync());
+ }
}
}
\ No newline at end of file
diff --git a/test/WebSites/ActionResultsWebSite/Controllers/ActionResultsVerificationController.cs b/test/WebSites/ActionResultsWebSite/Controllers/ActionResultsVerificationController.cs
index e951a6b420..8fddb320c3 100644
--- a/test/WebSites/ActionResultsWebSite/Controllers/ActionResultsVerificationController.cs
+++ b/test/WebSites/ActionResultsWebSite/Controllers/ActionResultsVerificationController.cs
@@ -85,6 +85,16 @@ namespace ActionResultsWebSite
return result;
}
+ public IActionResult GetNotFoundObjectResult()
+ {
+ return HttpNotFound(null);
+ }
+
+ public IActionResult GetNotFoundObjectResultWithContent()
+ {
+ return HttpNotFound(CreateDummy());
+ }
+
public DummyClass GetDummy(int id)
{
return CreateDummy();