diff --git a/Mvc.sln b/Mvc.sln
index d76f22b480..094e8798b5 100644
--- a/Mvc.sln
+++ b/Mvc.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.22606.0
+VisualStudioVersion = 14.0.22604.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
EndProject
@@ -140,6 +140,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RazorCompilerCacheWebSite",
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.PageExecutionInstrumentation", "src\Microsoft.AspNet.PageExecutionInstrumentation\Microsoft.AspNet.PageExecutionInstrumentation.kproj", "{4DA2D7C1-A7B6-4C01-B57D-89E6EA4609DE}"
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
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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|x86.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -878,5 +906,7 @@ Global
{551DC89E-2A13-4CF2-83D7-1ADD802443D5} = {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}
+ {C651F432-4EBE-41A6-BAD2-3E07CCBA209C} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
+ {A19022EF-9BA3-4349-94E4-F48E13E1C8AE} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
EndGlobalSection
EndGlobal
diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs
index feb1f22050..39a4541c0c 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs
@@ -4,7 +4,7 @@
using System;
using System.IO;
using System.Linq.Expressions;
-using System.Security.Principal;
+using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
@@ -12,16 +12,21 @@ using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Routing;
-using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc
{
- public class Controller : IActionFilter, IAsyncActionFilter, IDisposable
+ ///
+ /// Base class for an MVC controller.
+ ///
+ public abstract class Controller : IActionFilter, IAsyncActionFilter, IDisposable
{
private DynamicViewData _viewBag;
private ViewDataDictionary _viewData;
private ActionContext _actionContext;
+ ///
+ /// Gets the request-specific .
+ ///
public IServiceProvider Resolver
{
get
@@ -30,6 +35,9 @@ namespace Microsoft.AspNet.Mvc
}
}
+ ///
+ /// Gets the for the executing action.
+ ///
public HttpContext Context
{
get
@@ -38,6 +46,9 @@ namespace Microsoft.AspNet.Mvc
}
}
+ ///
+ /// Gets the for the executing action.
+ ///
public HttpRequest Request
{
get
@@ -46,6 +57,9 @@ namespace Microsoft.AspNet.Mvc
}
}
+ ///
+ /// Gets the for the executing action.
+ ///
public HttpResponse Response
{
get
@@ -54,14 +68,20 @@ namespace Microsoft.AspNet.Mvc
}
}
+ ///
+ /// Gets the for the executing action.
+ ///
public RouteData RouteData
{
get
{
return ActionContext?.RouteData;
}
- }
+ }
+ ///
+ /// Gets the that contains the state of the model and of model-binding validation.
+ ///
public ModelStateDictionary ModelState
{
get
@@ -97,16 +117,28 @@ namespace Microsoft.AspNet.Mvc
}
}
+ ///
+ /// Gets or sets the .
+ ///
[Activate]
public ActionBindingContext BindingContext { get; set; }
+ ///
+ /// Gets or sets the .
+ ///
[FromServices]
public IModelMetadataProvider MetadataProvider { get; set; }
+ ///
+ /// Gets or sets the .
+ ///
[FromServices]
public IUrlHelper Url { get; set; }
- public IPrincipal User
+ ///
+ /// Gets or sets the for user associated with the executing action.
+ ///
+ public ClaimsPrincipal User
{
get
{
@@ -138,7 +170,7 @@ namespace Microsoft.AspNet.Mvc
return _viewData;
}
set
- {
+ {
if (value == null)
{
throw
@@ -149,6 +181,9 @@ namespace Microsoft.AspNet.Mvc
}
}
+ ///
+ /// Gets the dynamic view bag.
+ ///
public dynamic ViewBag
{
get
@@ -1111,12 +1146,18 @@ namespace Microsoft.AspNet.Mvc
return ModelState.IsValid;
}
+ ///
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
+ ///
+ /// Releases all resources currently used by this instance.
+ ///
+ /// true if this method is being invoked by the method,
+ /// otherwise false .
protected virtual void Dispose(bool disposing)
{
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultAssemblyProvider.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultAssemblyProvider.cs
index f54ed43e4d..0914afca47 100644
--- a/src/Microsoft.AspNet.Mvc.Core/DefaultAssemblyProvider.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/DefaultAssemblyProvider.cs
@@ -12,10 +12,21 @@ namespace Microsoft.AspNet.Mvc
{
public class DefaultAssemblyProvider : IAssemblyProvider
{
+ private readonly ILibraryManager _libraryManager;
+
+ public DefaultAssemblyProvider(ILibraryManager libraryManager)
+ {
+ _libraryManager = libraryManager;
+ }
+
///
/// Gets the set of assembly names that are used as root for discovery of
/// MVC controllers, view components and views.
///
+ // 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 ReferenceAssemblies { get; } = new HashSet(StringComparer.Ordinal)
{
"Microsoft.AspNet.Mvc",
@@ -23,16 +34,8 @@ namespace Microsoft.AspNet.Mvc
"Microsoft.AspNet.Mvc.ModelBinding",
"Microsoft.AspNet.Mvc.Razor",
"Microsoft.AspNet.Mvc.Razor.Host",
- "Microsoft.AspNet.Mvc.Rendering",
};
- private readonly ILibraryManager _libraryManager;
-
- public DefaultAssemblyProvider(ILibraryManager libraryManager)
- {
- _libraryManager = libraryManager;
- }
-
///
public IEnumerable CandidateAssemblies
{
diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerTypeProvider.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerTypeProvider.cs
index 4e4ae48742..e8ae821c43 100644
--- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerTypeProvider.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerTypeProvider.cs
@@ -16,6 +16,9 @@ namespace Microsoft.AspNet.Mvc
///
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 ILogger _logger;
@@ -37,17 +40,17 @@ namespace Microsoft.AspNet.Mvc
{
get
{
- var assemblies = _assemblyProvider.CandidateAssemblies;
+ var candidateAssemblies = new HashSet(_assemblyProvider.CandidateAssemblies);
if (_logger.IsEnabled(LogLevel.Verbose))
{
- foreach (var assembly in assemblies)
+ foreach (var assembly in candidateAssemblies)
{
_logger.WriteVerbose(new AssemblyValues(assembly));
}
}
- var types = assemblies.SelectMany(a => a.DefinedTypes);
- return types.Where(IsController);
+ var types = candidateAssemblies.SelectMany(a => a.DefinedTypes);
+ return types.Where(typeInfo => IsController(typeInfo, candidateAssemblies));
}
}
@@ -55,8 +58,10 @@ namespace Microsoft.AspNet.Mvc
/// Returns true if the is a controller. Otherwise false .
///
/// The .
+ /// The set of candidate assemblies.
/// true if the is a controller. Otherwise false .
- protected internal virtual bool IsController([NotNull] TypeInfo typeInfo)
+ protected internal virtual bool IsController([NotNull] TypeInfo typeInfo,
+ [NotNull] ISet candidateAssemblies)
{
if (!typeInfo.IsClass)
{
@@ -76,17 +81,47 @@ namespace Microsoft.AspNet.Mvc
{
return false;
}
- if (typeInfo.Name.Equals("Controller", StringComparison.OrdinalIgnoreCase))
+ if (!typeInfo.Name.EndsWith(ControllerTypeName, StringComparison.OrdinalIgnoreCase) &&
+ !DerivesFromController(typeInfo, candidateAssemblies))
{
return false;
}
- if (!typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
- !typeof(Controller).GetTypeInfo().IsAssignableFrom(typeInfo))
+ if (typeInfo.IsDefined(typeof(NonControllerAttribute)))
{
return false;
}
return true;
}
+
+ private bool DerivesFromController(TypeInfo typeInfo, ISet 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;
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/NonControllerAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/NonControllerAttribute.cs
new file mode 100644
index 0000000000..cd924a7807
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/NonControllerAttribute.cs
@@ -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
+{
+ ///
+ /// 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.
+ ///
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
+ public sealed class NonControllerAttribute : Attribute
+ {
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs
index 88e8dbfd2e..4b1815604d 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs
@@ -40,7 +40,7 @@ namespace Microsoft.AspNet.Mvc.Test
{
// Arrange
var metadataProvider = new DataAnnotationsModelMetadataProvider();
- var controller = new Controller();
+ var controller = new TestableController();
var originalViewData = controller.ViewData = new ViewDataDictionary(metadataProvider);
var replacementViewData = new ViewDataDictionary(metadataProvider);
@@ -61,7 +61,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Redirect_WithParameterUrl_SetsRedirectResultSameUrl()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var url = "/test/url";
// Act
@@ -77,7 +77,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void RedirectPermanent_WithParameterUrl_SetsRedirectResultPermanentAndSameUrl()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var url = "/test/url";
// Act
@@ -95,7 +95,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Redirect_WithParameter_NullOrEmptyUrl_Throws(string url)
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act & Assert
ExceptionAssert.ThrowsArgumentNullOrEmpty(
@@ -108,7 +108,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void RedirectPermanent_WithParameter_NullOrEmptyUrl_Throws(string url)
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act & Assert
ExceptionAssert.ThrowsArgumentNullOrEmpty(
@@ -119,7 +119,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void RedirectToAction_WithParameterActionName_SetsResultActionName()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var resultTemporary = controller.RedirectToAction("SampleAction");
@@ -134,7 +134,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void RedirectToActionPermanent_WithParameterActionName_SetsResultActionNameAndPermanent()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var resultPermanent = controller.RedirectToActionPermanent("SampleAction");
@@ -152,7 +152,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void RedirectToAction_WithParameterActionAndControllerName_SetsEqualNames(string controllerName)
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var resultTemporary = controller.RedirectToAction("SampleAction", controllerName);
@@ -172,7 +172,7 @@ namespace Microsoft.AspNet.Mvc.Test
string controllerName)
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var resultPermanent = controller.RedirectToActionPermanent("SampleAction", controllerName);
@@ -191,7 +191,7 @@ namespace Microsoft.AspNet.Mvc.Test
IEnumerable> expected)
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var resultTemporary = controller.RedirectToAction("SampleAction", "SampleController", routeValues);
@@ -211,7 +211,7 @@ namespace Microsoft.AspNet.Mvc.Test
IEnumerable> expected)
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var resultPermanent = controller.RedirectToActionPermanent(
@@ -234,7 +234,7 @@ namespace Microsoft.AspNet.Mvc.Test
IEnumerable> expected)
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var resultTemporary = controller.RedirectToAction(actionName: null, routeValues: routeValues);
@@ -253,7 +253,7 @@ namespace Microsoft.AspNet.Mvc.Test
IEnumerable> expected)
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var resultPermanent = controller.RedirectToActionPermanent(null, routeValues);
@@ -272,7 +272,7 @@ namespace Microsoft.AspNet.Mvc.Test
IEnumerable> expected)
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var resultTemporary = controller.RedirectToRoute(routeValues);
@@ -290,7 +290,7 @@ namespace Microsoft.AspNet.Mvc.Test
IEnumerable> expected)
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var resultPermanent = controller.RedirectToRoutePermanent(routeValues);
@@ -305,7 +305,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void RedirectToRoute_WithParameterRouteName_SetsResultSameRouteName()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var routeName = "CustomRouteName";
// Act
@@ -321,7 +321,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void RedirectToRoutePermanent_WithParameterRouteName_SetsResultSameRouteNameAndPermanent()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var routeName = "CustomRouteName";
// Act
@@ -340,7 +340,7 @@ namespace Microsoft.AspNet.Mvc.Test
IEnumerable> expected)
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var routeName = "CustomRouteName";
// Act
@@ -360,7 +360,7 @@ namespace Microsoft.AspNet.Mvc.Test
IEnumerable> expected)
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var routeName = "CustomRouteName";
// Act
@@ -377,7 +377,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Created_WithStringParameter_SetsCreatedLocation()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var uri = "http://test/url";
// Act
@@ -393,7 +393,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Created_WithAbsoluteUriParameter_SetsCreatedLocation()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var uri = new Uri("http://test/url");
// Act
@@ -409,7 +409,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Created_WithRelativeUriParameter_SetsCreatedLocation()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var uri = new Uri("/test/url", UriKind.Relative);
// Act
@@ -425,7 +425,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void CreatedAtAction_WithParameterActionName_SetsResultActionName()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var result = controller.CreatedAtAction("SampleAction", null);
@@ -444,7 +444,7 @@ namespace Microsoft.AspNet.Mvc.Test
string controllerName)
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var result = controller.CreatedAtAction("SampleAction", controllerName, null, null);
@@ -460,7 +460,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void CreatedAtAction_WithActionControllerRouteValues_SetsSameValues()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var expected = new Dictionary
{
{ "test", "case" },
@@ -485,7 +485,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void CreatedAtRoute_WithParameterRouteName_SetsResultSameRouteName()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var routeName = "SampleRoute";
// Act
@@ -500,7 +500,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void CreatedAtRoute_WithParameterRouteValues_SetsResultSameRouteValues()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var expected = new Dictionary
{
{ "test", "case" },
@@ -520,7 +520,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void CreatedAtRoute_WithParameterRouteNameAndValues_SetsResultSameProperties()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var routeName = "SampleRoute";
var expected = new Dictionary
{
@@ -542,7 +542,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void File_WithContents()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var fileContents = new byte[0];
// Act
@@ -559,7 +559,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void File_WithContentsAndFileDownloadName()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var fileContents = new byte[0];
// Act
@@ -576,7 +576,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void File_WithPath()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var path = Path.GetFullPath("somepath");
// Act
@@ -593,7 +593,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void File_WithPathAndFileDownloadName()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var path = Path.GetFullPath("somepath");
// Act
@@ -610,7 +610,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void File_WithStream()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var fileStream = Stream.Null;
// Act
@@ -627,7 +627,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void File_WithStreamAndFileDownloadName()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var fileStream = Stream.Null;
// Act
@@ -645,7 +645,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void HttpNotFound_SetsStatusCode()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var result = controller.HttpNotFound();
@@ -659,7 +659,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void BadRequest_SetsStatusCode()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var result = controller.HttpBadRequest();
@@ -673,7 +673,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void BadRequest_SetsStatusCodeAndValue_Object()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var obj = new object();
// Act
@@ -689,7 +689,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void BadRequest_SetsStatusCodeAndValue_ModelState()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var result = controller.HttpBadRequest(new ModelStateDictionary());
@@ -713,7 +713,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Controller_View_WithoutParameter_SetsResultNullViewNameAndNullViewDataModel()
{
// Arrange
- var controller = new Controller()
+ var controller = new TestableController()
{
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
};
@@ -732,7 +732,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Controller_View_WithParameterViewName_SetsResultViewNameAndNullViewDataModel()
{
// Arrange
- var controller = new Controller()
+ var controller = new TestableController()
{
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
};
@@ -751,7 +751,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Controller_View_WithParameterViewModel_SetsResultNullViewNameAndViewDataModel()
{
// Arrange
- var controller = new Controller()
+ var controller = new TestableController()
{
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
};
@@ -771,7 +771,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Controller_View_WithParameterViewNameAndViewModel_SetsResultViewNameAndViewDataModel()
{
// Arrange
- var controller = new Controller()
+ var controller = new TestableController()
{
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
};
@@ -791,7 +791,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Controller_Content_WithParameterContentString_SetsResultContent()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var actualContentResult = controller.Content("TestContent");
@@ -807,7 +807,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Controller_Content_WithParameterContentStringAndContentType_SetsResultContentAndContentType()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var actualContentResult = controller.Content("TestContent", "text/plain");
@@ -823,7 +823,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Controller_Content_WithParameterContentAndTypeAndEncoding_SetsResultContentAndTypeAndEncoding()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
// Act
var actualContentResult = controller.Content("TestContent", "text/plain", Encoding.UTF8);
@@ -839,7 +839,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void Controller_Json_WithParameterValue_SetsResultData()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var data = new object();
// Act
@@ -1144,7 +1144,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void ControllerExposes_RequestServices()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var serviceProvider = Mock.Of();
var httpContext = new Mock();
@@ -1166,7 +1166,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void ControllerExposes_Request()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var request = Mock.Of();
var httpContext = new Mock();
@@ -1188,7 +1188,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void ControllerExposes_Response()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var response = Mock.Of();
var httpContext = new Mock();
@@ -1210,7 +1210,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void ControllerExposes_RouteData()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var routeData = Mock.Of();
@@ -1325,7 +1325,7 @@ namespace Microsoft.AspNet.Mvc.Test
public void TryValidateModelEmptyBindingContextThrowsException()
{
// Arrange
- var controller = new Controller();
+ var controller = new TestableController();
var model = new TryValidateModelModel();
// Act & Assert
@@ -1346,7 +1346,7 @@ namespace Microsoft.AspNet.Mvc.Test
ValueProvider = provider,
};
- return new Controller()
+ return new TestableController()
{
ActionContext = actionContext,
BindingContext = bindingContext,
@@ -1397,5 +1397,10 @@ namespace Microsoft.AspNet.Mvc.Test
DisposeCalled = true;
}
}
+
+ private class TestableController : Controller
+ {
+
+ }
}
}
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs
index 409045d2cf..2933aa5130 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs
@@ -490,8 +490,8 @@ namespace Microsoft.AspNet.Mvc
{
// Arrange
var actionContext = new ActionContext();
- var controller1 = new Controller();
- var controller2 = new Controller();
+ var controller1 = new TestabilityController();
+ var controller2 = new TestabilityController();
// Act
controller2.ActionContext = actionContext;
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultControllerTypeProviderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultControllerTypeProviderTest.cs
index 04357a1a0a..2fdc27db47 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultControllerTypeProviderTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultControllerTypeProviderTest.cs
@@ -1,7 +1,8 @@
// 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.Linq;
+using System;
+using System.Collections.Generic;
using System.Reflection;
using Microsoft.AspNet.Mvc.DefaultControllerTypeProviderControllers;
using Xunit;
@@ -10,6 +11,11 @@ namespace Microsoft.AspNet.Mvc
{
public class DefaultControllerTypeProviderTest
{
+ private static readonly ISet CandidateAssemblies = new HashSet
+ {
+ typeof(StoreController).GetTypeInfo().Assembly
+ };
+
[Fact]
public void IsController_UserDefinedClass()
{
@@ -18,7 +24,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider();
// Act
- var isController = provider.IsController(typeInfo);
+ var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert
Assert.True(isController);
@@ -32,7 +38,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider();
// Act
- var isController = provider.IsController(typeInfo);
+ var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert
Assert.False(isController);
@@ -46,7 +52,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider();
// Act
- var isController = provider.IsController(typeInfo);
+ var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert
Assert.False(isController);
@@ -60,7 +66,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider();
// Act
- var isController = provider.IsController(typeInfo);
+ var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert
Assert.False(isController);
@@ -74,7 +80,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider();
// Act
- var isController = provider.IsController(typeInfo);
+ var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert
Assert.False(isController);
@@ -88,7 +94,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider();
// Act
- var isController = provider.IsController(typeInfo);
+ var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert
Assert.True(isController);
@@ -102,7 +108,21 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider();
// 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.False(isController);
@@ -116,7 +136,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider();
// Act
- var isController = provider.IsController(typeInfo);
+ var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert
Assert.True(isController);
@@ -130,7 +150,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider();
// Act
- var isController = provider.IsController(typeInfo);
+ var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert
Assert.True(isController);
@@ -144,7 +164,7 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider();
// Act
- var isController = provider.IsController(typeInfo);
+ var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert
Assert.True(isController);
@@ -158,12 +178,47 @@ namespace Microsoft.AspNet.Mvc
var provider = GetControllerTypeProvider();
// Act
- var isController = provider.IsController(typeInfo);
+ var isController = provider.IsController(typeInfo, CandidateAssemblies);
// Assert
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()
{
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 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
+ {
+
+ }
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/BasicTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/BasicTests.cs
index bd28688ced..907c67cc97 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/BasicTests.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/BasicTests.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
@@ -18,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
{
public class BasicTests
{
- private readonly IServiceProvider _provider = TestHelper.CreateServices("BasicWebSite");
+ private readonly IServiceProvider _provider = TestHelper.CreateServices(nameof(BasicWebSite));
private readonly Action _app = new Startup().Configure;
// 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();
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);
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ControllerDiscoveryConventionTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ControllerDiscoveryConventionTests.cs
new file mode 100644
index 0000000000..8c329c5a1d
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ControllerDiscoveryConventionTests.cs
@@ -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 _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();
+ 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 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));
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ControllerFromServicesTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ControllerFromServicesTests.cs
index bf1c47962e..f7e94fa191 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ControllerFromServicesTests.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ControllerFromServicesTests.cs
@@ -49,6 +49,21 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
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]
public async Task TypesWithControllerSuffixAreRegistered()
{
@@ -84,9 +99,10 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
}
[Theory]
- [InlineData("generic")]
- [InlineData("nested")]
- [InlineData("not-in-services")]
+ [InlineData("not-discovered/generic")]
+ [InlineData("not-discovered/nested")]
+ [InlineData("not-discovered/not-in-services")]
+ [InlineData("ClientUIStub/GetClientContent/5")]
public async Task AddControllersFromServices_UsesControllerDiscoveryContentions(string action)
{
// Arrange
@@ -94,7 +110,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var client = server.CreateClient();
// Act
- var response = await client.GetAsync("http://localhost/not-discovered/" + action);
+ var response = await client.GetAsync("http://localhost/" + action);
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json
index 00b56f537f..378f626e35 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json
@@ -17,6 +17,7 @@
"BasicWebSite": "1.0.0",
"CompositeViewEngineWebSite": "1.0.0",
"ConnegWebSite": "1.0.0",
+ "ControllerDiscoveryConventionsWebSite": "1.0.0",
"ControllersFromServicesWebSite": "1.0.0",
"CustomRouteWebSite": "1.0.0",
"ErrorPageMiddlewareWebSite": "1.0.0",
diff --git a/test/WebSites/BasicWebSite/Controllers/ApplicationBaseController.cs b/test/WebSites/BasicWebSite/Controllers/ApplicationBaseController.cs
new file mode 100644
index 0000000000..5eaf0265a9
--- /dev/null
+++ b/test/WebSites/BasicWebSite/Controllers/ApplicationBaseController.cs
@@ -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
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/BasicWebSite/Controllers/Appointments.cs b/test/WebSites/BasicWebSite/Controllers/Appointments.cs
new file mode 100644
index 0000000000..d4a2bb835a
--- /dev/null
+++ b/test/WebSites/BasicWebSite/Controllers/Appointments.cs
@@ -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."
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/BasicWebSite/Controllers/SqlDataController.cs b/test/WebSites/BasicWebSite/Controllers/SqlDataController.cs
new file mode 100644
index 0000000000..8378caab44
--- /dev/null
+++ b/test/WebSites/BasicWebSite/Controllers/SqlDataController.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/ControllerDiscoveryConventionsWebSite/AbstractController.cs b/test/WebSites/ControllerDiscoveryConventionsWebSite/AbstractController.cs
new file mode 100644
index 0000000000..a690505d8b
--- /dev/null
+++ b/test/WebSites/ControllerDiscoveryConventionsWebSite/AbstractController.cs
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/ControllerDiscoveryConventionsWebSite/ChildOfAbstract.cs b/test/WebSites/ControllerDiscoveryConventionsWebSite/ChildOfAbstract.cs
new file mode 100644
index 0000000000..33bb9d4c56
--- /dev/null
+++ b/test/WebSites/ControllerDiscoveryConventionsWebSite/ChildOfAbstract.cs
@@ -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
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/ControllerDiscoveryConventionsWebSite/ControllerDiscoveryConventionsWebSite.kproj b/test/WebSites/ControllerDiscoveryConventionsWebSite/ControllerDiscoveryConventionsWebSite.kproj
new file mode 100644
index 0000000000..c3e1157516
--- /dev/null
+++ b/test/WebSites/ControllerDiscoveryConventionsWebSite/ControllerDiscoveryConventionsWebSite.kproj
@@ -0,0 +1,20 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ a19022ef-9ba3-4349-94e4-f48e13e1c8ae
+
+
+
+
+
+
+ 2.0
+ 51163
+
+
+
\ No newline at end of file
diff --git a/test/WebSites/ControllerDiscoveryConventionsWebSite/NonControllerController.cs b/test/WebSites/ControllerDiscoveryConventionsWebSite/NonControllerController.cs
new file mode 100644
index 0000000000..eb6acccc84
--- /dev/null
+++ b/test/WebSites/ControllerDiscoveryConventionsWebSite/NonControllerController.cs
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/ControllerDiscoveryConventionsWebSite/PersonApi.cs b/test/WebSites/ControllerDiscoveryConventionsWebSite/PersonApi.cs
new file mode 100644
index 0000000000..6ac7ab1cf5
--- /dev/null
+++ b/test/WebSites/ControllerDiscoveryConventionsWebSite/PersonApi.cs
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/ControllerDiscoveryConventionsWebSite/PocoController.cs b/test/WebSites/ControllerDiscoveryConventionsWebSite/PocoController.cs
new file mode 100644
index 0000000000..2a28322243
--- /dev/null
+++ b/test/WebSites/ControllerDiscoveryConventionsWebSite/PocoController.cs
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/ControllerDiscoveryConventionsWebSite/SqlTransactionManager.cs b/test/WebSites/ControllerDiscoveryConventionsWebSite/SqlTransactionManager.cs
new file mode 100644
index 0000000000..ac66a317b3
--- /dev/null
+++ b/test/WebSites/ControllerDiscoveryConventionsWebSite/SqlTransactionManager.cs
@@ -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);
+ }
+ }
+}
diff --git a/test/WebSites/ControllerDiscoveryConventionsWebSite/Startup.cs b/test/WebSites/ControllerDiscoveryConventionsWebSite/Startup.cs
new file mode 100644
index 0000000000..19dbf51153
--- /dev/null
+++ b/test/WebSites/ControllerDiscoveryConventionsWebSite/Startup.cs
@@ -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?}");
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/ControllerDiscoveryConventionsWebSite/project.json b/test/WebSites/ControllerDiscoveryConventionsWebSite/project.json
new file mode 100644
index 0000000000..6ab014eca2
--- /dev/null
+++ b/test/WebSites/ControllerDiscoveryConventionsWebSite/project.json
@@ -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"
+}
\ No newline at end of file
diff --git a/test/WebSites/ControllerDiscoveryConventionsWebSite/wwwroot/readme.md b/test/WebSites/ControllerDiscoveryConventionsWebSite/wwwroot/readme.md
new file mode 100644
index 0000000000..408d167619
--- /dev/null
+++ b/test/WebSites/ControllerDiscoveryConventionsWebSite/wwwroot/readme.md
@@ -0,0 +1 @@
+Functional test site for verifying controllers registration rules.
\ No newline at end of file
diff --git a/test/WebSites/ControllersFromServicesClassLibrary/ClientUIStubController.cs b/test/WebSites/ControllersFromServicesClassLibrary/ClientUIStubController.cs
new file mode 100644
index 0000000000..8e0d8f2a8f
--- /dev/null
+++ b/test/WebSites/ControllersFromServicesClassLibrary/ClientUIStubController.cs
@@ -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();
+ }
+ }
+}
diff --git a/test/WebSites/ControllersFromServicesClassLibrary/Inventory.cs b/test/WebSites/ControllersFromServicesClassLibrary/Inventory.cs
new file mode 100644
index 0000000000..41126acb20
--- /dev/null
+++ b/test/WebSites/ControllersFromServicesClassLibrary/Inventory.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/ControllersFromServicesClassLibrary/ResourcesController.cs b/test/WebSites/ControllersFromServicesClassLibrary/ResourcesController.cs
new file mode 100644
index 0000000000..54a046fa08
--- /dev/null
+++ b/test/WebSites/ControllersFromServicesClassLibrary/ResourcesController.cs
@@ -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
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/UserClassLibrary/TransactionController.cs b/test/WebSites/UserClassLibrary/TransactionController.cs
new file mode 100644
index 0000000000..3cb0558fd6
--- /dev/null
+++ b/test/WebSites/UserClassLibrary/TransactionController.cs
@@ -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;
+ }
+ }
+}
diff --git a/test/WebSites/UserClassLibrary/UserClassLibrary.kproj b/test/WebSites/UserClassLibrary/UserClassLibrary.kproj
new file mode 100644
index 0000000000..7e00fe3ca6
--- /dev/null
+++ b/test/WebSites/UserClassLibrary/UserClassLibrary.kproj
@@ -0,0 +1,20 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ c651f432-4ebe-41a6-bad2-3e07ccba209c
+
+
+
+
+
+
+
+ 2.0
+
+
+
\ No newline at end of file
diff --git a/test/WebSites/UserClassLibrary/project.json b/test/WebSites/UserClassLibrary/project.json
new file mode 100644
index 0000000000..d75eb8c2d5
--- /dev/null
+++ b/test/WebSites/UserClassLibrary/project.json
@@ -0,0 +1,10 @@
+{
+ "frameworks": {
+ "aspnet50": { },
+ "aspnetcore50": {
+ "dependencies": {
+ "System.Runtime": "4.0.20-beta-*"
+ }
+ }
+ }
+}
\ No newline at end of file