// 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.Collections.Generic; using System.ComponentModel.Design; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.Routing; using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.NestedProviders; using Moq; using Xunit; namespace Microsoft.AspNet.Mvc { public class DefaultActionDiscoveryConventionsActionSelectionTests { [Theory] [InlineData("GET")] [InlineData("POST")] public async Task ActionSelection_IndexSelectedByDefaultInAbsenceOfVerbOnlyMethod(string verb) { // Arrange var routeContext = new RouteContext(GetHttpContext(verb)); routeContext.RouteData.Values = new Dictionary { { "controller", "RpcOnly" } }; // Act var result = await InvokeActionSelector(routeContext); // Assert Assert.Equal("Index", result.Name); } [Theory] [InlineData("GET")] [InlineData("POST")] public async Task ActionSelection_PrefersVerbOnlyMethodOverIndex(string verb) { // Arrange var routeContext = new RouteContext(GetHttpContext(verb)); routeContext.RouteData.Values = new Dictionary { { "controller", "MixedRpcAndRest" } }; // Act var result = await InvokeActionSelector(routeContext); // Assert Assert.Equal(verb, result.Name, StringComparer.OrdinalIgnoreCase); } [Theory] [InlineData("PUT")] [InlineData("DELETE")] [InlineData("PATCH")] public async Task ActionSelection_IndexNotSelectedByDefaultExceptGetAndPostVerbs(string verb) { // Arrange var routeContext = new RouteContext(GetHttpContext(verb)); routeContext.RouteData.Values = new Dictionary { { "controller", "RpcOnly" } }; // Act var result = await InvokeActionSelector(routeContext); // Assert Assert.Equal(null, result); } [Theory] [InlineData("HEAD")] [InlineData("OPTIONS")] public async Task ActionSelection_NoConventionBasedRoutingForHeadAndOptions(string verb) { // Arrange var routeContext = new RouteContext(GetHttpContext(verb)); routeContext.RouteData.Values = new Dictionary { { "controller", "MixedRpcAndRest" }, }; // Act var result = await InvokeActionSelector(routeContext); // Assert Assert.Equal(null, result); } [Theory] [InlineData("HEAD")] [InlineData("OPTIONS")] public async Task ActionSelection_ActionNameBasedRoutingForHeadAndOptions(string verb) { // Arrange var routeContext = new RouteContext(GetHttpContext(verb)); routeContext.RouteData.Values = new Dictionary { { "controller", "MixedRpcAndRest" }, { "action", verb }, }; // Act var result = await InvokeActionSelector(routeContext); // Assert Assert.Equal(verb, result.Name, StringComparer.OrdinalIgnoreCase); } [Fact] public async Task ActionSelection_ChangeDefaultConventionPicksCustomMethodForPost_DefaultMethodIsSelectedForGet() { // Arrange var routeContext = new RouteContext(GetHttpContext("GET")); routeContext.RouteData.Values = new Dictionary { { "controller", "RpcOnly" } }; // Act var result = await InvokeActionSelector(routeContext, new CustomActionConvention()); // Assert Assert.Equal("INDEX", result.Name, StringComparer.OrdinalIgnoreCase); } [Fact] public async Task ActionSelection_ChangeDefaultConventionPicksCustomMethodForPost_CutomMethodIsSelected() { // Arrange var routeContext = new RouteContext(GetHttpContext("POST")); routeContext.RouteData.Values = new Dictionary { { "controller", "RpcOnly" } }; // Act var result = await InvokeActionSelector(routeContext, new CustomActionConvention()); // Assert Assert.Equal("PostSomething", result.Name); } private async Task InvokeActionSelector(RouteContext context) { var controllerTypeInfos = typeof(DefaultActionDiscoveryConventionsActionSelectionTests) .GetNestedTypes(BindingFlags.NonPublic) .Select(ct => ct.GetTypeInfo()) .ToArray(); var conventions = new StaticActionDiscoveryConventions(controllerTypeInfos); return await InvokeActionSelector(context, conventions); } private async Task InvokeActionSelector(RouteContext context, DefaultActionDiscoveryConventions actionDiscoveryConventions) { var actionDescriptorProvider = GetActionDescriptorProvider(actionDiscoveryConventions); var descriptorProvider = new NestedProviderManager(new[] { actionDescriptorProvider }); var serviceContainer = new ServiceContainer(); serviceContainer.AddService(typeof(INestedProviderManager), descriptorProvider); var actionCollectionDescriptorProvider = new DefaultActionDescriptorsCollectionProvider(serviceContainer); var decisionTreeProvider = new ActionSelectorDecisionTreeProvider(actionCollectionDescriptorProvider); var bindingProvider = new Mock(); var defaultActionSelector = new DefaultActionSelector( actionCollectionDescriptorProvider, decisionTreeProvider, bindingProvider.Object, NullLoggerFactory.Instance); return await defaultActionSelector.SelectAsync(context); } private ReflectedActionDescriptorProvider GetActionDescriptorProvider(DefaultActionDiscoveryConventions actionDiscoveryConventions) { var assemblies = new Assembly[] { typeof(DefaultActionDiscoveryConventionsActionSelectionTests).GetTypeInfo().Assembly, }; var controllerAssemblyProvider = new Mock(); controllerAssemblyProvider.SetupGet(x => x.CandidateAssemblies).Returns(assemblies); return new ReflectedActionDescriptorProvider( controllerAssemblyProvider.Object, actionDiscoveryConventions, new TestGlobalFilterProvider(), new MockMvcOptionsAccessor(), Mock.Of()); } private static HttpContext GetHttpContext(string httpMethod) { var request = new Mock(); var headers = new Mock(); request.SetupGet(r => r.Headers).Returns(headers.Object); request.SetupGet(x => x.Method).Returns(httpMethod); var httpContext = new Mock(); httpContext.SetupGet(c => c.Request).Returns(request.Object); return httpContext.Object; } private class CustomActionConvention : DefaultActionDiscoveryConventions { public override bool IsController([NotNull]TypeInfo typeInfo) { return typeof(DefaultActionDiscoveryConventionsActionSelectionTests) .GetNestedTypes(BindingFlags.NonPublic) .Select(ct => ct.GetTypeInfo()) .Contains(typeInfo); } public override IEnumerable GetSupportedHttpMethods(MethodInfo methodInfo) { if (methodInfo.Name.Equals("PostSomething", StringComparison.OrdinalIgnoreCase)) { return new[] { "POST" }; } return null; } } private class MixedRpcAndRestController { public void Index() { } public void Get() { } public void Post() { } public void GetSomething() { } // This will be treated as an RPC method. public void Head() { } // This will be treated as an RPC method. public void Options() { } } private class RestOnlyController { public void Get() { } public void Put() { } public void Post() { } public void Delete() { } public void Patch() { } } private class RpcOnlyController { public void Index() { } public void GetSomething() { } public void PutSomething() { } public void PostSomething() { } public void DeleteSomething() { } public void PatchSomething() { } } private class AmbiguousController { public void Index(int i) { } public void Index(string s) { } } } } #endif