* Modify DefaultControllerTypeProvider to look at the object graph to

determine if any ancestor has the "Controller" suffix.

* Introduce NonControllerAttribute to opt out of Controller detection.

Fixes #1274
This commit is contained in:
Pranav K 2015-02-11 22:54:38 -08:00
parent 8a9d0d16f7
commit 5e69d90076
31 changed files with 833 additions and 95 deletions

32
Mvc.sln
View File

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14 # Visual Studio 14
VisualStudioVersion = 14.0.22606.0 VisualStudioVersion = 14.0.22604.0
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
EndProject EndProject
@ -140,6 +140,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RazorCompilerCacheWebSite",
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.PageExecutionInstrumentation", "src\Microsoft.AspNet.PageExecutionInstrumentation\Microsoft.AspNet.PageExecutionInstrumentation.kproj", "{4DA2D7C1-A7B6-4C01-B57D-89E6EA4609DE}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.PageExecutionInstrumentation", "src\Microsoft.AspNet.PageExecutionInstrumentation\Microsoft.AspNet.PageExecutionInstrumentation.kproj", "{4DA2D7C1-A7B6-4C01-B57D-89E6EA4609DE}"
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "UserClassLibrary", "test\WebSites\UserClassLibrary\UserClassLibrary.kproj", "{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ControllerDiscoveryConventionsWebSite", "test\WebSites\ControllerDiscoveryConventionsWebSite\ControllerDiscoveryConventionsWebSite.kproj", "{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -810,6 +814,30 @@ Global
{4DA2D7C1-A7B6-4C01-B57D-89E6EA4609DE}.Release|Mixed Platforms.Build.0 = Release|Any CPU {4DA2D7C1-A7B6-4C01-B57D-89E6EA4609DE}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{4DA2D7C1-A7B6-4C01-B57D-89E6EA4609DE}.Release|x86.ActiveCfg = Release|Any CPU {4DA2D7C1-A7B6-4C01-B57D-89E6EA4609DE}.Release|x86.ActiveCfg = Release|Any CPU
{4DA2D7C1-A7B6-4C01-B57D-89E6EA4609DE}.Release|x86.Build.0 = Release|Any CPU {4DA2D7C1-A7B6-4C01-B57D-89E6EA4609DE}.Release|x86.Build.0 = Release|Any CPU
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Debug|x86.ActiveCfg = Debug|Any CPU
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Debug|x86.Build.0 = Debug|Any CPU
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Release|Any CPU.Build.0 = Release|Any CPU
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Release|x86.ActiveCfg = Release|Any CPU
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C}.Release|x86.Build.0 = Release|Any CPU
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Debug|x86.ActiveCfg = Debug|Any CPU
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Debug|x86.Build.0 = Debug|Any CPU
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Release|Any CPU.Build.0 = Release|Any CPU
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Release|x86.ActiveCfg = Release|Any CPU
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -878,5 +906,7 @@ Global
{551DC89E-2A13-4CF2-83D7-1ADD802443D5} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {551DC89E-2A13-4CF2-83D7-1ADD802443D5} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{42C5D417-4060-48F4-BB28-E9E179007779} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {42C5D417-4060-48F4-BB28-E9E179007779} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{4DA2D7C1-A7B6-4C01-B57D-89E6EA4609DE} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {4DA2D7C1-A7B6-4C01-B57D-89E6EA4609DE} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{C651F432-4EBE-41A6-BAD2-3E07CCBA209C} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@ -4,7 +4,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Security.Principal; using System.Security.Claims;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
@ -12,16 +12,21 @@ using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
public class Controller : IActionFilter, IAsyncActionFilter, IDisposable /// <summary>
/// Base class for an MVC controller.
/// </summary>
public abstract class Controller : IActionFilter, IAsyncActionFilter, IDisposable
{ {
private DynamicViewData _viewBag; private DynamicViewData _viewBag;
private ViewDataDictionary _viewData; private ViewDataDictionary _viewData;
private ActionContext _actionContext; private ActionContext _actionContext;
/// <summary>
/// Gets the request-specific <see cref="IServiceProvider"/>.
/// </summary>
public IServiceProvider Resolver public IServiceProvider Resolver
{ {
get get
@ -30,6 +35,9 @@ namespace Microsoft.AspNet.Mvc
} }
} }
/// <summary>
/// Gets the <see cref="HttpContext"/> for the executing action.
/// </summary>
public HttpContext Context public HttpContext Context
{ {
get get
@ -38,6 +46,9 @@ namespace Microsoft.AspNet.Mvc
} }
} }
/// <summary>
/// Gets the <see cref="HttpRequest"/> for the executing action.
/// </summary>
public HttpRequest Request public HttpRequest Request
{ {
get get
@ -46,6 +57,9 @@ namespace Microsoft.AspNet.Mvc
} }
} }
/// <summary>
/// Gets the <see cref="HttpResponse"/> for the executing action.
/// </summary>
public HttpResponse Response public HttpResponse Response
{ {
get get
@ -54,14 +68,20 @@ namespace Microsoft.AspNet.Mvc
} }
} }
/// <summary>
/// Gets the <see cref="AspNet.Routing.RouteData"/> for the executing action.
/// </summary>
public RouteData RouteData public RouteData RouteData
{ {
get get
{ {
return ActionContext?.RouteData; return ActionContext?.RouteData;
} }
} }
/// <summary>
/// Gets the <see cref="ModelStateDictionary"/> that contains the state of the model and of model-binding validation.
/// </summary>
public ModelStateDictionary ModelState public ModelStateDictionary ModelState
{ {
get get
@ -97,16 +117,28 @@ namespace Microsoft.AspNet.Mvc
} }
} }
/// <summary>
/// Gets or sets the <see cref="ActionBindingContext"/>.
/// </summary>
[Activate] [Activate]
public ActionBindingContext BindingContext { get; set; } public ActionBindingContext BindingContext { get; set; }
/// <summary>
/// Gets or sets the <see cref="IModelMetadataProvider"/>.
/// </summary>
[FromServices] [FromServices]
public IModelMetadataProvider MetadataProvider { get; set; } public IModelMetadataProvider MetadataProvider { get; set; }
/// <summary>
/// Gets or sets the <see cref="IUrlHelper"/>.
/// </summary>
[FromServices] [FromServices]
public IUrlHelper Url { get; set; } public IUrlHelper Url { get; set; }
public IPrincipal User /// <summary>
/// Gets or sets the <see cref="ClaimsPrincipal"/> for user associated with the executing action.
/// </summary>
public ClaimsPrincipal User
{ {
get get
{ {
@ -138,7 +170,7 @@ namespace Microsoft.AspNet.Mvc
return _viewData; return _viewData;
} }
set set
{ {
if (value == null) if (value == null)
{ {
throw throw
@ -149,6 +181,9 @@ namespace Microsoft.AspNet.Mvc
} }
} }
/// <summary>
/// Gets the dynamic view bag.
/// </summary>
public dynamic ViewBag public dynamic ViewBag
{ {
get get
@ -1111,12 +1146,18 @@ namespace Microsoft.AspNet.Mvc
return ModelState.IsValid; return ModelState.IsValid;
} }
/// <inheritdoc />
public void Dispose() public void Dispose()
{ {
Dispose(disposing: true); Dispose(disposing: true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
/// <summary>
/// Releases all resources currently used by this <see cref="Controller"/> instance.
/// </summary>
/// <param name="disposing"><c>true</c> if this method is being invoked by the <see cref="Dispose"/> method,
/// otherwise <c>false</c>.</param>
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
} }

View File

@ -12,10 +12,21 @@ namespace Microsoft.AspNet.Mvc
{ {
public class DefaultAssemblyProvider : IAssemblyProvider public class DefaultAssemblyProvider : IAssemblyProvider
{ {
private readonly ILibraryManager _libraryManager;
public DefaultAssemblyProvider(ILibraryManager libraryManager)
{
_libraryManager = libraryManager;
}
/// <summary> /// <summary>
/// Gets the set of assembly names that are used as root for discovery of /// Gets the set of assembly names that are used as root for discovery of
/// MVC controllers, view components and views. /// MVC controllers, view components and views.
/// </summary> /// </summary>
// DefaultControllerTypeProvider uses CandidateAssemblies to determine if the base type of a POCO controller
// lives in an assembly that references MVC. CandidateAssemblies excludes all assemblies from the
// ReferenceAssemblies set. Consequently adding WebApiCompatShim to this set would cause the ApiController to
/// fail this test.
protected virtual HashSet<string> ReferenceAssemblies { get; } = new HashSet<string>(StringComparer.Ordinal) protected virtual HashSet<string> ReferenceAssemblies { get; } = new HashSet<string>(StringComparer.Ordinal)
{ {
"Microsoft.AspNet.Mvc", "Microsoft.AspNet.Mvc",
@ -23,16 +34,8 @@ namespace Microsoft.AspNet.Mvc
"Microsoft.AspNet.Mvc.ModelBinding", "Microsoft.AspNet.Mvc.ModelBinding",
"Microsoft.AspNet.Mvc.Razor", "Microsoft.AspNet.Mvc.Razor",
"Microsoft.AspNet.Mvc.Razor.Host", "Microsoft.AspNet.Mvc.Razor.Host",
"Microsoft.AspNet.Mvc.Rendering",
}; };
private readonly ILibraryManager _libraryManager;
public DefaultAssemblyProvider(ILibraryManager libraryManager)
{
_libraryManager = libraryManager;
}
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<Assembly> CandidateAssemblies public IEnumerable<Assembly> CandidateAssemblies
{ {

View File

@ -16,6 +16,9 @@ namespace Microsoft.AspNet.Mvc
/// </summary> /// </summary>
public class DefaultControllerTypeProvider : IControllerTypeProvider public class DefaultControllerTypeProvider : IControllerTypeProvider
{ {
private const string ControllerTypeName = nameof(Controller);
private static readonly TypeInfo ControllerTypeInfo = typeof(Controller).GetTypeInfo();
private static readonly TypeInfo ObjectTypeInfo = typeof(object).GetTypeInfo();
private readonly IAssemblyProvider _assemblyProvider; private readonly IAssemblyProvider _assemblyProvider;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -37,17 +40,17 @@ namespace Microsoft.AspNet.Mvc
{ {
get get
{ {
var assemblies = _assemblyProvider.CandidateAssemblies; var candidateAssemblies = new HashSet<Assembly>(_assemblyProvider.CandidateAssemblies);
if (_logger.IsEnabled(LogLevel.Verbose)) if (_logger.IsEnabled(LogLevel.Verbose))
{ {
foreach (var assembly in assemblies) foreach (var assembly in candidateAssemblies)
{ {
_logger.WriteVerbose(new AssemblyValues(assembly)); _logger.WriteVerbose(new AssemblyValues(assembly));
} }
} }
var types = assemblies.SelectMany(a => a.DefinedTypes); var types = candidateAssemblies.SelectMany(a => a.DefinedTypes);
return types.Where(IsController); return types.Where(typeInfo => IsController(typeInfo, candidateAssemblies));
} }
} }
@ -55,8 +58,10 @@ namespace Microsoft.AspNet.Mvc
/// Returns <c>true</c> if the <paramref name="typeInfo"/> is a controller. Otherwise <c>false</c>. /// Returns <c>true</c> if the <paramref name="typeInfo"/> is a controller. Otherwise <c>false</c>.
/// </summary> /// </summary>
/// <param name="typeInfo">The <see cref="TypeInfo"/>.</param> /// <param name="typeInfo">The <see cref="TypeInfo"/>.</param>
/// <param name="candidateAssemblies">The set of candidate assemblies.</param>
/// <returns><c>true</c> if the <paramref name="typeInfo"/> is a controller. Otherwise <c>false</c>.</returns> /// <returns><c>true</c> if the <paramref name="typeInfo"/> is a controller. Otherwise <c>false</c>.</returns>
protected internal virtual bool IsController([NotNull] TypeInfo typeInfo) protected internal virtual bool IsController([NotNull] TypeInfo typeInfo,
[NotNull] ISet<Assembly> candidateAssemblies)
{ {
if (!typeInfo.IsClass) if (!typeInfo.IsClass)
{ {
@ -76,17 +81,47 @@ namespace Microsoft.AspNet.Mvc
{ {
return false; return false;
} }
if (typeInfo.Name.Equals("Controller", StringComparison.OrdinalIgnoreCase)) if (!typeInfo.Name.EndsWith(ControllerTypeName, StringComparison.OrdinalIgnoreCase) &&
!DerivesFromController(typeInfo, candidateAssemblies))
{ {
return false; return false;
} }
if (!typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) && if (typeInfo.IsDefined(typeof(NonControllerAttribute)))
!typeof(Controller).GetTypeInfo().IsAssignableFrom(typeInfo))
{ {
return false; return false;
} }
return true; return true;
} }
private bool DerivesFromController(TypeInfo typeInfo, ISet<Assembly> candidateAssemblies)
{
// A type is a controller if it derives from a type that is either named "Controller" or has the suffix
// "Controller". We'll optimize the most common case of types deriving from the Mvc Controller type and
// walk up the object graph if that's not the case.
if (ControllerTypeInfo.IsAssignableFrom(typeInfo))
{
return true;
}
while (typeInfo != ObjectTypeInfo)
{
var baseTypeInfo = typeInfo.BaseType.GetTypeInfo();
// A base type will be treated as a controller if
// a) it ends in the term "Controller" and
// b) it's assembly is one of the candidate assemblies we're considering. This ensures that the assembly
// the base type is declared in references Mvc.
if (baseTypeInfo.Name.EndsWith(ControllerTypeName, StringComparison.Ordinal) &&
candidateAssemblies.Contains(baseTypeInfo.Assembly))
{
return true;
}
typeInfo = baseTypeInfo;
}
return false;
}
} }
} }

View File

@ -0,0 +1,16 @@
// 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;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Indicates that the type and any derived types that this attribute is applied to
/// is not considered a controller by the default controller discovery mechanism.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class NonControllerAttribute : Attribute
{
}
}

View File

@ -40,7 +40,7 @@ namespace Microsoft.AspNet.Mvc.Test
{ {
// Arrange // Arrange
var metadataProvider = new DataAnnotationsModelMetadataProvider(); var metadataProvider = new DataAnnotationsModelMetadataProvider();
var controller = new Controller(); var controller = new TestableController();
var originalViewData = controller.ViewData = new ViewDataDictionary<object>(metadataProvider); var originalViewData = controller.ViewData = new ViewDataDictionary<object>(metadataProvider);
var replacementViewData = new ViewDataDictionary<object>(metadataProvider); var replacementViewData = new ViewDataDictionary<object>(metadataProvider);
@ -61,7 +61,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Redirect_WithParameterUrl_SetsRedirectResultSameUrl() public void Redirect_WithParameterUrl_SetsRedirectResultSameUrl()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var url = "/test/url"; var url = "/test/url";
// Act // Act
@ -77,7 +77,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void RedirectPermanent_WithParameterUrl_SetsRedirectResultPermanentAndSameUrl() public void RedirectPermanent_WithParameterUrl_SetsRedirectResultPermanentAndSameUrl()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var url = "/test/url"; var url = "/test/url";
// Act // Act
@ -95,7 +95,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Redirect_WithParameter_NullOrEmptyUrl_Throws(string url) public void Redirect_WithParameter_NullOrEmptyUrl_Throws(string url)
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act & Assert // Act & Assert
ExceptionAssert.ThrowsArgumentNullOrEmpty( ExceptionAssert.ThrowsArgumentNullOrEmpty(
@ -108,7 +108,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void RedirectPermanent_WithParameter_NullOrEmptyUrl_Throws(string url) public void RedirectPermanent_WithParameter_NullOrEmptyUrl_Throws(string url)
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act & Assert // Act & Assert
ExceptionAssert.ThrowsArgumentNullOrEmpty( ExceptionAssert.ThrowsArgumentNullOrEmpty(
@ -119,7 +119,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void RedirectToAction_WithParameterActionName_SetsResultActionName() public void RedirectToAction_WithParameterActionName_SetsResultActionName()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var resultTemporary = controller.RedirectToAction("SampleAction"); var resultTemporary = controller.RedirectToAction("SampleAction");
@ -134,7 +134,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void RedirectToActionPermanent_WithParameterActionName_SetsResultActionNameAndPermanent() public void RedirectToActionPermanent_WithParameterActionName_SetsResultActionNameAndPermanent()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var resultPermanent = controller.RedirectToActionPermanent("SampleAction"); var resultPermanent = controller.RedirectToActionPermanent("SampleAction");
@ -152,7 +152,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void RedirectToAction_WithParameterActionAndControllerName_SetsEqualNames(string controllerName) public void RedirectToAction_WithParameterActionAndControllerName_SetsEqualNames(string controllerName)
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var resultTemporary = controller.RedirectToAction("SampleAction", controllerName); var resultTemporary = controller.RedirectToAction("SampleAction", controllerName);
@ -172,7 +172,7 @@ namespace Microsoft.AspNet.Mvc.Test
string controllerName) string controllerName)
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var resultPermanent = controller.RedirectToActionPermanent("SampleAction", controllerName); var resultPermanent = controller.RedirectToActionPermanent("SampleAction", controllerName);
@ -191,7 +191,7 @@ namespace Microsoft.AspNet.Mvc.Test
IEnumerable<KeyValuePair<string, object>> expected) IEnumerable<KeyValuePair<string, object>> expected)
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var resultTemporary = controller.RedirectToAction("SampleAction", "SampleController", routeValues); var resultTemporary = controller.RedirectToAction("SampleAction", "SampleController", routeValues);
@ -211,7 +211,7 @@ namespace Microsoft.AspNet.Mvc.Test
IEnumerable<KeyValuePair<string, object>> expected) IEnumerable<KeyValuePair<string, object>> expected)
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var resultPermanent = controller.RedirectToActionPermanent( var resultPermanent = controller.RedirectToActionPermanent(
@ -234,7 +234,7 @@ namespace Microsoft.AspNet.Mvc.Test
IEnumerable<KeyValuePair<string, object>> expected) IEnumerable<KeyValuePair<string, object>> expected)
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var resultTemporary = controller.RedirectToAction(actionName: null, routeValues: routeValues); var resultTemporary = controller.RedirectToAction(actionName: null, routeValues: routeValues);
@ -253,7 +253,7 @@ namespace Microsoft.AspNet.Mvc.Test
IEnumerable<KeyValuePair<string, object>> expected) IEnumerable<KeyValuePair<string, object>> expected)
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var resultPermanent = controller.RedirectToActionPermanent(null, routeValues); var resultPermanent = controller.RedirectToActionPermanent(null, routeValues);
@ -272,7 +272,7 @@ namespace Microsoft.AspNet.Mvc.Test
IEnumerable<KeyValuePair<string, object>> expected) IEnumerable<KeyValuePair<string, object>> expected)
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var resultTemporary = controller.RedirectToRoute(routeValues); var resultTemporary = controller.RedirectToRoute(routeValues);
@ -290,7 +290,7 @@ namespace Microsoft.AspNet.Mvc.Test
IEnumerable<KeyValuePair<string, object>> expected) IEnumerable<KeyValuePair<string, object>> expected)
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var resultPermanent = controller.RedirectToRoutePermanent(routeValues); var resultPermanent = controller.RedirectToRoutePermanent(routeValues);
@ -305,7 +305,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void RedirectToRoute_WithParameterRouteName_SetsResultSameRouteName() public void RedirectToRoute_WithParameterRouteName_SetsResultSameRouteName()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var routeName = "CustomRouteName"; var routeName = "CustomRouteName";
// Act // Act
@ -321,7 +321,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void RedirectToRoutePermanent_WithParameterRouteName_SetsResultSameRouteNameAndPermanent() public void RedirectToRoutePermanent_WithParameterRouteName_SetsResultSameRouteNameAndPermanent()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var routeName = "CustomRouteName"; var routeName = "CustomRouteName";
// Act // Act
@ -340,7 +340,7 @@ namespace Microsoft.AspNet.Mvc.Test
IEnumerable<KeyValuePair<string, object>> expected) IEnumerable<KeyValuePair<string, object>> expected)
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var routeName = "CustomRouteName"; var routeName = "CustomRouteName";
// Act // Act
@ -360,7 +360,7 @@ namespace Microsoft.AspNet.Mvc.Test
IEnumerable<KeyValuePair<string, object>> expected) IEnumerable<KeyValuePair<string, object>> expected)
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var routeName = "CustomRouteName"; var routeName = "CustomRouteName";
// Act // Act
@ -377,7 +377,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Created_WithStringParameter_SetsCreatedLocation() public void Created_WithStringParameter_SetsCreatedLocation()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var uri = "http://test/url"; var uri = "http://test/url";
// Act // Act
@ -393,7 +393,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Created_WithAbsoluteUriParameter_SetsCreatedLocation() public void Created_WithAbsoluteUriParameter_SetsCreatedLocation()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var uri = new Uri("http://test/url"); var uri = new Uri("http://test/url");
// Act // Act
@ -409,7 +409,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Created_WithRelativeUriParameter_SetsCreatedLocation() public void Created_WithRelativeUriParameter_SetsCreatedLocation()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var uri = new Uri("/test/url", UriKind.Relative); var uri = new Uri("/test/url", UriKind.Relative);
// Act // Act
@ -425,7 +425,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void CreatedAtAction_WithParameterActionName_SetsResultActionName() public void CreatedAtAction_WithParameterActionName_SetsResultActionName()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var result = controller.CreatedAtAction("SampleAction", null); var result = controller.CreatedAtAction("SampleAction", null);
@ -444,7 +444,7 @@ namespace Microsoft.AspNet.Mvc.Test
string controllerName) string controllerName)
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var result = controller.CreatedAtAction("SampleAction", controllerName, null, null); var result = controller.CreatedAtAction("SampleAction", controllerName, null, null);
@ -460,7 +460,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void CreatedAtAction_WithActionControllerRouteValues_SetsSameValues() public void CreatedAtAction_WithActionControllerRouteValues_SetsSameValues()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var expected = new Dictionary<string, object> var expected = new Dictionary<string, object>
{ {
{ "test", "case" }, { "test", "case" },
@ -485,7 +485,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void CreatedAtRoute_WithParameterRouteName_SetsResultSameRouteName() public void CreatedAtRoute_WithParameterRouteName_SetsResultSameRouteName()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var routeName = "SampleRoute"; var routeName = "SampleRoute";
// Act // Act
@ -500,7 +500,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void CreatedAtRoute_WithParameterRouteValues_SetsResultSameRouteValues() public void CreatedAtRoute_WithParameterRouteValues_SetsResultSameRouteValues()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var expected = new Dictionary<string, object> var expected = new Dictionary<string, object>
{ {
{ "test", "case" }, { "test", "case" },
@ -520,7 +520,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void CreatedAtRoute_WithParameterRouteNameAndValues_SetsResultSameProperties() public void CreatedAtRoute_WithParameterRouteNameAndValues_SetsResultSameProperties()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var routeName = "SampleRoute"; var routeName = "SampleRoute";
var expected = new Dictionary<string, object> var expected = new Dictionary<string, object>
{ {
@ -542,7 +542,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void File_WithContents() public void File_WithContents()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var fileContents = new byte[0]; var fileContents = new byte[0];
// Act // Act
@ -559,7 +559,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void File_WithContentsAndFileDownloadName() public void File_WithContentsAndFileDownloadName()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var fileContents = new byte[0]; var fileContents = new byte[0];
// Act // Act
@ -576,7 +576,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void File_WithPath() public void File_WithPath()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var path = Path.GetFullPath("somepath"); var path = Path.GetFullPath("somepath");
// Act // Act
@ -593,7 +593,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void File_WithPathAndFileDownloadName() public void File_WithPathAndFileDownloadName()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var path = Path.GetFullPath("somepath"); var path = Path.GetFullPath("somepath");
// Act // Act
@ -610,7 +610,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void File_WithStream() public void File_WithStream()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var fileStream = Stream.Null; var fileStream = Stream.Null;
// Act // Act
@ -627,7 +627,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void File_WithStreamAndFileDownloadName() public void File_WithStreamAndFileDownloadName()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var fileStream = Stream.Null; var fileStream = Stream.Null;
// Act // Act
@ -645,7 +645,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void HttpNotFound_SetsStatusCode() public void HttpNotFound_SetsStatusCode()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var result = controller.HttpNotFound(); var result = controller.HttpNotFound();
@ -659,7 +659,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void BadRequest_SetsStatusCode() public void BadRequest_SetsStatusCode()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var result = controller.HttpBadRequest(); var result = controller.HttpBadRequest();
@ -673,7 +673,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void BadRequest_SetsStatusCodeAndValue_Object() public void BadRequest_SetsStatusCodeAndValue_Object()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var obj = new object(); var obj = new object();
// Act // Act
@ -689,7 +689,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void BadRequest_SetsStatusCodeAndValue_ModelState() public void BadRequest_SetsStatusCodeAndValue_ModelState()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var result = controller.HttpBadRequest(new ModelStateDictionary()); var result = controller.HttpBadRequest(new ModelStateDictionary());
@ -713,7 +713,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Controller_View_WithoutParameter_SetsResultNullViewNameAndNullViewDataModel() public void Controller_View_WithoutParameter_SetsResultNullViewNameAndNullViewDataModel()
{ {
// Arrange // Arrange
var controller = new Controller() var controller = new TestableController()
{ {
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
}; };
@ -732,7 +732,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Controller_View_WithParameterViewName_SetsResultViewNameAndNullViewDataModel() public void Controller_View_WithParameterViewName_SetsResultViewNameAndNullViewDataModel()
{ {
// Arrange // Arrange
var controller = new Controller() var controller = new TestableController()
{ {
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
}; };
@ -751,7 +751,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Controller_View_WithParameterViewModel_SetsResultNullViewNameAndViewDataModel() public void Controller_View_WithParameterViewModel_SetsResultNullViewNameAndViewDataModel()
{ {
// Arrange // Arrange
var controller = new Controller() var controller = new TestableController()
{ {
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
}; };
@ -771,7 +771,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Controller_View_WithParameterViewNameAndViewModel_SetsResultViewNameAndViewDataModel() public void Controller_View_WithParameterViewNameAndViewModel_SetsResultViewNameAndViewDataModel()
{ {
// Arrange // Arrange
var controller = new Controller() var controller = new TestableController()
{ {
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
}; };
@ -791,7 +791,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Controller_Content_WithParameterContentString_SetsResultContent() public void Controller_Content_WithParameterContentString_SetsResultContent()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var actualContentResult = controller.Content("TestContent"); var actualContentResult = controller.Content("TestContent");
@ -807,7 +807,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Controller_Content_WithParameterContentStringAndContentType_SetsResultContentAndContentType() public void Controller_Content_WithParameterContentStringAndContentType_SetsResultContentAndContentType()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var actualContentResult = controller.Content("TestContent", "text/plain"); var actualContentResult = controller.Content("TestContent", "text/plain");
@ -823,7 +823,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Controller_Content_WithParameterContentAndTypeAndEncoding_SetsResultContentAndTypeAndEncoding() public void Controller_Content_WithParameterContentAndTypeAndEncoding_SetsResultContentAndTypeAndEncoding()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
// Act // Act
var actualContentResult = controller.Content("TestContent", "text/plain", Encoding.UTF8); var actualContentResult = controller.Content("TestContent", "text/plain", Encoding.UTF8);
@ -839,7 +839,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Controller_Json_WithParameterValue_SetsResultData() public void Controller_Json_WithParameterValue_SetsResultData()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var data = new object(); var data = new object();
// Act // Act
@ -1144,7 +1144,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void ControllerExposes_RequestServices() public void ControllerExposes_RequestServices()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var serviceProvider = Mock.Of<IServiceProvider>(); var serviceProvider = Mock.Of<IServiceProvider>();
var httpContext = new Mock<HttpContext>(); var httpContext = new Mock<HttpContext>();
@ -1166,7 +1166,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void ControllerExposes_Request() public void ControllerExposes_Request()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var request = Mock.Of<HttpRequest>(); var request = Mock.Of<HttpRequest>();
var httpContext = new Mock<HttpContext>(); var httpContext = new Mock<HttpContext>();
@ -1188,7 +1188,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void ControllerExposes_Response() public void ControllerExposes_Response()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var response = Mock.Of<HttpResponse>(); var response = Mock.Of<HttpResponse>();
var httpContext = new Mock<HttpContext>(); var httpContext = new Mock<HttpContext>();
@ -1210,7 +1210,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void ControllerExposes_RouteData() public void ControllerExposes_RouteData()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var routeData = Mock.Of<RouteData>(); var routeData = Mock.Of<RouteData>();
@ -1325,7 +1325,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void TryValidateModelEmptyBindingContextThrowsException() public void TryValidateModelEmptyBindingContextThrowsException()
{ {
// Arrange // Arrange
var controller = new Controller(); var controller = new TestableController();
var model = new TryValidateModelModel(); var model = new TryValidateModelModel();
// Act & Assert // Act & Assert
@ -1346,7 +1346,7 @@ namespace Microsoft.AspNet.Mvc.Test
ValueProvider = provider, ValueProvider = provider,
}; };
return new Controller() return new TestableController()
{ {
ActionContext = actionContext, ActionContext = actionContext,
BindingContext = bindingContext, BindingContext = bindingContext,
@ -1397,5 +1397,10 @@ namespace Microsoft.AspNet.Mvc.Test
DisposeCalled = true; DisposeCalled = true;
} }
} }
private class TestableController : Controller
{
}
} }
} }

View File

@ -490,8 +490,8 @@ namespace Microsoft.AspNet.Mvc
{ {
// Arrange // Arrange
var actionContext = new ActionContext(); var actionContext = new ActionContext();
var controller1 = new Controller(); var controller1 = new TestabilityController();
var controller2 = new Controller(); var controller2 = new TestabilityController();
// Act // Act
controller2.ActionContext = actionContext; controller2.ActionContext = actionContext;

View File

@ -1,7 +1,8 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq; using System;
using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using Microsoft.AspNet.Mvc.DefaultControllerTypeProviderControllers; using Microsoft.AspNet.Mvc.DefaultControllerTypeProviderControllers;
using Xunit; using Xunit;
@ -10,6 +11,11 @@ namespace Microsoft.AspNet.Mvc
{ {
public class DefaultControllerTypeProviderTest public class DefaultControllerTypeProviderTest
{ {
private static readonly ISet<Assembly> CandidateAssemblies = new HashSet<Assembly>
{
typeof(StoreController).GetTypeInfo().Assembly
};
[Fact] [Fact]
public void IsController_UserDefinedClass() public void IsController_UserDefinedClass()
{ {
@ -18,7 +24,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider(); var provider = GetControllerTypeProvider();
// Act // Act
var isController = provider.IsController(typeInfo); var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert // Assert
Assert.True(isController); Assert.True(isController);
@ -32,7 +38,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider(); var provider = GetControllerTypeProvider();
// Act // Act
var isController = provider.IsController(typeInfo); var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert // Assert
Assert.False(isController); Assert.False(isController);
@ -46,7 +52,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider(); var provider = GetControllerTypeProvider();
// Act // Act
var isController = provider.IsController(typeInfo); var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert // Assert
Assert.False(isController); Assert.False(isController);
@ -60,7 +66,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider(); var provider = GetControllerTypeProvider();
// Act // Act
var isController = provider.IsController(typeInfo); var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert // Assert
Assert.False(isController); Assert.False(isController);
@ -74,7 +80,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider(); var provider = GetControllerTypeProvider();
// Act // Act
var isController = provider.IsController(typeInfo); var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert // Assert
Assert.False(isController); Assert.False(isController);
@ -88,7 +94,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider(); var provider = GetControllerTypeProvider();
// Act // Act
var isController = provider.IsController(typeInfo); var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert // Assert
Assert.True(isController); Assert.True(isController);
@ -102,7 +108,21 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider(); var provider = GetControllerTypeProvider();
// Act // Act
var isController = provider.IsController(typeInfo); var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert
Assert.False(isController);
}
[Fact]
public void IsController_WithoutSuffixOrAncestorWithController()
{
// Arrange
var typeInfo = typeof(NoSuffixPoco).GetTypeInfo();
var provider = GetControllerTypeProvider();
// Act
var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert // Assert
Assert.False(isController); Assert.False(isController);
@ -116,7 +136,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider(); var provider = GetControllerTypeProvider();
// Act // Act
var isController = provider.IsController(typeInfo); var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert // Assert
Assert.True(isController); Assert.True(isController);
@ -130,7 +150,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider(); var provider = GetControllerTypeProvider();
// Act // Act
var isController = provider.IsController(typeInfo); var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert // Assert
Assert.True(isController); Assert.True(isController);
@ -144,7 +164,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider(); var provider = GetControllerTypeProvider();
// Act // Act
var isController = provider.IsController(typeInfo); var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert // Assert
Assert.True(isController); Assert.True(isController);
@ -158,12 +178,47 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider(); var provider = GetControllerTypeProvider();
// Act // Act
var isController = provider.IsController(typeInfo); var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert // Assert
Assert.True(isController); Assert.True(isController);
} }
[Theory]
[InlineData(typeof(DescendantLevel1))]
[InlineData(typeof(DescendantLevel2))]
public void IsController_ReturnsTrue_IfAncestorTypeNameHasControllerSuffix(Type type)
{
// Arrange
var provider = GetControllerTypeProvider();
// Act
var isController = provider.IsController(type.GetTypeInfo(), CandidateAssemblies);
// Assert
Assert.True(isController);
}
[Theory]
[InlineData(typeof(BaseNonControllerController))]
[InlineData(typeof(BaseNonControllerControllerChild))]
[InlineData(typeof(BasePocoNonControllerController))]
[InlineData(typeof(BasePocoNonControllerControllerChild))]
[InlineData(typeof(NonController))]
[InlineData(typeof(NonControllerChild))]
[InlineData(typeof(PersonModel))] // Verifies that POCO type hierarchies that don't derive from controller return false.
public void IsController_ReturnsFalse_IfTypeOrAncestorHasNonControllerAttribute(Type type)
{
// Arrange
var provider = GetControllerTypeProvider();
// Act
var isController = provider.IsController(type.GetTypeInfo(), CandidateAssemblies);
// Assert
Assert.False(isController);
}
private static DefaultControllerTypeProvider GetControllerTypeProvider() private static DefaultControllerTypeProvider GetControllerTypeProvider()
{ {
var assemblyProvider = new FixedSetAssemblyProvider(); var assemblyProvider = new FixedSetAssemblyProvider();
@ -191,7 +246,7 @@ namespace Microsoft.AspNet.Mvc.DefaultControllerTypeProviderControllers
{ {
} }
public class Controller public abstract class Controller
{ {
} }
@ -211,7 +266,86 @@ namespace Microsoft.AspNet.Mvc.DefaultControllerTypeProviderControllers
{ {
} }
public class NoSuffixPoco
{
}
public class PocoController public class PocoController
{ {
} }
public class CustomBaseController
{
}
public abstract class CustomAbstractBaseController
{
}
public class DescendantLevel1 : CustomBaseController
{
}
public class DescendantLevel2 : DescendantLevel1
{
}
public class AbstractChildWithoutSuffix : CustomAbstractBaseController
{
}
[NonController]
public class BasePocoNonControllerController
{
}
public class BasePocoNonControllerControllerChild : BasePocoNonControllerController
{
}
[NonController]
public class BaseNonControllerController : Controller
{
}
public class BaseNonControllerControllerChild : BaseNonControllerController
{
}
[NonController]
public class NonControllerChild : Controller
{
}
[NonController]
public class NonController : Controller
{
}
public class DataModelBase
{
}
public class EntityDataModel : DataModelBase
{
}
public class PersonModel : EntityDataModel
{
}
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
@ -18,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
{ {
public class BasicTests public class BasicTests
{ {
private readonly IServiceProvider _provider = TestHelper.CreateServices("BasicWebSite"); private readonly IServiceProvider _provider = TestHelper.CreateServices(nameof(BasicWebSite));
private readonly Action<IApplicationBuilder> _app = new Startup().Configure; private readonly Action<IApplicationBuilder> _app = new Startup().Configure;
// Some tests require comparing the actual response body against an expected response baseline // Some tests require comparing the actual response body against an expected response baseline
@ -275,5 +276,33 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var responseData = await response.Content.ReadAsStringAsync(); var responseData = await response.Content.ReadAsStringAsync();
Assert.Equal("This is a basic website.", responseData); Assert.Equal("This is a basic website.", responseData);
} }
[Fact]
public async Task TypesWithoutControllerSuffix_DerivingFromTypesWithControllerSuffix_CanBeAccessed()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = new HttpClient(server.CreateHandler(), false);
// Act
var response = await client.GetStringAsync("http://localhost/appointments");
// Assert
Assert.Equal("2 appointments available.", response);
}
[Fact]
public async Task TypesMarkedAsNonAction_AreInaccessible()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = new HttpClient(server.CreateHandler(), false);
// Act
var response = await client.GetAsync("http://localhost/SqlData/TruncateAllDbRecords");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
} }
} }

View File

@ -0,0 +1,143 @@
// 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using ControllerDiscoveryConventionsWebSite;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.TestHost;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Runtime;
using Xunit;
namespace Microsoft.AspNet.Mvc.FunctionalTests
{
public class ControllerDiscoveryConventionTests
{
private readonly IServiceProvider _provider = TestHelper.CreateServices(
nameof(ControllerDiscoveryConventionsWebSite));
private readonly Action<IApplicationBuilder> _app = new Startup().Configure;
[Fact]
public async Task AbstractControllers_AreSkipped()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
client.BaseAddress = new Uri("http://localhost/");
// Act
var response = await client.GetAsync("Abstract/GetValue");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task TypesDerivingFromControllerBaseTypesThatDoNotReferenceMvc_AreSkipped()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
client.BaseAddress = new Uri("http://localhost/");
// Act
var response = await client.GetAsync("SqlTransactionManager/GetValue");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task TypesMarkedWithNonController_AreSkipped()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
client.BaseAddress = new Uri("http://localhost/");
// Act
var response = await client.GetAsync("NonController/GetValue");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task PocoTypesWithControllerSuffix_AreDiscovered()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
client.BaseAddress = new Uri("http://localhost/");
// Act
var response = await client.GetAsync("Poco/GetValue");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("PocoController", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task TypesDerivingFromTypesWithControllerSuffix_AreDiscovered()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
client.BaseAddress = new Uri("http://localhost/");
// Act
var response = await client.GetAsync("ChildOfAbstract/GetValue");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("AbstractController", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task TypesDerivingFromApiController_AreDiscovered()
{
// Arrange
// TestHelper.CreateServices replaces the DefaultAssemblyProvider with a provider that
// limits the set of candidate assemblies to the executing application. For this test,
// we'll switch it back to using a filtered default assembly provider.
var services = HostingServices.Create(configuration: null);
services.AddTransient<IAssemblyProvider, FilteredDefaultAssemblyProvider>();
var serviceProvider = TestHelper.CreateServices(nameof(ControllerDiscoveryConventionsWebSite), services);
var server = TestServer.Create(serviceProvider, _app);
var client = server.CreateClient();
client.BaseAddress = new Uri("http://localhost/");
// Act
var response = await client.GetAsync("PersonApi/GetValue");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("PersonApi", await response.Content.ReadAsStringAsync());
}
private class FilteredDefaultAssemblyProvider : DefaultAssemblyProvider
{
public FilteredDefaultAssemblyProvider(ILibraryManager libraryManager)
: base(libraryManager)
{
}
protected override IEnumerable<ILibraryInformation> GetCandidateLibraries()
{
var libraries = base.GetCandidateLibraries();
// Filter out other WebSite projects
return libraries.Where(library => !library.Name.Contains("WebSite") ||
library.Name.Equals(nameof(ControllerDiscoveryConventionsWebSite), StringComparison.Ordinal));
}
}
}
}

View File

@ -49,6 +49,21 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal(expected, response); Assert.Equal(expected, response);
} }
[Fact]
public async Task TypesDerivingFromControllerPrefixedTypesAreRegistered()
{
// Arrange
var expected = "4";
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
// Act
var response = await client.GetStringAsync("http://localhost/inventory/");
// Assert
Assert.Equal(expected, response);
}
[Fact] [Fact]
public async Task TypesWithControllerSuffixAreRegistered() public async Task TypesWithControllerSuffixAreRegistered()
{ {
@ -84,9 +99,10 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
} }
[Theory] [Theory]
[InlineData("generic")] [InlineData("not-discovered/generic")]
[InlineData("nested")] [InlineData("not-discovered/nested")]
[InlineData("not-in-services")] [InlineData("not-discovered/not-in-services")]
[InlineData("ClientUIStub/GetClientContent/5")]
public async Task AddControllersFromServices_UsesControllerDiscoveryContentions(string action) public async Task AddControllersFromServices_UsesControllerDiscoveryContentions(string action)
{ {
// Arrange // Arrange
@ -94,7 +110,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var client = server.CreateClient(); var client = server.CreateClient();
// Act // Act
var response = await client.GetAsync("http://localhost/not-discovered/" + action); var response = await client.GetAsync("http://localhost/" + action);
// Assert // Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);

View File

@ -17,6 +17,7 @@
"BasicWebSite": "1.0.0", "BasicWebSite": "1.0.0",
"CompositeViewEngineWebSite": "1.0.0", "CompositeViewEngineWebSite": "1.0.0",
"ConnegWebSite": "1.0.0", "ConnegWebSite": "1.0.0",
"ControllerDiscoveryConventionsWebSite": "1.0.0",
"ControllersFromServicesWebSite": "1.0.0", "ControllersFromServicesWebSite": "1.0.0",
"CustomRouteWebSite": "1.0.0", "CustomRouteWebSite": "1.0.0",
"ErrorPageMiddlewareWebSite": "1.0.0", "ErrorPageMiddlewareWebSite": "1.0.0",

View File

@ -0,0 +1,10 @@
// 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.
namespace BasicWebSite.Controllers
{
public abstract class ApplicationBaseController
{
}
}

View File

@ -0,0 +1,20 @@
// 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 Microsoft.AspNet.Mvc;
namespace BasicWebSite.Controllers
{
[Route("/appointments")]
public class Appointments : ApplicationBaseController
{
[HttpGet("")]
public IActionResult Get()
{
return new ContentResult
{
Content = "2 appointments available."
};
}
}
}

View File

@ -0,0 +1,17 @@
// 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 Microsoft.AspNet.Mvc;
namespace BasicWebSite
{
[NonController]
public class SqlDataController
{
public int TruncateAllDbRecords()
{
// Return no. of tables truncated
return 7;
}
}
}

View File

@ -0,0 +1,13 @@
// 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.
namespace ControllerDiscoveryConventionsWebSite
{
public abstract class AbstractController
{
public string GetValue()
{
return nameof(AbstractController);
}
}
}

View File

@ -0,0 +1,10 @@
// 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.
namespace ControllerDiscoveryConventionsWebSite
{
public class ChildOfAbstract : AbstractController
{
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="__ToolsVersion__" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>a19022ef-9ba3-4349-94e4-f48e13e1c8ae</ProjectGuid>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>51163</DevelopmentServerPort>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,16 @@
// 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 Microsoft.AspNet.Mvc;
namespace ControllerDiscoveryConventionsWebSite
{
[NonController]
public class NonControllerController : Controller
{
public string GetValue()
{
return nameof(NonControllerController);
}
}
}

View File

@ -0,0 +1,15 @@
// 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.Web.Http;
namespace ControllerDiscoveryConventionsWebSite
{
public class PersonApi : ApiController
{
public string GetValue()
{
return nameof(PersonApi);
}
}
}

View File

@ -0,0 +1,13 @@
// 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.
namespace ControllerDiscoveryConventionsWebSite
{
public class PocoController
{
public string GetValue()
{
return nameof(PocoController);
}
}
}

View File

@ -0,0 +1,13 @@
// 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.
namespace UserClassLibrary
{
public class SqlTransactionManager : TransactionController
{
public string GetValue()
{
return nameof(SqlTransactionManager);
}
}
}

View File

@ -0,0 +1,26 @@
// 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 Microsoft.AspNet.Builder;
using Microsoft.Framework.DependencyInjection;
namespace ControllerDiscoveryConventionsWebSite
{
public class Startup
{
public void Configure(IApplicationBuilder app)
{
var configuration = app.GetTestConfiguration();
app.UseServices(services =>
{
services.AddMvc(configuration);
});
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller}/{action}/{id?}");
});
}
}
}

View File

@ -0,0 +1,21 @@
{
"commands": {
"web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001",
"kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000"
},
"dependencies": {
"UserClassLibrary": "1.0.0",
"Kestrel": "1.0.0-*",
"Microsoft.AspNet.Mvc": "6.0.0-*",
"Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-*",
"Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0",
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
"Microsoft.AspNet.StaticFiles": "1.0.0-*"
},
"frameworks": {
"aspnet50": {},
"aspnetcore50": { }
},
"webroot": "wwwroot"
}

View File

@ -0,0 +1 @@
Functional test site for verifying controllers registration rules.

View File

@ -0,0 +1,16 @@
// 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 Microsoft.AspNet.Mvc;
namespace ControllersFromServicesClassLibrary
{
[NonController]
public class ClientUIStubController
{
public object GetClientContent(int id)
{
return new object();
}
}
}

View File

@ -0,0 +1,16 @@
// 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 Microsoft.AspNet.Mvc;
namespace ControllersFromServicesClassLibrary
{
public class Inventory : ResourcesController
{
[HttpGet]
public int Get()
{
return 4;
}
}
}

View File

@ -0,0 +1,13 @@
// 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 Microsoft.AspNet.Mvc;
namespace ControllersFromServicesClassLibrary
{
[Route("/[controller]")]
public class ResourcesController
{
}
}

View File

@ -0,0 +1,15 @@
// 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.
namespace UserClassLibrary
{
// A type with the suffix Controller that lives in an assembly that does not reference Mvc.
// This will not be treated as a controller in an Mvc application.
public class TransactionController
{
public int UpdateTransaction()
{
return 1;
}
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="__ToolsVersion__" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>c651f432-4ebe-41a6-bad2-3e07ccba209c</ProjectGuid>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,10 @@
{
"frameworks": {
"aspnet50": { },
"aspnetcore50": {
"dependencies": {
"System.Runtime": "4.0.20-beta-*"
}
}
}
}