diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs
new file mode 100644
index 0000000000..c9f8d3fe4c
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs
@@ -0,0 +1,65 @@
+// 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.Security.Principal;
+using Microsoft.AspNet.Http;
+using Microsoft.AspNet.Mvc;
+using Microsoft.AspNet.Mvc.ModelBinding;
+
+namespace System.Web.Http
+{
+ public abstract class ApiController : IDisposable
+ {
+ /// Gets the action context.
+ /// The setter is intended for unit testing purposes only.
+ [Activate]
+ public ActionContext ActionContext { get; set; }
+
+ ///
+ /// Gets the http context.
+ ///
+ public HttpContext Context
+ {
+ get
+ {
+ return ActionContext?.HttpContext;
+ }
+ }
+
+ ///
+ /// Gets model state after the model binding process. This ModelState will be empty before model binding happens.
+ ///
+ public ModelStateDictionary ModelState
+ {
+ get
+ {
+ return ActionContext?.ModelState;
+ }
+ }
+
+ /// Gets a factory used to generate URLs to other APIs.
+ /// The setter is intended for unit testing purposes only.
+ [Activate]
+ public IUrlHelper Url { get; set; }
+
+ /// Gets or sets the current principal associated with this request.
+ public IPrincipal User
+ {
+ get
+ {
+ return Context?.User;
+ }
+ }
+
+ [NonAction]
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs
new file mode 100644
index 0000000000..ebc4129d2c
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs
@@ -0,0 +1,56 @@
+// 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.
+
+#if ASPNET50
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Builder;
+using Microsoft.AspNet.TestHost;
+using Xunit;
+using System.Net;
+
+namespace Microsoft.AspNet.Mvc.FunctionalTests
+{
+ public class WebApiCompatShimBasicTest
+ {
+ private readonly IServiceProvider _provider = TestHelper.CreateServices(nameof(WebApiCompatShimWebSite));
+ private readonly Action _app = new WebApiCompatShimWebSite.Startup().Configure;
+
+ [Fact]
+ public async Task ApiController_Activates_HttpContextAndUser()
+ {
+ // Arrange
+ var server = TestServer.Create(_provider, _app);
+ var client = server.CreateClient();
+
+ // Act
+ var response = await client.GetAsync("http://localhost/BasicApi/WriteToHttpContext");
+ var content = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(
+ "Hello, Anonymous User from WebApiCompatShimWebSite.BasicApiController.WriteToHttpContext",
+ content);
+ }
+
+ [Fact]
+ public async Task ApiController_Activates_UrlHelper()
+ {
+ // Arrange
+ var server = TestServer.Create(_provider, _app);
+ var client = server.CreateClient();
+
+ // Act
+ var response = await client.GetAsync("http://localhost/BasicApi/GenerateUrl");
+ var content = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(
+ "Visited: /BasicApi/GenerateUrl",
+ content);
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs
new file mode 100644
index 0000000000..f6759bba9c
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs
@@ -0,0 +1,133 @@
+// 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.Linq;
+using System.Reflection;
+using Microsoft.AspNet.Mvc;
+using Microsoft.AspNet.Mvc.Filters;
+using Microsoft.Framework.DependencyInjection;
+using Microsoft.Framework.DependencyInjection.NestedProviders;
+using Microsoft.Framework.OptionsModel;
+using Moq;
+using Xunit;
+
+namespace System.Web.Http
+{
+ public class ApiControllerActionDiscoveryTest
+ {
+ // For now we just want to verify that an ApiController is-a controller and produces
+ // actions. When we implement the conventions for action discovery, this test will be revised.
+ [Fact]
+ public void GetActions_ApiControllerWithControllerSuffix_IsController()
+ {
+ // Arrange
+ var provider = CreateProvider();
+
+ // Act
+ var context = new ActionDescriptorProviderContext();
+ provider.Invoke(context);
+
+ var results = context.Results.Cast();
+
+ // Assert
+ var controllerType = typeof(TestControllers.ProductsController).GetTypeInfo();
+ var filtered = results.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType).ToArray();
+
+ Assert.Equal(3, filtered.Length);
+ }
+
+ [Fact]
+ public void GetActions_ApiControllerWithoutControllerSuffix_IsNotController()
+ {
+ // Arrange
+ var provider = CreateProvider();
+
+ // Act
+ var context = new ActionDescriptorProviderContext();
+ provider.Invoke(context);
+
+ var results = context.Results.Cast();
+
+ // Assert
+ var controllerType = typeof(TestControllers.Blog).GetTypeInfo();
+ var filtered = results.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType).ToArray();
+
+ Assert.Empty(filtered);
+ }
+
+ private INestedProviderManager CreateProvider()
+ {
+ var assemblyProvider = new Mock();
+ assemblyProvider
+ .SetupGet(ap => ap.CandidateAssemblies)
+ .Returns(new Assembly[] { typeof(ApiControllerActionDiscoveryTest).Assembly });
+
+ var filterProvider = new Mock();
+ filterProvider
+ .SetupGet(fp => fp.Filters)
+ .Returns(new List());
+
+ var conventions = new NamespaceLimitedActionDiscoveryConventions();
+
+ var optionsAccessor = new Mock>();
+ optionsAccessor
+ .SetupGet(o => o.Options)
+ .Returns(new MvcOptions());
+
+ var provider = new ControllerActionDescriptorProvider(
+ assemblyProvider.Object,
+ conventions,
+ filterProvider.Object,
+ optionsAccessor.Object);
+
+ return new NestedProviderManager(
+ new INestedProvider[]
+ {
+ provider
+ });
+ }
+
+ private class NamespaceLimitedActionDiscoveryConventions : DefaultActionDiscoveryConventions
+ {
+ public override bool IsController(TypeInfo typeInfo)
+ {
+ return
+ typeInfo.Namespace == "System.Web.Http.TestControllers" &&
+ base.IsController(typeInfo);
+ }
+ }
+ }
+}
+
+// These need to be public top-level classes to test discovery end-to-end. Don't reuse
+// these outside of this test.
+namespace System.Web.Http.TestControllers
+{
+ public class ProductsController : ApiController
+ {
+ public IActionResult GetAll()
+ {
+ return null;
+ }
+
+ public IActionResult Get(int id)
+ {
+ return null;
+ }
+
+ public IActionResult Edit(int id)
+ {
+ return null;
+ }
+ }
+
+ // Not a controller, because there's no controller suffix
+ public class Blog : ApiController
+ {
+ public IActionResult GetBlogPosts()
+ {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerTest.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerTest.cs
new file mode 100644
index 0000000000..1aa96e5cac
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerTest.cs
@@ -0,0 +1,51 @@
+// 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.Security.Claims;
+using Microsoft.AspNet.Mvc;
+using Microsoft.AspNet.PipelineCore;
+using Microsoft.AspNet.Routing;
+using Xunit;
+
+namespace System.Web.Http
+{
+ public class ApiControllerTest
+ {
+ [Fact]
+ public void AccessDependentProperties()
+ {
+ // Arrange
+ var controller = new ConcreteApiController();
+
+ var httpContext = new DefaultHttpContext();
+ httpContext.User = new ClaimsPrincipal();
+
+ var routeContext = new RouteContext(httpContext);
+ var actionContext = new ActionContext(routeContext, new ActionDescriptor());
+
+ // Act
+ controller.ActionContext = actionContext;
+
+ // Assert
+ Assert.Same(httpContext, controller.Context);
+ Assert.Same(actionContext.ModelState, controller.ModelState);
+ Assert.Same(httpContext.User, controller.User);
+ }
+
+ [Fact]
+ public void AccessDependentProperties_UnsetContext()
+ {
+ // Arrange
+ var controller = new ConcreteApiController();
+
+ // Act & Assert
+ Assert.Null(controller.Context);
+ Assert.Null(controller.ModelState);
+ Assert.Null(controller.User);
+ }
+
+ private class ConcreteApiController : ApiController
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/project.json b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/project.json
index ceb4dec9b7..1ca380fda6 100644
--- a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/project.json
+++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/project.json
@@ -1,16 +1,17 @@
{
- "compilationOptions": {
- "warningsAsErrors": "true"
- },
- "dependencies": {
- "Microsoft.AspNet.Mvc": "6.0.0-*",
- "Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-*",
- "Xunit.KRunner": "1.0.0-*"
- },
- "commands": {
- "test": "Xunit.KRunner"
- },
- "frameworks": {
- "aspnet50": { }
- }
+ "compilationOptions": {
+ "warningsAsErrors": "true"
+ },
+ "dependencies": {
+ "Microsoft.AspNet.Mvc": "6.0.0-*",
+ "Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-*",
+ "Moq": "4.2.1312.1622",
+ "Xunit.KRunner": "1.0.0-*"
+ },
+ "commands": {
+ "test": "Xunit.KRunner"
+ },
+ "frameworks": {
+ "aspnet50": { }
+ }
}
diff --git a/test/WebSites/WebApiCompatShimWebSite/Controllers/BasicApiController.cs b/test/WebSites/WebApiCompatShimWebSite/Controllers/BasicApiController.cs
new file mode 100644
index 0000000000..9dc05e5467
--- /dev/null
+++ b/test/WebSites/WebApiCompatShimWebSite/Controllers/BasicApiController.cs
@@ -0,0 +1,36 @@
+// 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;
+using System.Web.Http;
+using Microsoft.AspNet.Http;
+using Microsoft.AspNet.Mvc;
+
+namespace WebApiCompatShimWebSite
+{
+ public class BasicApiController : ApiController
+ {
+ // Verifies property activation
+ [HttpGet]
+ public async Task WriteToHttpContext()
+ {
+ var message = string.Format(
+ "Hello, {0} from {1}",
+ User.Identity?.Name ?? "Anonymous User",
+ ActionContext.ActionDescriptor.DisplayName);
+
+ await Context.Response.WriteAsync(message);
+ return new EmptyResult();
+ }
+
+ // Verifies property activation
+ [HttpGet]
+ public async Task GenerateUrl()
+ {
+ var message = string.Format("Visited: {0}", Url.Action());
+
+ await Context.Response.WriteAsync(message);
+ return new EmptyResult();
+ }
+ }
+}
\ No newline at end of file