aspnetcore/test/Microsoft.AspNetCore.Mvc.We.../ApiControllerActionDiscover...

538 lines
18 KiB
C#

// Copyright (c) .NET Foundation. 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.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.WebApiCompatShim;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace System.Web.Http
{
public class ApiControllerActionDiscoveryTest
{
[Fact]
public void GetActions_ApiControllerWithControllerSuffix_IsController()
{
// Arrange
var provider = CreateProvider();
// Act
var context = new ActionDescriptorProviderContext();
Invoke(provider, context);
var results = context.Results.Cast<ControllerActionDescriptor>();
// Assert
var controllerType = typeof(TestControllers.ProductsController).GetTypeInfo();
var actions = results.Where(ad => ad.ControllerTypeInfo == controllerType).ToArray();
Assert.NotEmpty(actions);
}
[Fact]
public void GetActions_ApiControllerWithoutControllerSuffix_IsNotController()
{
// Arrange
var provider = CreateProvider();
// Act
var context = new ActionDescriptorProviderContext();
Invoke(provider, context);
var results = context.Results.Cast<ControllerActionDescriptor>();
// Assert
var controllerType = typeof(TestControllers.Blog).GetTypeInfo();
var actions = results.Where(ad => ad.ControllerTypeInfo == controllerType);
Assert.Equal(2, actions.Count());
}
[Fact]
public void GetActions_CreatesNamedAndUnnamedAction()
{
// Arrange
var provider = CreateProvider();
// Act
var context = new ActionDescriptorProviderContext();
Invoke(provider, context);
var results = context.Results.Cast<ControllerActionDescriptor>();
// Assert
var controllerType = typeof(TestControllers.StoreController).GetTypeInfo();
var actions = results
.Where(ad => ad.ControllerTypeInfo == controllerType)
.Where(ad => ad.MethodInfo.Name == "GetAll")
.ToArray();
Assert.Equal(2, actions.Length);
var action = Assert.Single(
actions,
a => a.RouteValues.Any(rc => rc.Key == "action" && rc.Value == "GetAll"));
Assert.Equal(
new string[] { "GET" },
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
action = Assert.Single(
actions,
a => a.RouteValues.Any(rc => rc.Key == "action" && string.IsNullOrEmpty(rc.Value)));
Assert.Equal(
new string[] { "GET" },
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
}
[Fact]
public void GetActions_CreatesNamedAndUnnamedAction_DefaultVerbIsPost()
{
// Arrange
var provider = CreateProvider();
// Act
var context = new ActionDescriptorProviderContext();
Invoke(provider, context);
var results = context.Results.Cast<ControllerActionDescriptor>();
// Assert
var controllerType = typeof(TestControllers.StoreController).GetTypeInfo();
var actions = results
.Where(ad => ad.ControllerTypeInfo == controllerType)
.Where(ad => ad.MethodInfo.Name == "Edit")
.ToArray();
Assert.Equal(2, actions.Length);
var action = Assert.Single(
actions,
a => a.RouteValues.Any(rc => rc.Key == "action" && rc.Value == "Edit"));
Assert.Equal(
new string[] { "POST" },
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
action = Assert.Single(
actions,
a => a.RouteValues.Any(rc => rc.Key == "action" && string.IsNullOrEmpty(rc.Value)));
Assert.Equal(
new string[] { "POST" },
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
}
[Fact]
public void GetActions_CreatesNamedAndUnnamedAction_RespectsVerbAttribute()
{
// Arrange
var provider = CreateProvider();
// Act
var context = new ActionDescriptorProviderContext();
Invoke(provider, context);
var results = context.Results.Cast<ControllerActionDescriptor>();
// Assert
var controllerType = typeof(TestControllers.StoreController).GetTypeInfo();
var actions = results
.Where(ad => ad.ControllerTypeInfo == controllerType)
.Where(ad => ad.MethodInfo.Name == "Delete")
.ToArray();
Assert.Equal(2, actions.Length);
var action = Assert.Single(
actions,
a => a.RouteValues.Any(rc => rc.Key == "action" && rc.Value == "Delete"));
Assert.Equal(
new string[] { "PUT" },
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
action = Assert.Single(
actions,
a => a.RouteValues.Any(rc => rc.Key == "action" && string.IsNullOrEmpty(rc.Value)));
Assert.Equal(
new string[] { "PUT" },
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
}
// The method name is used to infer a verb, not the action name
[Fact]
public void GetActions_CreatesNamedAndUnnamedAction_VerbBasedOnMethodName()
{
// Arrange
var provider = CreateProvider();
// Act
var context = new ActionDescriptorProviderContext();
Invoke(provider, context);
var results = context.Results.Cast<ControllerActionDescriptor>();
// Assert
var controllerType = typeof(TestControllers.StoreController).GetTypeInfo();
var actions = results
.Where(ad => ad.ControllerTypeInfo == controllerType)
.Where(ad => ad.MethodInfo.Name == "Options")
.ToArray();
Assert.Equal(2, actions.Length);
var action = Assert.Single(
actions,
a => a.RouteValues.Any(rc => rc.Key == "action" && rc.Value == "GetOptions"));
Assert.Equal(
new string[] { "OPTIONS" },
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
action = Assert.Single(
actions,
a => a.RouteValues.Any(rc => rc.Key == "action" && string.IsNullOrEmpty(rc.Value)));
Assert.Equal(
new string[] { "OPTIONS" },
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
}
[Fact]
public void GetActions_AllWebApiActionsAreOverloaded()
{
// Arrange
var provider = CreateProvider();
// Act
var context = new ActionDescriptorProviderContext();
Invoke(provider, context);
var results = context.Results.Cast<ControllerActionDescriptor>();
// Assert
var controllerType = typeof(TestControllers.StoreController).GetTypeInfo();
var actions = results
.Where(ad => ad.ControllerTypeInfo == controllerType)
.ToArray();
Assert.NotEmpty(actions);
foreach (var action in actions)
{
Assert.Single(action.ActionConstraints, c => c is OverloadActionConstraint);
}
}
[Fact]
public void GetActions_AllWebApiActionsAreInWebApiArea()
{
// Arrange
var provider = CreateProvider();
// Act
var context = new ActionDescriptorProviderContext();
Invoke(provider, context);
var results = context.Results.Cast<ControllerActionDescriptor>();
// Assert
var controllerType = typeof(TestControllers.StoreController).GetTypeInfo();
var actions = results
.Where(ad => ad.ControllerTypeInfo == controllerType)
.ToArray();
Assert.NotEmpty(actions);
foreach (var action in actions)
{
Assert.Single(action.RouteValues, c => c.Key == "area" && c.Value== "api");
}
}
[Fact]
public void GetActions_Parameters_SimpleTypeFromUriByDefault()
{
// Arrange
var provider = CreateProvider();
// Act
var context = new ActionDescriptorProviderContext();
Invoke(provider, context);
var results = context.Results.Cast<ControllerActionDescriptor>();
// Assert
var controllerType = typeof(TestControllers.EmployeesController).GetTypeInfo();
var actions = results
.Where(ad => ad.ControllerTypeInfo == controllerType)
.Where(ad => ad.ActionName == "Get")
.ToArray();
Assert.NotEmpty(actions);
foreach (var action in actions)
{
var parameter = Assert.Single(action.Parameters);
Assert.Equal((new FromUriAttribute()).BindingSource, parameter.BindingInfo.BindingSource);
var optionalParameters = (HashSet<string>)action.Properties["OptionalParameters"];
Assert.DoesNotContain(parameter.Name, optionalParameters);
}
}
[Fact]
public void GetActions_Parameters_ComplexTypeFromBodyByDefault()
{
// Arrange
var provider = CreateProvider();
// Act
var context = new ActionDescriptorProviderContext();
Invoke(provider, context);
var results = context.Results.Cast<ControllerActionDescriptor>();
// Assert
var controllerType = typeof(TestControllers.EmployeesController).GetTypeInfo();
var actions = results
.Where(ad => ad.ControllerTypeInfo == controllerType)
.Where(ad => ad.ActionName == "Put")
.ToArray();
Assert.NotEmpty(actions);
foreach (var action in actions)
{
var parameter = Assert.Single(action.Parameters);
Assert.Equal(BindingSource.Body, parameter.BindingInfo.BindingSource);
}
}
[Fact]
public void GetActions_Parameters_WithBindingSource()
{
// Arrange
var provider = CreateProvider();
// Act
var context = new ActionDescriptorProviderContext();
Invoke(provider, context);
var results = context.Results.Cast<ControllerActionDescriptor>();
// Assert
var controllerType = typeof(TestControllers.EmployeesController).GetTypeInfo();
var actions = results
.Where(ad => ad.ControllerTypeInfo == controllerType)
.Where(ad => ad.ActionName == "Post")
.ToArray();
Assert.NotEmpty(actions);
foreach (var action in actions)
{
var parameter = Assert.Single(action.Parameters);
Assert.Null(parameter.BindingInfo.BindingSource);
}
}
[Theory]
[InlineData(nameof(TestControllers.EventsController.GetWithId))]
[InlineData(nameof(TestControllers.EventsController.GetWithEmployee))]
public void GetActions_Parameters_ImplicitOptional(string name)
{
// Arrange
var provider = CreateProvider();
// Act
var context = new ActionDescriptorProviderContext();
Invoke(provider, context);
var results = context.Results.Cast<ControllerActionDescriptor>();
// Assert
var controllerType = typeof(TestControllers.EventsController).GetTypeInfo();
var actions = results
.Where(ad => ad.ControllerTypeInfo == controllerType)
.Where(ad => ad.ActionName == name)
.ToArray();
Assert.NotEmpty(actions);
foreach (var action in actions)
{
var parameter = Assert.Single(action.Parameters);
Assert.Equal((new FromUriAttribute()).BindingSource, parameter.BindingInfo.BindingSource);
var optionalParameters = (HashSet<string>)action.Properties["OptionalParameters"];
Assert.Contains(parameter.Name, optionalParameters);
}
}
private ControllerActionDescriptorProvider CreateProvider()
{
var manager = GetApplicationManager(GetType().GetTypeInfo().Assembly.DefinedTypes.ToArray());
var options = new MvcOptions();
var setup = new WebApiCompatShimOptionsSetup();
setup.Configure(options);
var optionsAccessor = new Mock<IOptions<MvcOptions>>();
optionsAccessor
.SetupGet(o => o.Value)
.Returns(options);
var authorizationOptionsAccessor = new Mock<IOptions<AuthorizationOptions>>();
authorizationOptionsAccessor
.SetupGet(o => o.Value)
.Returns(new AuthorizationOptions());
var modelProvider = new DefaultApplicationModelProvider(optionsAccessor.Object);
var provider = new ControllerActionDescriptorProvider(
manager,
new[] { modelProvider },
optionsAccessor.Object);
return provider;
}
private void Invoke(ControllerActionDescriptorProvider provider, ActionDescriptorProviderContext context)
{
provider.OnProvidersExecuting(context);
provider.OnProvidersExecuted(context);
}
private static ApplicationPartManager GetApplicationManager(params TypeInfo[] controllerTypes)
{
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new TestPart(controllerTypes));
manager.FeatureProviders.Add(new TestProvider());
manager.FeatureProviders.Add(new NamespaceFilteredControllersFeatureProvider());
return manager;
}
private class TestPart : ApplicationPart, IApplicationPartTypeProvider
{
public TestPart(IEnumerable<TypeInfo> types)
{
Types = types;
}
public override string Name => "Test";
public IEnumerable<TypeInfo> Types { get; }
}
private class TestProvider : IApplicationFeatureProvider<ControllerFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
foreach (var type in parts.OfType<IApplicationPartTypeProvider>().SelectMany(t => t.Types))
{
feature.Controllers.Add(type);
}
}
}
private class NamespaceFilteredControllersFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
var controllers = feature.Controllers.ToList();
foreach (var controller in controllers)
{
if (controller.Namespace != "System.Web.Http.TestControllers")
{
feature.Controllers.Remove(controller);
}
}
}
}
}
}
// 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;
}
}
// Not a controller, because there's no controller suffix
public class Blog : ApiController
{
public IActionResult GetBlogPosts()
{
return null;
}
}
public class StoreController : ApiController
{
public IActionResult GetAll()
{
return null;
}
public IActionResult Edit(int id)
{
return null;
}
[HttpPut]
public IActionResult Delete(int id)
{
return null;
}
[ActionName("GetOptions")]
public IActionResult Options()
{
return null;
}
}
public class EmployeesController : ApiController
{
public IActionResult Get(int id)
{
return null;
}
public IActionResult Put(Employee employee)
{
return null;
}
public IActionResult Post([ModelBinder] Employee employee)
{
return null;
}
}
public class Employee
{
}
public class EventsController : ApiController
{
public IActionResult GetWithId(int id = 0)
{
return null;
}
public IActionResult GetWithEmployee([FromUri] Employee e = null)
{
return null;
}
}
}