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:
Ryan Nowak 2014-10-08 09:19:42 -07:00
parent f66345263d
commit d9fe305802
6 changed files with 356 additions and 14 deletions

View File

@ -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)
{
}
}
}

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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
{
}
}
}

View File

@ -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": { }
}
}

View File

@ -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();
}
}
}