Fix for #1275 - Adding ApiController
This change includes the basic properties that we're providing for compatability as well as some functional tests and unit tests that verify that ApiController can be a controller class.
This commit is contained in:
parent
f66345263d
commit
d9fe305802
|
|
@ -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
|
||||
{
|
||||
/// <summary>Gets the action context.</summary>
|
||||
/// <remarks>The setter is intended for unit testing purposes only.</remarks>
|
||||
[Activate]
|
||||
public ActionContext ActionContext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the http context.
|
||||
/// </summary>
|
||||
public HttpContext Context
|
||||
{
|
||||
get
|
||||
{
|
||||
return ActionContext?.HttpContext;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets model state after the model binding process. This ModelState will be empty before model binding happens.
|
||||
/// </summary>
|
||||
public ModelStateDictionary ModelState
|
||||
{
|
||||
get
|
||||
{
|
||||
return ActionContext?.ModelState;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets a factory used to generate URLs to other APIs.</summary>
|
||||
/// <remarks>The setter is intended for unit testing purposes only.</remarks>
|
||||
[Activate]
|
||||
public IUrlHelper Url { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the current principal associated with this request.</summary>
|
||||
public IPrincipal User
|
||||
{
|
||||
get
|
||||
{
|
||||
return Context?.User;
|
||||
}
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<IApplicationBuilder> _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
|
||||
|
|
@ -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<ControllerActionDescriptor>();
|
||||
|
||||
// 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<ControllerActionDescriptor>();
|
||||
|
||||
// Assert
|
||||
var controllerType = typeof(TestControllers.Blog).GetTypeInfo();
|
||||
var filtered = results.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType).ToArray();
|
||||
|
||||
Assert.Empty(filtered);
|
||||
}
|
||||
|
||||
private INestedProviderManager<ActionDescriptorProviderContext> CreateProvider()
|
||||
{
|
||||
var assemblyProvider = new Mock<IControllerAssemblyProvider>();
|
||||
assemblyProvider
|
||||
.SetupGet(ap => ap.CandidateAssemblies)
|
||||
.Returns(new Assembly[] { typeof(ApiControllerActionDiscoveryTest).Assembly });
|
||||
|
||||
var filterProvider = new Mock<IGlobalFilterProvider>();
|
||||
filterProvider
|
||||
.SetupGet(fp => fp.Filters)
|
||||
.Returns(new List<IFilter>());
|
||||
|
||||
var conventions = new NamespaceLimitedActionDiscoveryConventions();
|
||||
|
||||
var optionsAccessor = new Mock<IOptionsAccessor<MvcOptions>>();
|
||||
optionsAccessor
|
||||
.SetupGet(o => o.Options)
|
||||
.Returns(new MvcOptions());
|
||||
|
||||
var provider = new ControllerActionDescriptorProvider(
|
||||
assemblyProvider.Object,
|
||||
conventions,
|
||||
filterProvider.Object,
|
||||
optionsAccessor.Object);
|
||||
|
||||
return new NestedProviderManager<ActionDescriptorProviderContext>(
|
||||
new INestedProvider<ActionDescriptorProviderContext>[]
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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": { }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<IActionResult> 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<IActionResult> GenerateUrl()
|
||||
{
|
||||
var message = string.Format("Visited: {0}", Url.Action());
|
||||
|
||||
await Context.Response.WriteAsync(message);
|
||||
return new EmptyResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue