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