From ec9dfe49ea8c8e7678f5a8b91f215b36c28469f1 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Thu, 24 Mar 2016 01:40:42 -0700 Subject: [PATCH] [Fixes #4014] Add overload to AddControllerAsServices that uses the default controller discovery logic. * Added ControllerFeature and ControllerFeatureProvider to perform controller discovery. * Changed controller discovery to use application parts. * Changed ControllerActionDescriptorProvider to make use of Application parts. * Simplified AddControllerAsServices to not accept any parameter and perform controller discovery through the ApplicationPartManager in the IMvcBuilder and IMvcCoreBuilder. Assemblies should be added to the ApplicationPartManager in order to discover controllers in them. --- .../ApplicationParts/AssemblyPart.cs | 5 +- .../IApplicationPartTypeProvider.cs | 19 + .../Controllers/ControllerFeature.cs | 24 + .../Controllers/ControllerFeatureProvider.cs | 79 +++ .../DefaultControllerTypeProvider.cs | 91 --- .../StaticControllerTypeProvider.cs | 46 -- .../MvcCoreMvcBuilderExtensions.cs | 66 +-- .../MvcCoreMvcCoreBuilderExtensions.cs | 66 +-- .../MvcCoreServiceCollectionExtensions.cs | 2 +- .../ControllerActionDescriptorProvider.cs | 22 +- .../Internal/ControllersAsServices.cs | 73 --- .../Internal/MvcCoreMvcOptionsSetup.cs | 2 +- .../ApplicationParts/AssemblyPartTest.cs | 15 + .../ControllerFeatureProviderTest.cs | 529 ++++++++++++++++++ .../DefaultControllerTypeProviderTest.cs | 433 -------------- .../MvcBuilderExtensionsTest.cs | 55 +- .../MvcCoreBuilderExtensionsTest.cs | 7 +- .../DefaultActionSelectorTests.cs | 14 +- ...ControllerActionDescriptorProviderTests.cs | 23 +- .../Internal/ControllerAsServicesTest.cs | 48 -- .../Binders/ComplexTypeModelBinderTest.cs | 3 +- .../TestApplicationPart.cs | 44 ++ .../TestFeatureProvider.cs | 35 ++ .../MvcTestFixture.cs | 3 +- .../ApiControllerActionDiscoveryTest.cs | 50 +- .../ControllersFromServicesWebSite/Startup.cs | 26 +- 26 files changed, 919 insertions(+), 861 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationPartTypeProvider.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeature.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeatureProvider.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerTypeProvider.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Controllers/StaticControllerTypeProvider.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllersAsServices.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerFeatureProviderTest.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerTypeProviderTest.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerAsServicesTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/TestApplicationPart.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/TestFeatureProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs index 0147f59eb7..61ac71bb01 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts /// /// An backed by an . /// - public class AssemblyPart : ApplicationPart + public class AssemblyPart : ApplicationPart, IApplicationPartTypeProvider { /// /// Initalizes a new instance. @@ -35,5 +35,8 @@ public AssemblyPart(Assembly assembly) /// Gets the name of the . /// public override string Name => Assembly.GetName().Name; + + /// + public IEnumerable Types => Assembly.DefinedTypes; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationPartTypeProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationPartTypeProvider.cs new file mode 100644 index 0000000000..616c9a9aea --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationPartTypeProvider.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Reflection; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + /// + /// Exposes a set of types from an . + /// + public interface IApplicationPartTypeProvider + { + /// + /// Gets the list of available types in the . + /// + IEnumerable Types { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeature.cs b/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeature.cs new file mode 100644 index 0000000000..e3c8eaafc5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeature.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Controllers +{ + /// + /// The list of controllers types in an MVC application. The can be populated + /// using the that is available during startup at + /// and or at a later stage by requiring the + /// as a dependency in a component. + /// + public class ControllerFeature + { + /// + /// Gets the list of controller types in an MVC application. + /// + public IList Controllers { get; } = new List(); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeatureProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeatureProvider.cs new file mode 100644 index 0000000000..6b5a3616d2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeatureProvider.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApplicationParts; + +namespace Microsoft.AspNetCore.Mvc.Controllers +{ + /// + /// Discovers controllers from a list of instances. + /// + public class ControllerFeatureProvider : IApplicationFeatureProvider + { + private const string ControllerTypeNameSuffix = "Controller"; + + /// + public void PopulateFeature( + IEnumerable parts, + ControllerFeature feature) + { + foreach (var part in parts.OfType()) + { + foreach (var type in part.Types) + { + if (IsController(type) && !feature.Controllers.Contains(type)) + { + feature.Controllers.Add(type); + } + } + } + } + + /// + /// Determines if a given is a controller. + /// + /// The candidate. + /// true if the type is a controller; otherwise false. + protected virtual bool IsController(TypeInfo typeInfo) + { + if (!typeInfo.IsClass) + { + return false; + } + + if (typeInfo.IsAbstract) + { + return false; + } + + // We only consider public top-level classes as controllers. IsPublic returns false for nested + // classes, regardless of visibility modifiers + if (!typeInfo.IsPublic) + { + return false; + } + + if (typeInfo.ContainsGenericParameters) + { + return false; + } + + if (typeInfo.IsDefined(typeof(NonControllerAttribute))) + { + return false; + } + + if (!typeInfo.Name.EndsWith(ControllerTypeNameSuffix, StringComparison.OrdinalIgnoreCase) && + !typeInfo.IsDefined(typeof(ControllerAttribute))) + { + return false; + } + + return true; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerTypeProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerTypeProvider.cs deleted file mode 100644 index a6d93d60c6..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerTypeProvider.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Microsoft.AspNetCore.Mvc.Infrastructure; - -namespace Microsoft.AspNetCore.Mvc.Controllers -{ - /// - /// A that identifies controller types from assemblies - /// specified by the registered . - /// - public class DefaultControllerTypeProvider : IControllerTypeProvider - { - private const string ControllerTypeName = "Controller"; - private static readonly TypeInfo ObjectTypeInfo = typeof(object).GetTypeInfo(); - private readonly IAssemblyProvider _assemblyProvider; - - /// - /// Initializes a new instance of . - /// - /// that provides assemblies to look for - /// controllers in. - public DefaultControllerTypeProvider(IAssemblyProvider assemblyProvider) - { - _assemblyProvider = assemblyProvider; - } - - /// - public virtual IEnumerable ControllerTypes - { - get - { - var candidateAssemblies = new HashSet(_assemblyProvider.CandidateAssemblies); - var types = candidateAssemblies.SelectMany(a => a.DefinedTypes); - return types.Where(typeInfo => IsController(typeInfo)); - } - } - - /// - /// Returns true if the is a controller. Otherwise false. - /// - /// The . - /// true if the is a controller. Otherwise false. - protected internal virtual bool IsController(TypeInfo typeInfo) - { - if (typeInfo == null) - { - throw new ArgumentNullException(nameof(typeInfo)); - } - - if (!typeInfo.IsClass) - { - return false; - } - - if (typeInfo.IsAbstract) - { - return false; - } - - // We only consider public top-level classes as controllers. IsPublic returns false for nested - // classes, regardless of visibility modifiers - if (!typeInfo.IsPublic) - { - return false; - } - - if (typeInfo.ContainsGenericParameters) - { - return false; - } - - if (typeInfo.IsDefined(typeof(NonControllerAttribute))) - { - return false; - } - - if (!typeInfo.Name.EndsWith(ControllerTypeName, StringComparison.OrdinalIgnoreCase) && - !typeInfo.IsDefined(typeof(ControllerAttribute))) - { - return false; - } - - return true; - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/StaticControllerTypeProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Controllers/StaticControllerTypeProvider.cs deleted file mode 100644 index a20c7fce1d..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/StaticControllerTypeProvider.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace Microsoft.AspNetCore.Mvc.Controllers -{ - /// - /// A with a fixed set of types that are used as controllers. - /// - public class StaticControllerTypeProvider : IControllerTypeProvider - { - /// - /// Initializes a new instance of . - /// - public StaticControllerTypeProvider() - : this(Enumerable.Empty()) - { - } - - /// - /// Initializes a new instance of . - /// - /// The sequence of controller . - public StaticControllerTypeProvider(IEnumerable controllerTypes) - { - if (controllerTypes == null) - { - throw new ArgumentNullException(nameof(controllerTypes)); - } - - ControllerTypes = new List(controllerTypes); - } - - /// - /// Gets the list of controller s. - /// - public IList ControllerTypes { get; } - - /// - IEnumerable IControllerTypeProvider.ControllerTypes => ControllerTypes; - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs index 4c57152edc..9603a89083 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs @@ -2,13 +2,13 @@ // 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.Linq; using System.Reflection; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Formatters; -using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection.Extensions; namespace Microsoft.Extensions.DependencyInjection { @@ -110,70 +110,22 @@ public static IMvcBuilder ConfigureApplicationPartManager( } /// - /// Register the specified as services and as a source for controller - /// discovery. + /// Registers discovered controllers as services in the . /// /// The . - /// A sequence of controller s to register. /// The . - public static IMvcBuilder AddControllersAsServices( - this IMvcBuilder builder, - params Type[] controllerTypes) + public static IMvcBuilder AddControllersAsServices(this IMvcBuilder builder) { - return builder.AddControllersAsServices(controllerTypes.AsEnumerable()); - } + var feature = new ControllerFeature(); + builder.PartManager.PopulateFeature(feature); - /// - /// Register the specified as services and as a source for controller - /// discovery. - /// - /// The . - /// A sequence of controller s to register. - /// The . - public static IMvcBuilder AddControllersAsServices( - this IMvcBuilder builder, - IEnumerable controllerTypes) + foreach (var controller in feature.Controllers.Select(c => c.AsType())) { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); + builder.Services.TryAddTransient(controller, controller); } - ControllersAsServices.AddControllersAsServices(builder.Services, controllerTypes); - return builder; - } - - /// - /// Registers controller types from the specified as services and as a source - /// for controller discovery. - /// - /// The . - /// Assemblies to scan. - /// The . - public static IMvcBuilder AddControllersAsServices( - this IMvcBuilder builder, - params Assembly[] controllerAssemblies) - { - return builder.AddControllersAsServices(controllerAssemblies.AsEnumerable()); - } - - /// - /// Registers controller types from the specified as services and as a source - /// for controller discovery. - /// - /// The . - /// Assemblies to scan. - /// The . - public static IMvcBuilder AddControllersAsServices( - this IMvcBuilder builder, - IEnumerable controllerAssemblies) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } + builder.Services.Replace(ServiceDescriptor.Transient()); - ControllersAsServices.AddControllersAsServices(builder.Services, controllerAssemblies); return builder; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs index 3ef94e1343..f550810bbb 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs @@ -2,13 +2,13 @@ // 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.Linq; using System.Reflection; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -97,51 +97,23 @@ internal static void AddAuthorizationServices(IServiceCollection services) } /// - /// Register the specified as services and as a source for controller - /// discovery. + /// Registers discovered controllers as services in the . /// /// The . - /// A sequence of controller s to register. /// The . - public static IMvcCoreBuilder AddControllersAsServices( - this IMvcCoreBuilder builder, - params Type[] controllerTypes) + public static IMvcCoreBuilder AddControllersAsServices(this IMvcCoreBuilder builder) { - return builder.AddControllersAsServices(controllerTypes.AsEnumerable()); - } + var feature = new ControllerFeature(); + builder.PartManager.PopulateFeature(feature); - /// - /// Register the specified as services and as a source for controller - /// discovery. - /// - /// The . - /// A sequence of controller s to register. - /// The . - public static IMvcCoreBuilder AddControllersAsServices( - this IMvcCoreBuilder builder, - IEnumerable controllerTypes) - { - if (builder == null) + foreach (var controller in feature.Controllers.Select(c => c.AsType())) { - throw new ArgumentNullException(nameof(builder)); + builder.Services.TryAddTransient(controller, controller); } - ControllersAsServices.AddControllersAsServices(builder.Services, controllerTypes); - return builder; - } + builder.Services.Replace(ServiceDescriptor.Transient()); - /// - /// Registers controller types from the specified as services and as a source - /// for controller discovery. - /// - /// The . - /// Assemblies to scan. - /// The . - public static IMvcCoreBuilder AddControllersAsServices( - this IMvcCoreBuilder builder, - params Assembly[] controllerAssemblies) - { - return builder.AddControllersAsServices(controllerAssemblies.AsEnumerable()); + return builder; } /// @@ -193,25 +165,5 @@ public static IMvcCoreBuilder ConfigureApplicationPartManager( return builder; } - - /// - /// Registers controller types from the specified as services and as a source - /// for controller discovery. - /// - /// The . - /// Assemblies to scan. - /// The . - public static IMvcCoreBuilder AddControllersAsServices( - this IMvcCoreBuilder builder, - IEnumerable controllerAssemblies) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - ControllersAsServices.AddControllersAsServices(builder.Services, controllerAssemblies); - return builder; - } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index 6c21cf5c65..c64ef84169 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -59,6 +59,7 @@ private static ApplicationPartManager GetApplicationPartManager(IServiceCollecti if (manager == null) { manager = new ApplicationPartManager(); + manager.FeatureProviders.Add(new ControllerFeatureProvider()); var environment = GetServiceFromCollection(services); if (environment == null) @@ -126,7 +127,6 @@ internal static void AddMvcCoreServices(IServiceCollection services) // These are consumed only when creating action descriptors, then they can be de-allocated services.TryAddTransient(); - services.TryAddTransient(); services.TryAddEnumerable( ServiceDescriptor.Transient()); services.TryAddEnumerable( diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorProvider.cs index d2df6a11ff..f6706effe1 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorProvider.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.Options; @@ -13,18 +15,18 @@ namespace Microsoft.AspNetCore.Mvc.Internal { public class ControllerActionDescriptorProvider : IActionDescriptorProvider { + private readonly ApplicationPartManager _partManager; private readonly IApplicationModelProvider[] _applicationModelProviders; - private readonly IControllerTypeProvider _controllerTypeProvider; private readonly IEnumerable _conventions; public ControllerActionDescriptorProvider( - IControllerTypeProvider controllerTypeProvider, + ApplicationPartManager partManager, IEnumerable applicationModelProviders, IOptions optionsAccessor) { - if (controllerTypeProvider == null) + if (partManager == null) { - throw new ArgumentNullException(nameof(controllerTypeProvider)); + throw new ArgumentNullException(nameof(partManager)); } if (applicationModelProviders == null) @@ -37,7 +39,7 @@ public ControllerActionDescriptorProvider( throw new ArgumentNullException(nameof(optionsAccessor)); } - _controllerTypeProvider = controllerTypeProvider; + _partManager = partManager; _applicationModelProviders = applicationModelProviders.OrderBy(p => p.Order).ToArray(); _conventions = optionsAccessor.Value.Conventions; } @@ -75,7 +77,7 @@ internal protected IEnumerable GetDescriptors() internal protected ApplicationModel BuildModel() { - var controllerTypes = _controllerTypeProvider.ControllerTypes; + var controllerTypes = GetControllerTypes(); var context = new ApplicationModelProviderContext(controllerTypes); for (var i = 0; i < _applicationModelProviders.Length; i++) @@ -90,5 +92,13 @@ internal protected ApplicationModel BuildModel() return context.Result; } + + private IEnumerable GetControllerTypes() + { + var feature = new ControllerFeature(); + _partManager.PopulateFeature(feature); + + return feature.Controllers; + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllersAsServices.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllersAsServices.cs deleted file mode 100644 index a6b3985fa1..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllersAsServices.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace Microsoft.AspNetCore.Mvc.Internal -{ - public static class ControllersAsServices - { - public static void AddControllersAsServices(IServiceCollection services, IEnumerable types) - { - if (services == null) - { - throw new ArgumentNullException(nameof(services)); - } - - if (types == null) - { - throw new ArgumentNullException(nameof(types)); - } - - StaticControllerTypeProvider controllerTypeProvider = null; - - controllerTypeProvider = services - .Where(s => s.ServiceType == typeof(IControllerTypeProvider)) - .Select(s => s.ImplementationInstance) - .OfType() - .FirstOrDefault() - ?? new StaticControllerTypeProvider(); - - foreach (var type in types) - { - services.TryAddTransient(type, type); - controllerTypeProvider.ControllerTypes.Add(type.GetTypeInfo()); - } - - services.Replace(ServiceDescriptor.Transient()); - services.Replace(ServiceDescriptor.Singleton(controllerTypeProvider)); - } - - public static void AddControllersAsServices(IServiceCollection services, IEnumerable assemblies) - { - if (services == null) - { - throw new ArgumentNullException(nameof(services)); - } - - if (assemblies == null) - { - throw new ArgumentNullException(nameof(assemblies)); - } - - var assemblyProvider = new StaticAssemblyProvider(); - - foreach (var assembly in assemblies) - { - assemblyProvider.CandidateAssemblies.Add(assembly); - } - - var controllerTypeProvider = new DefaultControllerTypeProvider(assemblyProvider); - var controllerTypes = controllerTypeProvider.ControllerTypes; - - AddControllersAsServices(services, controllerTypes.Select(type => type.AsType())); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs index 522e3edcaf..537a82a16c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs @@ -45,7 +45,7 @@ public void Configure(MvcOptions options) // Set up ModelBinding options.ModelBinderProviders.Add(new BinderTypeModelBinderProvider()); options.ModelBinderProviders.Add(new ServicesModelBinderProvider()); - options.ModelBinderProviders.Add(new BodyModelBinderProvider(readerFactory)); + options.ModelBinderProviders.Add(new BodyModelBinderProvider(_readerFactory)); options.ModelBinderProviders.Add(new HeaderModelBinderProvider()); options.ModelBinderProviders.Add(new SimpleTypeModelBinderProvider()); options.ModelBinderProviders.Add(new CancellationTokenModelBinderProvider()); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/AssemblyPartTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/AssemblyPartTest.cs index 591026fa5d..dea3859282 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/AssemblyPartTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/AssemblyPartTest.cs @@ -21,6 +21,21 @@ public void AssemblyPart_Name_ReturnsAssemblyName() Assert.Equal("Microsoft.AspNetCore.Mvc.Core.Test", name); } + [Fact] + public void AssemblyPart_Types_ReturnsDefinedTypes() + { + // Arrange + var assembly = typeof(AssemblyPartTest).GetTypeInfo().Assembly; + var part = new AssemblyPart(assembly); + + // Act + var types = part.Types; + + // Assert + Assert.Equal(assembly.DefinedTypes, types); + Assert.NotSame(assembly.DefinedTypes, types); + } + [Fact] public void AssemblyPart_Assembly_ReturnsAssembly() { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerFeatureProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerFeatureProviderTest.cs new file mode 100644 index 0000000000..69bd65a80e --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerFeatureProviderTest.cs @@ -0,0 +1,529 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.ControllerFeatureProviderControllers; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Controllers +{ + public class ControllerFeatureProviderTest + { + [Fact] + public void UserDefinedClass_DerivedFromController_IsController() + { + // Arrange + var controllerType = typeof(StoreController).GetTypeInfo(); + var manager = GetApplicationPartManager(controllerType); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + var discovered = Assert.Single(feature.Controllers); + Assert.Equal(controllerType, discovered); + } + + [Fact] + public void UserDefinedClass_DerivedFromControllerBase_IsController() + { + // Arrange + var controllerType = typeof(ProductsController).GetTypeInfo(); + var manager = GetApplicationPartManager(controllerType); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + var discovered = Assert.Single(feature.Controllers); + Assert.Equal(controllerType, discovered); + } + + [Fact] + public void UserDefinedClass_DerivedFromControllerBaseWithoutSuffix_IsController() + { + // Arrange + var controllerType = typeof(Products).GetTypeInfo(); + var manager = GetApplicationPartManager(controllerType); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + var discovered = Assert.Single(feature.Controllers); + Assert.Equal(controllerType, discovered); + } + + [Fact] + public void FrameworkControllerClass_IsNotController() + { + // Arrange + var controllerType = typeof(Controller).GetTypeInfo(); + var manager = GetApplicationPartManager(controllerType); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + Assert.Empty(feature.Controllers); + } + + [Fact] + public void FrameworkBaseControllerClass_IsNotController() + { + // Arrange + var controllerType = typeof(ControllerBase).GetTypeInfo(); + var manager = GetApplicationPartManager(controllerType); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + Assert.Empty(feature.Controllers); + } + + [Fact] + public void UserDefinedControllerClass_IsNotController() + { + // Arrange + var controllerType = typeof(ControllerFeatureProviderControllers.Controller).GetTypeInfo(); + var manager = GetApplicationPartManager(controllerType); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + Assert.Empty(feature.Controllers); + } + + [Fact] + public void Interface_IsNotController() + { + // Arrange + var controllerType = typeof(ITestController).GetTypeInfo(); + var manager = GetApplicationPartManager(controllerType); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + Assert.Empty(feature.Controllers); + } + + [Fact] + public void AbstractClass_IsNotController() + { + // Arrange + var controllerType = typeof(AbstractController).GetTypeInfo(); + var manager = GetApplicationPartManager(controllerType); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + Assert.Empty(feature.Controllers); + } + + [Fact] + public void DerivedAbstractClass_IsController() + { + // Arrange + var controllerType = typeof(DerivedAbstractController).GetTypeInfo(); + var manager = GetApplicationPartManager(controllerType); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + var discovered = Assert.Single(feature.Controllers); + Assert.Equal(controllerType, discovered); + } + + [Fact] + public void OpenGenericClass_IsNotController() + { + // Arrange + var controllerType = typeof(OpenGenericController<>).GetTypeInfo(); + var manager = GetApplicationPartManager(controllerType); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + Assert.Empty(feature.Controllers); + } + + [Fact] + public void WithoutSuffixOrAncestorWithController_IsNotController() + { + // Arrange + var controllerType = typeof(NoSuffixPoco).GetTypeInfo(); + var manager = GetApplicationPartManager(controllerType); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + Assert.Empty(feature.Controllers); + } + + [Fact] + public void ClosedGenericClass_IsController() + { + // Arrange + var controllerType = typeof(OpenGenericController).GetTypeInfo(); + var manager = GetApplicationPartManager(controllerType); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + var discovered = Assert.Single(feature.Controllers); + Assert.Equal(controllerType, discovered); + } + + [Fact] + public void DerivedGenericClass_IsController() + { + // Arrange + var controllerType = typeof(DerivedGenericController).GetTypeInfo(); + var manager = GetApplicationPartManager(controllerType); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + var discovered = Assert.Single(feature.Controllers); + Assert.Equal(controllerType, discovered); + } + + [Fact] + public void Poco_WithNamingConvention_IsController() + { + // Arrange + var controllerType = typeof(PocoController).GetTypeInfo(); + var manager = GetApplicationPartManager(controllerType); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + var discovered = Assert.Single(feature.Controllers); + Assert.Equal(controllerType, discovered); + } + + [Fact] + public void NoControllerSuffix_IsController() + { + // Arrange + var controllerType = typeof(NoSuffix).GetTypeInfo(); + var manager = GetApplicationPartManager(controllerType); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + var discovered = Assert.Single(feature.Controllers); + Assert.Equal(controllerType, discovered); + } + + [Theory] + [InlineData(typeof(DescendantLevel1))] + [InlineData(typeof(DescendantLevel2))] + public void AncestorTypeHasControllerAttribute_IsController(Type type) + { + // Arrange + var manager = GetApplicationPartManager(type.GetTypeInfo()); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + var discovered = Assert.Single(feature.Controllers); + Assert.Equal(type.GetTypeInfo(), discovered); + } + + [Fact] + public void AncestorTypeDoesNotHaveControllerAttribute_IsNotController() + { + // Arrange + var controllerType = typeof(NoSuffixNoControllerAttribute).GetTypeInfo(); + var manager = GetApplicationPartManager(controllerType); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + Assert.Empty(feature.Controllers); + } + + [Fact] + public void GetFeature_OnlyRunsOnParts_ThatImplementIExportTypes() + { + // Arrange + var otherPart = new Mock(); + otherPart + .As() + .Setup(t => t.Types) + .Returns(new[] { typeof(PocoController).GetTypeInfo() }); + + var parts = new[] { + Mock.Of(), + new TestApplicationPart(typeof(NoSuffix).GetTypeInfo()), + otherPart.Object + }; + + var feature = new ControllerFeature(); + + var expected = new List + { + typeof(NoSuffix).GetTypeInfo(), + typeof(PocoController).GetTypeInfo() + }; + + var provider = new ControllerFeatureProvider(); + + // Act + provider.PopulateFeature(parts, feature); + + // Assert + Assert.Equal(expected, feature.Controllers.ToList()); + } + + [Fact] + public void GetFeature_DoesNotAddDuplicates_ToTheListOfControllers() + { + // Arrange + var otherPart = new Mock(); + otherPart + .As() + .Setup(t => t.Types) + .Returns(new[] { typeof(PocoController).GetTypeInfo() }); + + var parts = new[] { + Mock.Of(), + new TestApplicationPart(typeof(NoSuffix)), + otherPart.Object + }; + + var feature = new ControllerFeature(); + + var expected = new List + { + typeof(NoSuffix).GetTypeInfo(), + typeof(PocoController).GetTypeInfo() + }; + + var provider = new ControllerFeatureProvider(); + + provider.PopulateFeature(parts, feature); + + // Act + provider.PopulateFeature(parts, feature); + + // Assert + Assert.Equal(expected, feature.Controllers.ToList()); + } + + [Theory] + [InlineData(typeof(BaseNonControllerController))] + [InlineData(typeof(BaseNonControllerControllerChild))] + [InlineData(typeof(BasePocoNonControllerController))] + [InlineData(typeof(BasePocoNonControllerControllerChild))] + [InlineData(typeof(NonController))] + [InlineData(typeof(NonControllerChild))] + [InlineData(typeof(BaseNonControllerAttributeChildControllerControllerAttributeController))] + [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 manager = GetApplicationPartManager(type.GetTypeInfo()); + var feature = new ControllerFeature(); + + // Act + manager.PopulateFeature(feature); + + // Assert + Assert.Empty(feature.Controllers); + } + + private static ApplicationPartManager GetApplicationPartManager(params TypeInfo[] types) + { + var manager = new ApplicationPartManager(); + manager.ApplicationParts.Add(new TestApplicationPart(types)); + manager.FeatureProviders.Add(new ControllerFeatureProvider()); + + return manager; + } + } +} + +// These controllers are used to test the ControllerFeatureProvider implementation +// which REQUIRES that they be public top-level classes. To avoid having to stub out the +// implementation of this class to test it, they are just top level classes. Don't reuse +// these outside this test - find a better way or use nested classes to keep the tests +// independent. +namespace Microsoft.AspNetCore.Mvc.ControllerFeatureProviderControllers +{ + public abstract class AbstractController : Controller + { + } + + public class DerivedAbstractController : AbstractController + { + } + + public class StoreController : Controller + { + } + + public class ProductsController : ControllerBase + { + } + + public class Products : ControllerBase + { + } + + [Controller] + public abstract class Controller + { + } + + public abstract class NoControllerAttributeBaseController + { + } + + public class NoSuffixNoControllerAttribute : NoControllerAttributeBaseController + { + } + + public class OpenGenericController : Controller + { + } + + public class DerivedGenericController : OpenGenericController + { + } + + public interface ITestController + { + } + + public class NoSuffix : Controller + { + } + + public class NoSuffixPoco + { + + } + + public class PocoController + { + } + + [Controller] + public class CustomBase + { + + } + + [Controller] + public abstract class CustomAbstractBaseController + { + + } + + public class DescendantLevel1 : CustomBase + { + + } + + public class DescendantLevel2 : DescendantLevel1 + { + + } + + public class AbstractChildWithoutSuffix : CustomAbstractBaseController + { + + } + + [NonController] + public class BasePocoNonControllerController + { + + } + + [Controller] + public class BaseNonControllerAttributeChildControllerControllerAttributeController : BaseNonControllerController + { + + } + + 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.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerTypeProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerTypeProviderTest.cs deleted file mode 100644 index 0a0df2d162..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerTypeProviderTest.cs +++ /dev/null @@ -1,433 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Reflection; -using Microsoft.AspNetCore.Mvc.DefaultControllerTypeProviderControllers; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Controllers -{ - public class DefaultControllerTypeProviderTest - { - private static readonly ISet CandidateAssemblies = new HashSet - { - typeof(StoreController).GetTypeInfo().Assembly - }; - - [Fact] - public void IsController_UserDefinedClass_DerivedFromController() - { - // Arrange - var typeInfo = typeof(StoreController).GetTypeInfo(); - var provider = GetControllerTypeProvider(); - - // Act - var isController = provider.IsController(typeInfo); - - // Assert - Assert.True(isController); - } - - [Fact] - public void IsController_UserDefinedClass_DerivedFromControllerBase() - { - // Arrange - var typeInfo = typeof(ProductsController).GetTypeInfo(); - var provider = GetControllerTypeProvider(); - - // Act - var isController = provider.IsController(typeInfo); - - // Assert - Assert.True(isController); - } - - [Fact] - public void IsController_UserDefinedClass_DerivedFromControllerBase_WithoutSuffix() - { - // Arrange - var typeInfo = typeof(Products).GetTypeInfo(); - var provider = GetControllerTypeProvider(); - - // Act - var isController = provider.IsController(typeInfo); - - // Assert - Assert.True(isController); - } - - [Fact] - public void IsController_FrameworkControllerClass() - { - // Arrange - var typeInfo = typeof(Controller).GetTypeInfo(); - var provider = GetControllerTypeProvider(); - - // Act - var isController = provider.IsController(typeInfo); - - // Assert - Assert.False(isController); - } - - [Fact] - public void IsController_FrameworkBaseControllerClass() - { - // Arrange - var typeInfo = typeof(Controller).GetTypeInfo(); - var provider = GetControllerTypeProvider(); - - // Act - var isController = provider.IsController(typeInfo); - - // Assert - Assert.False(isController); - } - - [Fact] - public void IsController_UserDefinedControllerClass() - { - // Arrange - var typeInfo = typeof(DefaultControllerTypeProviderControllers.Controller).GetTypeInfo(); - var provider = GetControllerTypeProvider(); - - // Act - var isController = provider.IsController(typeInfo); - - // Assert - Assert.False(isController); - } - - [Fact] - public void IsController_Interface() - { - // Arrange - var typeInfo = typeof(IController).GetTypeInfo(); - var provider = GetControllerTypeProvider(); - - // Act - var isController = provider.IsController(typeInfo); - - // Assert - Assert.False(isController); - } - - [Fact] - public void IsController_AbstractClass() - { - // Arrange - var typeInfo = typeof(AbstractController).GetTypeInfo(); - var provider = GetControllerTypeProvider(); - - // Act - var isController = provider.IsController(typeInfo); - - // Assert - Assert.False(isController); - } - - [Fact] - public void IsController_DerivedAbstractClass() - { - // Arrange - var typeInfo = typeof(DerivedAbstractController).GetTypeInfo(); - var provider = GetControllerTypeProvider(); - - // Act - var isController = provider.IsController(typeInfo); - - // Assert - Assert.True(isController); - } - - [Fact] - public void IsController_OpenGenericClass() - { - // Arrange - var typeInfo = typeof(OpenGenericController<>).GetTypeInfo(); - var provider = GetControllerTypeProvider(); - - // Act - var isController = provider.IsController(typeInfo); - - // Assert - Assert.False(isController); - } - - [Fact] - public void IsController_WithoutSuffixOrAncestorWithController() - { - // Arrange - var typeInfo = typeof(NoSuffixPoco).GetTypeInfo(); - var provider = GetControllerTypeProvider(); - - // Act - var isController = provider.IsController(typeInfo); - - // Assert - Assert.False(isController); - } - - [Fact] - public void IsController_ClosedGenericClass() - { - // Arrange - var typeInfo = typeof(OpenGenericController).GetTypeInfo(); - var provider = GetControllerTypeProvider(); - - // Act - var isController = provider.IsController(typeInfo); - - // Assert - Assert.True(isController); - } - - [Fact] - public void IsController_DerivedGenericClass() - { - // Arrange - var typeInfo = typeof(DerivedGenericController).GetTypeInfo(); - var provider = GetControllerTypeProvider(); - - // Act - var isController = provider.IsController(typeInfo); - - // Assert - Assert.True(isController); - } - - [Fact] - public void IsController_Poco_WithNamingConvention() - { - // Arrange - var typeInfo = typeof(PocoController).GetTypeInfo(); - var provider = GetControllerTypeProvider(); - - // Act - var isController = provider.IsController(typeInfo); - - // Assert - Assert.True(isController); - } - - [Fact] - public void IsController_NoControllerSuffix() - { - // Arrange - var typeInfo = typeof(NoSuffix).GetTypeInfo(); - var provider = GetControllerTypeProvider(); - - // Act - var isController = provider.IsController(typeInfo); - - // Assert - Assert.True(isController); - } - - [Theory] - [InlineData(typeof(DescendantLevel1))] - [InlineData(typeof(DescendantLevel2))] - public void IsController_ReturnsTrue_IfAncestorTypeHasControllerAttribute(Type type) - { - // Arrange - var provider = GetControllerTypeProvider(); - - // Act - var isController = provider.IsController(type.GetTypeInfo()); - - // Assert - Assert.True(isController); - } - - [Fact] - public void IsController_ReturnsFalse_IfAncestorTypeDoesNotHaveControllerAttribute() - { - // Arrange - var provider = GetControllerTypeProvider(); - - // Act - var isController = provider.IsController(typeof(NoSuffixNoControllerAttribute).GetTypeInfo()); - - // Assert - Assert.False(isController); - } - - [Theory] - [InlineData(typeof(BaseNonControllerController))] - [InlineData(typeof(BaseNonControllerControllerChild))] - [InlineData(typeof(BasePocoNonControllerController))] - [InlineData(typeof(BasePocoNonControllerControllerChild))] - [InlineData(typeof(NonController))] - [InlineData(typeof(NonControllerChild))] - [InlineData(typeof(BaseNonControllerAttributeChildControllerControllerAttributeController))] - [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()); - - // Assert - Assert.False(isController); - } - - private static DefaultControllerTypeProvider GetControllerTypeProvider() - { - var assemblyProvider = new StaticAssemblyProvider(); - return new DefaultControllerTypeProvider(assemblyProvider); - } - } -} - -// These controllers are used to test the DefaultControllerTypeProvider implementation -// which REQUIRES that they be public top-level classes. To avoid having to stub out the -// implementation of this class to test it, they are just top level classes. Don't reuse -// these outside this test - find a better way or use nested classes to keep the tests -// independent. -namespace Microsoft.AspNetCore.Mvc.DefaultControllerTypeProviderControllers -{ - public abstract class AbstractController : Controller - { - } - - public class DerivedAbstractController : AbstractController - { - } - - public class StoreController : Controller - { - } - - public class ProductsController : ControllerBase - { - } - - public class Products : ControllerBase - { - } - - [Controller] - public abstract class Controller - { - } - - public abstract class NoControllerAttributeBaseController - { - } - - public class NoSuffixNoControllerAttribute : NoControllerAttributeBaseController - { - } - - public class OpenGenericController : Controller - { - } - - public class DerivedGenericController : OpenGenericController - { - } - - public interface IController - { - } - - public class NoSuffix : Controller - { - } - - public class NoSuffixPoco - { - - } - - public class PocoController - { - } - - [Controller] - public class CustomBase - { - - } - - [Controller] - public abstract class CustomAbstractBaseController - { - - } - - public class DescendantLevel1 : CustomBase - { - - } - - public class DescendantLevel2 : DescendantLevel1 - { - - } - - public class AbstractChildWithoutSuffix : CustomAbstractBaseController - { - - } - - [NonController] - public class BasePocoNonControllerController - { - - } - - public class BasePocoNonControllerControllerChild : BasePocoNonControllerController - { - - } - - [NonController] - public class BaseNonControllerController : Controller - { - - } - - [Controller] - public class BaseNonControllerAttributeChildControllerControllerAttributeController : BaseNonControllerController - { - - } - - 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.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs index 853330b643..1aa3240f6f 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Mvc.ApplicationParts; @@ -42,7 +43,7 @@ public void ConfigureApplicationParts_InvokesSetupAction() Mock.Of(), new ApplicationPartManager()); - var part = new TestPart(); + var part = new TestApplicationPart(); // Act var result = builder.ConfigureApplicationPartManager(manager => @@ -59,22 +60,21 @@ public void ConfigureApplicationParts_InvokesSetupAction() public void WithControllersAsServices_AddsTypesToControllerTypeProviderAndServiceCollection() { // Arrange - var builder = new Mock(); var collection = new ServiceCollection(); - builder.SetupGet(b => b.Services).Returns(collection); - var controllerTypes = new[] { typeof(ControllerTypeA), typeof(TypeBController), - }; + }.Select(t => t.GetTypeInfo()).ToArray(); + + var builder = new MvcBuilder(collection, GetApplicationPartManager(controllerTypes)); // Act - builder.Object.AddControllersAsServices(controllerTypes); + builder.AddControllersAsServices(); // Assert var services = collection.ToList(); - Assert.Equal(4, services.Count); + Assert.Equal(3, services.Count); Assert.Equal(typeof(ControllerTypeA), services[0].ServiceType); Assert.Equal(typeof(ControllerTypeA), services[0].ImplementationType); Assert.Equal(ServiceLifetime.Transient, services[0].Lifetime); @@ -86,16 +86,45 @@ public void WithControllersAsServices_AddsTypesToControllerTypeProviderAndServic Assert.Equal(typeof(IControllerActivator), services[2].ServiceType); Assert.Equal(typeof(ServiceBasedControllerActivator), services[2].ImplementationType); Assert.Equal(ServiceLifetime.Transient, services[2].Lifetime); + } + + [Fact] + public void AddControllerAsServices_MultipleCalls_RetainsPreviouslyAddedTypes() + { + // Arrange + var services = new ServiceCollection(); + var manager = new ApplicationPartManager(); + manager.ApplicationParts.Add(new TestApplicationPart(typeof(ControllerOne), typeof(ControllerTwo))); + manager.FeatureProviders.Add(new TestFeatureProvider()); + var builder = new MvcBuilder(services, manager); - Assert.Equal(typeof(IControllerTypeProvider), services[3].ServiceType); - var typeProvider = Assert.IsType(services[3].ImplementationInstance); - Assert.Equal(controllerTypes, typeProvider.ControllerTypes.OrderBy(c => c.Name).Select(t => t.AsType())); - Assert.Equal(ServiceLifetime.Singleton, services[3].Lifetime); + builder.AddControllersAsServices(); + + // Act + builder.AddControllersAsServices(); + + // Assert 2 + var collection = services.ToList(); + Assert.Equal(3, collection.Count); + Assert.Single(collection, d => d.ServiceType.Equals(typeof(ControllerOne))); + Assert.Single(collection, d => d.ServiceType.Equals(typeof(ControllerTwo))); } - private class TestPart : ApplicationPart + private class ControllerOne { - public override string Name => "Test"; + } + + private class ControllerTwo + { + } + + private static ApplicationPartManager GetApplicationPartManager(params TypeInfo[] types) + { + var manager = new ApplicationPartManager(); + manager.ApplicationParts.Add(new TestApplicationPart(types)); + manager.FeatureProviders.Add(new ControllerFeatureProvider()); + + return manager; } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreBuilderExtensionsTest.cs index 1cf271d476..4fc6d41572 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreBuilderExtensionsTest.cs @@ -39,7 +39,7 @@ public void ConfigureApplicationParts_InvokesSetupAction() Mock.Of(), new ApplicationPartManager()); - var part = new TestPart(); + var part = new TestApplicationPart(); // Act var result = builder.ConfigureApplicationPartManager(manager => @@ -51,10 +51,5 @@ public void ConfigureApplicationParts_InvokesSetupAction() Assert.Same(result, builder); Assert.Equal(new ApplicationPart[] { part }, builder.PartManager.ApplicationParts.ToArray()); } - - private class TestPart : ApplicationPart - { - public override string Name => "Test"; - } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultActionSelectorTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultActionSelectorTests.cs index fbbca07087..0bcf969a9c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultActionSelectorTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultActionSelectorTests.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Routing; @@ -545,17 +546,26 @@ private ControllerActionDescriptorProvider GetActionDescriptorProvider() var options = new TestOptionsManager(); - var controllerTypeProvider = new StaticControllerTypeProvider(controllerTypes); + var manager = GetApplicationManager(controllerTypes); + var modelProvider = new DefaultApplicationModelProvider(options); var provider = new ControllerActionDescriptorProvider( - controllerTypeProvider, + manager, new[] { modelProvider }, options); return provider; } + private static ApplicationPartManager GetApplicationManager(List controllerTypes) + { + var manager = new ApplicationPartManager(); + manager.ApplicationParts.Add(new TestApplicationPart(controllerTypes)); + manager.FeatureProviders.Add(new TestFeatureProvider()); + return manager; + } + private static HttpContext GetHttpContext(string httpMethod) { var httpContext = new DefaultHttpContext(); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs index e9f4a0962d..d953c8fed6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -1449,11 +1450,12 @@ private ControllerActionDescriptorProvider GetProvider( } } - var controllerTypeProvider = new StaticControllerTypeProvider(new[] { controllerTypeInfo }); + var manager = GetApplicationManager(new[] { controllerTypeInfo }); + var modelProvider = new DefaultApplicationModelProvider(options); var provider = new ControllerActionDescriptorProvider( - controllerTypeProvider, + manager, new[] { modelProvider }, options); @@ -1465,11 +1467,11 @@ private ControllerActionDescriptorProvider GetProvider( { var options = new TestOptionsManager(); - var controllerTypeProvider = new StaticControllerTypeProvider(controllerTypeInfos); + var manager = GetApplicationManager(controllerTypeInfos); var modelProvider = new DefaultApplicationModelProvider(options); var provider = new ControllerActionDescriptorProvider( - controllerTypeProvider, + manager, new[] { modelProvider }, options); @@ -1483,17 +1485,26 @@ private ControllerActionDescriptorProvider GetProvider( var options = new TestOptionsManager(); options.Value.Conventions.Add(convention); - var controllerTypeProvider = new StaticControllerTypeProvider(new[] { controllerTypeInfo }); + var manager = GetApplicationManager(new[] { controllerTypeInfo }); + var modelProvider = new DefaultApplicationModelProvider(options); var provider = new ControllerActionDescriptorProvider( - controllerTypeProvider, + manager, new[] { modelProvider }, options); return provider; } + private static ApplicationPartManager GetApplicationManager(IEnumerable controllerTypes) + { + var manager = new ApplicationPartManager(); + manager.ApplicationParts.Add(new TestApplicationPart(controllerTypes)); + manager.FeatureProviders.Add(new TestFeatureProvider()); + return manager; + } + private IEnumerable GetDescriptors(params TypeInfo[] controllerTypeInfos) { var provider = GetProvider(controllerTypeInfos); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerAsServicesTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerAsServicesTest.cs deleted file mode 100644 index 22998e7e1c..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerAsServicesTest.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Internal -{ - public class ControllerAsServicesTest - { - [Fact] - public void AddControllerAsServices_MultipleCalls_RetainsPreviouslyAddedTypes() - { - // Arrange - var services = new ServiceCollection(); - - // Act 1 - ControllersAsServices.AddControllersAsServices(services, new Type[] { typeof(ControllerOne) }); - - // Assert 1 - var serviceDescriptor = Assert.Single(services, s => s.ServiceType == typeof(IControllerTypeProvider)); - var controllerTypeProvider = Assert.IsType(serviceDescriptor.ImplementationInstance); - var expectedControllerType = Assert.Single(controllerTypeProvider.ControllerTypes); - - // Act 2 - ControllersAsServices.AddControllersAsServices(services, new Type[] { typeof(ControllerTwo) }); - - // Assert 2 - serviceDescriptor = Assert.Single(services, s => s.ServiceType == typeof(IControllerTypeProvider)); - controllerTypeProvider = Assert.IsType(serviceDescriptor.ImplementationInstance); - Assert.Equal(2, controllerTypeProvider.ControllerTypes.Count); - Assert.Same(expectedControllerType, controllerTypeProvider.ControllerTypes[0]); - Assert.Same(typeof(ControllerTwo), controllerTypeProvider.ControllerTypes[1]); - } - - private class ControllerOne - { - } - - private class ControllerTwo - { - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs index cc8ee08b06..4e8eed2d3d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs @@ -1067,7 +1067,8 @@ public void SetProperty_PropertySetterThrows_CapturesException() private static TestableComplexTypeModelBinder CreateBinder(ModelMetadata metadata) { var options = new TestOptionsManager(); - MvcCoreMvcOptionsSetup.ConfigureMvc(options.Value, new TestHttpRequestStreamReaderFactory()); + var setup = new MvcCoreMvcOptionsSetup(new TestHttpRequestStreamReaderFactory()); + setup.Configure(options.Value); var lastIndex = options.Value.ModelBinderProviders.Count - 1; Assert.IsType(options.Value.ModelBinderProviders[lastIndex]); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/TestApplicationPart.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/TestApplicationPart.cs new file mode 100644 index 0000000000..b8541d776b --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/TestApplicationPart.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +using Microsoft.AspNetCore.Mvc.ApplicationParts; + +namespace Microsoft.AspNetCore.Mvc +{ + public class TestApplicationPart : ApplicationPart, IApplicationPartTypeProvider + { + public TestApplicationPart() + { + Types = Enumerable.Empty(); + } + + public TestApplicationPart(params TypeInfo[] types) + { + Types = types; + } + + public TestApplicationPart(IEnumerable types) + { + Types = types; + } + + public TestApplicationPart(IEnumerable types) + :this(types.Select(t => t.GetTypeInfo())) + { + } + + public TestApplicationPart(params Type[] types) + : this(types.Select(t => t.GetTypeInfo())) + { + } + + public override string Name => "Test part"; + + public IEnumerable Types { get; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/TestFeatureProvider.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/TestFeatureProvider.cs new file mode 100644 index 0000000000..77c87709a4 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/TestFeatureProvider.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Controllers; + +namespace Microsoft.AspNetCore.Mvc +{ + public class TestFeatureProvider : IApplicationFeatureProvider + { + private readonly Func _filter; + + public TestFeatureProvider() + : this(t => true) + { + } + + public TestFeatureProvider(Func filter) + { + _filter = filter; + } + + public void PopulateFeature(IEnumerable parts, ControllerFeature feature) + { + foreach (var type in parts.OfType().SelectMany(t => t.Types).Where(_filter)) + { + feature.Controllers.Add(type); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/MvcTestFixture.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/MvcTestFixture.cs index f700b403a0..33a74a5712 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/MvcTestFixture.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/MvcTestFixture.cs @@ -10,8 +10,6 @@ using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Razor.Compilation; -using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.TestHost; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; @@ -74,6 +72,7 @@ protected virtual void InitializeServices(IServiceCollection services) var manager = new ApplicationPartManager(); manager.ApplicationParts.Add(new AssemblyPart(startupAssembly)); + manager.FeatureProviders.Add(new ControllerFeatureProvider()); services.AddSingleton(manager); } diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs b/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs index e66d33ad7a..a0f894e836 100644 --- a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Internal; @@ -371,9 +372,7 @@ public void GetActions_Parameters_ImplicitOptional(string name) private ControllerActionDescriptorProvider CreateProvider() { - var assemblyProvider = new StaticAssemblyProvider(); - assemblyProvider.CandidateAssemblies.Add(GetType().GetTypeInfo().Assembly); - var controllerTypeProvider = new NamespaceFilteredControllerTypeProvider(assemblyProvider); + var manager = GetApplicationManager(GetType().GetTypeInfo().Assembly.DefinedTypes.ToArray()); var options = new MvcOptions(); @@ -393,7 +392,7 @@ private ControllerActionDescriptorProvider CreateProvider() var modelProvider = new DefaultApplicationModelProvider(optionsAccessor.Object); var provider = new ControllerActionDescriptorProvider( - controllerTypeProvider, + manager, new[] { modelProvider }, optionsAccessor.Object); @@ -406,20 +405,49 @@ private void Invoke(ControllerActionDescriptorProvider provider, ActionDescripto provider.OnProvidersExecuted(context); } - private class NamespaceFilteredControllerTypeProvider : DefaultControllerTypeProvider + private static ApplicationPartManager GetApplicationManager(params TypeInfo[] controllerTypes) { - public NamespaceFilteredControllerTypeProvider(IAssemblyProvider provider) - : base(provider) + var manager = new ApplicationPartManager(); + manager.ApplicationParts.Add(new TestPart(controllerTypes)); + manager.FeatureProviders.Add(new TestProvider()); + manager.FeatureProviders.Add(new NamespaceFilteredControllersFeatureProvider()); + return manager; + } + + private class TestPart : ApplicationPart, IApplicationPartTypeProvider + { + public TestPart(IEnumerable types) { + Types = types; + } + + public override string Name => "Test"; + + public IEnumerable Types { get; } + } + private class TestProvider : IApplicationFeatureProvider + { + public void PopulateFeature(IEnumerable parts, ControllerFeature feature) + { + foreach (var type in parts.OfType().SelectMany(t => t.Types)) + { + feature.Controllers.Add(type); + } } + } - public override IEnumerable ControllerTypes + private class NamespaceFilteredControllersFeatureProvider : IApplicationFeatureProvider + { + public void PopulateFeature(IEnumerable parts, ControllerFeature feature) { - get + var controllers = feature.Controllers.ToList(); + foreach (var controller in controllers) { - return base.ControllerTypes - .Where(typeInfo => typeInfo.Namespace == "System.Web.Http.TestControllers"); + if (controller.Namespace != "System.Web.Http.TestControllers") + { + feature.Controllers.Remove(controller); + } } } } diff --git a/test/WebSites/ControllersFromServicesWebSite/Startup.cs b/test/WebSites/ControllersFromServicesWebSite/Startup.cs index eb2057849d..6db36073d3 100644 --- a/test/WebSites/ControllersFromServicesWebSite/Startup.cs +++ b/test/WebSites/ControllersFromServicesWebSite/Startup.cs @@ -1,12 +1,16 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Collections.Generic; +using System.Linq; using System.Reflection; using ControllersFromServicesClassLibrary; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Internal; +using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Extensions.DependencyInjection; namespace ControllersFromServicesWebSite @@ -15,18 +19,28 @@ public class Startup { public void ConfigureServices(IServiceCollection services) { - services + var builder = services .AddMvc() - .AddControllersAsServices(typeof(AnotherController)) - .AddControllersAsServices(new[] - { - typeof(TimeScheduleController).GetTypeInfo().Assembly - }); + .ConfigureApplicationPartManager(manager => manager.ApplicationParts.Clear()) + .AddApplicationPart(typeof(TimeScheduleController).GetTypeInfo().Assembly) + .ConfigureApplicationPartManager(manager => manager.ApplicationParts.Add(new TypesPart(typeof(AnotherController)))); services.AddTransient(); services.AddSingleton(); } + private class TypesPart : ApplicationPart, IApplicationPartTypeProvider + { + public TypesPart(params Type[] types) + { + Types = types.Select(t => t.GetTypeInfo()); + } + + public override string Name => string.Join(", ", Types.Select(t => t.FullName)); + + public IEnumerable Types { get; } + } + public void Configure(IApplicationBuilder app) { app.UseCultureReplacer();