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 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts /// 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 @@ namespace Microsoft.Extensions.DependencyInjection } /// - /// 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; - } + 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 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)); - } - - 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 @@ namespace Microsoft.Extensions.DependencyInjection } /// - /// 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 @@ namespace Microsoft.Extensions.DependencyInjection 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..01e6b741ea 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -21,7 +21,6 @@ using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; -using Microsoft.Extensions.PlatformAbstractions; namespace Microsoft.Extensions.DependencyInjection { @@ -45,6 +44,7 @@ namespace Microsoft.Extensions.DependencyInjection var partManager = GetApplicationPartManager(services); services.TryAddSingleton(partManager); + ConfigureDefaultFeatureProviders(partManager); ConfigureDefaultServices(services); AddMvcCoreServices(services); @@ -53,6 +53,14 @@ namespace Microsoft.Extensions.DependencyInjection return builder; } + private static void ConfigureDefaultFeatureProviders(ApplicationPartManager manager) + { + if (!manager.FeatureProviders.OfType().Any()) + { + manager.FeatureProviders.Add(new ControllerFeatureProvider()); + } + } + private static ApplicationPartManager GetApplicationPartManager(IServiceCollection services) { var manager = GetServiceFromCollection(services); @@ -126,7 +134,6 @@ namespace Microsoft.Extensions.DependencyInjection // 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 @@ namespace Microsoft.AspNetCore.Mvc.Internal throw new ArgumentNullException(nameof(optionsAccessor)); } - _controllerTypeProvider = controllerTypeProvider; + _partManager = partManager; _applicationModelProviders = applicationModelProviders.OrderBy(p => p.Order).ToArray(); _conventions = optionsAccessor.Value.Conventions; } @@ -75,7 +77,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal 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 @@ namespace Microsoft.AspNetCore.Mvc.Internal 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/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 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts 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 @@ namespace Microsoft.AspNetCore.Mvc Mock.Of(), new ApplicationPartManager()); - var part = new TestPart(); + var part = new TestApplicationPart(); // Act var result = builder.ConfigureApplicationPartManager(manager => @@ -59,22 +60,21 @@ namespace Microsoft.AspNetCore.Mvc 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 @@ namespace Microsoft.AspNetCore.Mvc Assert.Equal(typeof(IControllerActivator), services[2].ServiceType); Assert.Equal(typeof(ServiceBasedControllerActivator), services[2].ImplementationType); Assert.Equal(ServiceLifetime.Transient, services[2].Lifetime); - - 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); } - private class TestPart : ApplicationPart + [Fact] + public void AddControllerAsServices_MultipleCalls_RetainsPreviouslyAddedTypes() { - public override string Name => "Test"; + // 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); + + 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 ControllerOne + { + } + + 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 @@ namespace Microsoft.AspNetCore.Mvc.DependencyInjection Mock.Of(), new ApplicationPartManager()); - var part = new TestPart(); + var part = new TestApplicationPart(); // Act var result = builder.ConfigureApplicationPartManager(manager => @@ -51,10 +51,5 @@ namespace Microsoft.AspNetCore.Mvc.DependencyInjection 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 7de2df1f72..366ec69656 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultActionSelectorTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultActionSelectorTests.cs @@ -8,6 +8,8 @@ using System.Reflection; using Microsoft.AspNetCore.Http; 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; @@ -543,17 +545,26 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure 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.Abstractions; 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 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - 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 @@ namespace Microsoft.AspNetCore.Mvc.Internal { 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 @@ namespace Microsoft.AspNetCore.Mvc.Internal 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/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.Hosting; 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 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests 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; 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 @@ namespace System.Web.Http 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 @@ namespace System.Web.Http var modelProvider = new DefaultApplicationModelProvider(optionsAccessor.Object); var provider = new ControllerActionDescriptorProvider( - controllerTypeProvider, + manager, new[] { modelProvider }, optionsAccessor.Object); @@ -406,20 +405,49 @@ namespace System.Web.Http 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 IEnumerable ControllerTypes + public override string Name => "Test"; + + public IEnumerable Types { get; } + } + + private class TestProvider : IApplicationFeatureProvider + { + public void PopulateFeature(IEnumerable parts, ControllerFeature feature) { - get + foreach (var type in parts.OfType().SelectMany(t => t.Types)) { - return base.ControllerTypes - .Where(typeInfo => typeInfo.Namespace == "System.Web.Http.TestControllers"); + feature.Controllers.Add(type); + } + } + } + + private class NamespaceFilteredControllersFeatureProvider : IApplicationFeatureProvider + { + public void PopulateFeature(IEnumerable parts, ControllerFeature feature) + { + var controllers = feature.Controllers.ToList(); + foreach (var controller in controllers) + { + 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 03e9d5b1aa..7339ad8400 100644 --- a/test/WebSites/ControllersFromServicesWebSite/Startup.cs +++ b/test/WebSites/ControllersFromServicesWebSite/Startup.cs @@ -1,11 +1,15 @@ // 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.Mvc.ApplicationParts; using Microsoft.Extensions.DependencyInjection; namespace ControllersFromServicesWebSite @@ -14,18 +18,29 @@ namespace ControllersFromServicesWebSite { 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)))) + .AddControllersAsServices(); 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();