Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit d9fe305

Browse files
committed
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.
1 parent f663452 commit d9fe305

File tree

6 files changed

+356
-14
lines changed

6 files changed

+356
-14
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Security.Principal;
5+
using Microsoft.AspNet.Http;
6+
using Microsoft.AspNet.Mvc;
7+
using Microsoft.AspNet.Mvc.ModelBinding;
8+
9+
namespace System.Web.Http
10+
{
11+
public abstract class ApiController : IDisposable
12+
{
13+
/// <summary>Gets the action context.</summary>
14+
/// <remarks>The setter is intended for unit testing purposes only.</remarks>
15+
[Activate]
16+
public ActionContext ActionContext { get; set; }
17+
18+
/// <summary>
19+
/// Gets the http context.
20+
/// </summary>
21+
public HttpContext Context
22+
{
23+
get
24+
{
25+
return ActionContext?.HttpContext;
26+
}
27+
}
28+
29+
/// <summary>
30+
/// Gets model state after the model binding process. This ModelState will be empty before model binding happens.
31+
/// </summary>
32+
public ModelStateDictionary ModelState
33+
{
34+
get
35+
{
36+
return ActionContext?.ModelState;
37+
}
38+
}
39+
40+
/// <summary>Gets a factory used to generate URLs to other APIs.</summary>
41+
/// <remarks>The setter is intended for unit testing purposes only.</remarks>
42+
[Activate]
43+
public IUrlHelper Url { get; set; }
44+
45+
/// <summary>Gets or sets the current principal associated with this request.</summary>
46+
public IPrincipal User
47+
{
48+
get
49+
{
50+
return Context?.User;
51+
}
52+
}
53+
54+
[NonAction]
55+
public void Dispose()
56+
{
57+
Dispose(true);
58+
GC.SuppressFinalize(this);
59+
}
60+
61+
protected virtual void Dispose(bool disposing)
62+
{
63+
}
64+
}
65+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
#if ASPNET50
5+
using System;
6+
using System.Threading.Tasks;
7+
using Microsoft.AspNet.Builder;
8+
using Microsoft.AspNet.TestHost;
9+
using Xunit;
10+
using System.Net;
11+
12+
namespace Microsoft.AspNet.Mvc.FunctionalTests
13+
{
14+
public class WebApiCompatShimBasicTest
15+
{
16+
private readonly IServiceProvider _provider = TestHelper.CreateServices(nameof(WebApiCompatShimWebSite));
17+
private readonly Action<IApplicationBuilder> _app = new WebApiCompatShimWebSite.Startup().Configure;
18+
19+
[Fact]
20+
public async Task ApiController_Activates_HttpContextAndUser()
21+
{
22+
// Arrange
23+
var server = TestServer.Create(_provider, _app);
24+
var client = server.CreateClient();
25+
26+
// Act
27+
var response = await client.GetAsync("http://localhost/BasicApi/WriteToHttpContext");
28+
var content = await response.Content.ReadAsStringAsync();
29+
30+
// Assert
31+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
32+
Assert.Equal(
33+
"Hello, Anonymous User from WebApiCompatShimWebSite.BasicApiController.WriteToHttpContext",
34+
content);
35+
}
36+
37+
[Fact]
38+
public async Task ApiController_Activates_UrlHelper()
39+
{
40+
// Arrange
41+
var server = TestServer.Create(_provider, _app);
42+
var client = server.CreateClient();
43+
44+
// Act
45+
var response = await client.GetAsync("http://localhost/BasicApi/GenerateUrl");
46+
var content = await response.Content.ReadAsStringAsync();
47+
48+
// Assert
49+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
50+
Assert.Equal(
51+
"Visited: /BasicApi/GenerateUrl",
52+
content);
53+
}
54+
}
55+
}
56+
#endif
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Reflection;
7+
using Microsoft.AspNet.Mvc;
8+
using Microsoft.AspNet.Mvc.Filters;
9+
using Microsoft.Framework.DependencyInjection;
10+
using Microsoft.Framework.DependencyInjection.NestedProviders;
11+
using Microsoft.Framework.OptionsModel;
12+
using Moq;
13+
using Xunit;
14+
15+
namespace System.Web.Http
16+
{
17+
public class ApiControllerActionDiscoveryTest
18+
{
19+
// For now we just want to verify that an ApiController is-a controller and produces
20+
// actions. When we implement the conventions for action discovery, this test will be revised.
21+
[Fact]
22+
public void GetActions_ApiControllerWithControllerSuffix_IsController()
23+
{
24+
// Arrange
25+
var provider = CreateProvider();
26+
27+
// Act
28+
var context = new ActionDescriptorProviderContext();
29+
provider.Invoke(context);
30+
31+
var results = context.Results.Cast<ControllerActionDescriptor>();
32+
33+
// Assert
34+
var controllerType = typeof(TestControllers.ProductsController).GetTypeInfo();
35+
var filtered = results.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType).ToArray();
36+
37+
Assert.Equal(3, filtered.Length);
38+
}
39+
40+
[Fact]
41+
public void GetActions_ApiControllerWithoutControllerSuffix_IsNotController()
42+
{
43+
// Arrange
44+
var provider = CreateProvider();
45+
46+
// Act
47+
var context = new ActionDescriptorProviderContext();
48+
provider.Invoke(context);
49+
50+
var results = context.Results.Cast<ControllerActionDescriptor>();
51+
52+
// Assert
53+
var controllerType = typeof(TestControllers.Blog).GetTypeInfo();
54+
var filtered = results.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType).ToArray();
55+
56+
Assert.Empty(filtered);
57+
}
58+
59+
private INestedProviderManager<ActionDescriptorProviderContext> CreateProvider()
60+
{
61+
var assemblyProvider = new Mock<IControllerAssemblyProvider>();
62+
assemblyProvider
63+
.SetupGet(ap => ap.CandidateAssemblies)
64+
.Returns(new Assembly[] { typeof(ApiControllerActionDiscoveryTest).Assembly });
65+
66+
var filterProvider = new Mock<IGlobalFilterProvider>();
67+
filterProvider
68+
.SetupGet(fp => fp.Filters)
69+
.Returns(new List<IFilter>());
70+
71+
var conventions = new NamespaceLimitedActionDiscoveryConventions();
72+
73+
var optionsAccessor = new Mock<IOptionsAccessor<MvcOptions>>();
74+
optionsAccessor
75+
.SetupGet(o => o.Options)
76+
.Returns(new MvcOptions());
77+
78+
var provider = new ControllerActionDescriptorProvider(
79+
assemblyProvider.Object,
80+
conventions,
81+
filterProvider.Object,
82+
optionsAccessor.Object);
83+
84+
return new NestedProviderManager<ActionDescriptorProviderContext>(
85+
new INestedProvider<ActionDescriptorProviderContext>[]
86+
{
87+
provider
88+
});
89+
}
90+
91+
private class NamespaceLimitedActionDiscoveryConventions : DefaultActionDiscoveryConventions
92+
{
93+
public override bool IsController(TypeInfo typeInfo)
94+
{
95+
return
96+
typeInfo.Namespace == "System.Web.Http.TestControllers" &&
97+
base.IsController(typeInfo);
98+
}
99+
}
100+
}
101+
}
102+
103+
// These need to be public top-level classes to test discovery end-to-end. Don't reuse
104+
// these outside of this test.
105+
namespace System.Web.Http.TestControllers
106+
{
107+
public class ProductsController : ApiController
108+
{
109+
public IActionResult GetAll()
110+
{
111+
return null;
112+
}
113+
114+
public IActionResult Get(int id)
115+
{
116+
return null;
117+
}
118+
119+
public IActionResult Edit(int id)
120+
{
121+
return null;
122+
}
123+
}
124+
125+
// Not a controller, because there's no controller suffix
126+
public class Blog : ApiController
127+
{
128+
public IActionResult GetBlogPosts()
129+
{
130+
return null;
131+
}
132+
}
133+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Security.Claims;
5+
using Microsoft.AspNet.Mvc;
6+
using Microsoft.AspNet.PipelineCore;
7+
using Microsoft.AspNet.Routing;
8+
using Xunit;
9+
10+
namespace System.Web.Http
11+
{
12+
public class ApiControllerTest
13+
{
14+
[Fact]
15+
public void AccessDependentProperties()
16+
{
17+
// Arrange
18+
var controller = new ConcreteApiController();
19+
20+
var httpContext = new DefaultHttpContext();
21+
httpContext.User = new ClaimsPrincipal();
22+
23+
var routeContext = new RouteContext(httpContext);
24+
var actionContext = new ActionContext(routeContext, new ActionDescriptor());
25+
26+
// Act
27+
controller.ActionContext = actionContext;
28+
29+
// Assert
30+
Assert.Same(httpContext, controller.Context);
31+
Assert.Same(actionContext.ModelState, controller.ModelState);
32+
Assert.Same(httpContext.User, controller.User);
33+
}
34+
35+
[Fact]
36+
public void AccessDependentProperties_UnsetContext()
37+
{
38+
// Arrange
39+
var controller = new ConcreteApiController();
40+
41+
// Act & Assert
42+
Assert.Null(controller.Context);
43+
Assert.Null(controller.ModelState);
44+
Assert.Null(controller.User);
45+
}
46+
47+
private class ConcreteApiController : ApiController
48+
{
49+
}
50+
}
51+
}
Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
{
2-
"compilationOptions": {
3-
"warningsAsErrors": "true"
4-
},
5-
"dependencies": {
6-
"Microsoft.AspNet.Mvc": "6.0.0-*",
7-
"Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-*",
8-
"Xunit.KRunner": "1.0.0-*"
9-
},
10-
"commands": {
11-
"test": "Xunit.KRunner"
12-
},
13-
"frameworks": {
14-
"aspnet50": { }
15-
}
2+
"compilationOptions": {
3+
"warningsAsErrors": "true"
4+
},
5+
"dependencies": {
6+
"Microsoft.AspNet.Mvc": "6.0.0-*",
7+
"Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-*",
8+
"Moq": "4.2.1312.1622",
9+
"Xunit.KRunner": "1.0.0-*"
10+
},
11+
"commands": {
12+
"test": "Xunit.KRunner"
13+
},
14+
"frameworks": {
15+
"aspnet50": { }
16+
}
1617
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Threading.Tasks;
5+
using System.Web.Http;
6+
using Microsoft.AspNet.Http;
7+
using Microsoft.AspNet.Mvc;
8+
9+
namespace WebApiCompatShimWebSite
10+
{
11+
public class BasicApiController : ApiController
12+
{
13+
// Verifies property activation
14+
[HttpGet]
15+
public async Task<IActionResult> WriteToHttpContext()
16+
{
17+
var message = string.Format(
18+
"Hello, {0} from {1}",
19+
User.Identity?.Name ?? "Anonymous User",
20+
ActionContext.ActionDescriptor.DisplayName);
21+
22+
await Context.Response.WriteAsync(message);
23+
return new EmptyResult();
24+
}
25+
26+
// Verifies property activation
27+
[HttpGet]
28+
public async Task<IActionResult> GenerateUrl()
29+
{
30+
var message = string.Format("Visited: {0}", Url.Action());
31+
32+
await Context.Response.WriteAsync(message);
33+
return new EmptyResult();
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)