[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.
This commit is contained in:
jacalvar 2016-03-24 01:40:42 -07:00
parent 50af106b6a
commit 0d0aad41f5
24 changed files with 928 additions and 862 deletions

View File

@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
/// <summary>
/// An <see cref="ApplicationPart"/> backed by an <see cref="Assembly"/>.
/// </summary>
public class AssemblyPart : ApplicationPart
public class AssemblyPart : ApplicationPart, IApplicationPartTypeProvider
{
/// <summary>
/// Initalizes a new <see cref="AssemblyPart"/> instance.
@ -35,5 +35,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
/// Gets the name of the <see cref="ApplicationPart"/>.
/// </summary>
public override string Name => Assembly.GetName().Name;
/// <inheritdoc />
public IEnumerable<TypeInfo> Types => Assembly.DefinedTypes;
}
}

View File

@ -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
{
/// <summary>
/// Exposes a set of types from an <see cref="ApplicationPart"/>.
/// </summary>
public interface IApplicationPartTypeProvider
{
/// <summary>
/// Gets the list of available types in the <see cref="ApplicationPart"/>.
/// </summary>
IEnumerable<TypeInfo> Types { get; }
}
}

View File

@ -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
{
/// <summary>
/// The list of controllers types in an MVC application. The <see cref="ControllerFeature"/> can be populated
/// using the <see cref="ApplicationPartManager"/> that is available during startup at <see cref="IMvcBuilder.PartManager"/>
/// and <see cref="IMvcCoreBuilder.PartManager"/> or at a later stage by requiring the <see cref="ApplicationPartManager"/>
/// as a dependency in a component.
/// </summary>
public class ControllerFeature
{
/// <summary>
/// Gets the list of controller types in an MVC application.
/// </summary>
public IList<TypeInfo> Controllers { get; } = new List<TypeInfo>();
}
}

View File

@ -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
{
/// <summary>
/// Discovers controllers from a list of <see cref="ApplicationPart"/> instances.
/// </summary>
public class ControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
private const string ControllerTypeNameSuffix = "Controller";
/// <inheritdoc />
public void PopulateFeature(
IEnumerable<ApplicationPart> parts,
ControllerFeature feature)
{
foreach (var part in parts.OfType<IApplicationPartTypeProvider>())
{
foreach (var type in part.Types)
{
if (IsController(type) && !feature.Controllers.Contains(type))
{
feature.Controllers.Add(type);
}
}
}
}
/// <summary>
/// Determines if a given <paramref name="typeInfo"/> is a controller.
/// </summary>
/// <param name="typeInfo">The <see cref="TypeInfo"/> candidate.</param>
/// <returns><code>true</code> if the type is a controller; otherwise <code>false</code>.</returns>
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;
}
}
}

View File

@ -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
{
/// <summary>
/// A <see cref="IControllerTypeProvider"/> that identifies controller types from assemblies
/// specified by the registered <see cref="IAssemblyProvider"/>.
/// </summary>
public class DefaultControllerTypeProvider : IControllerTypeProvider
{
private const string ControllerTypeName = "Controller";
private static readonly TypeInfo ObjectTypeInfo = typeof(object).GetTypeInfo();
private readonly IAssemblyProvider _assemblyProvider;
/// <summary>
/// Initializes a new instance of <see cref="DefaultControllerTypeProvider"/>.
/// </summary>
/// <param name="assemblyProvider"><see cref="IAssemblyProvider"/> that provides assemblies to look for
/// controllers in.</param>
public DefaultControllerTypeProvider(IAssemblyProvider assemblyProvider)
{
_assemblyProvider = assemblyProvider;
}
/// <inheritdoc />
public virtual IEnumerable<TypeInfo> ControllerTypes
{
get
{
var candidateAssemblies = new HashSet<Assembly>(_assemblyProvider.CandidateAssemblies);
var types = candidateAssemblies.SelectMany(a => a.DefinedTypes);
return types.Where(typeInfo => IsController(typeInfo));
}
}
/// <summary>
/// Returns <c>true</c> if the <paramref name="typeInfo"/> is a controller. Otherwise <c>false</c>.
/// </summary>
/// <param name="typeInfo">The <see cref="TypeInfo"/>.</param>
/// <returns><c>true</c> if the <paramref name="typeInfo"/> is a controller. Otherwise <c>false</c>.</returns>
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;
}
}
}

View File

@ -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
{
/// <summary>
/// A <see cref="IControllerTypeProvider"/> with a fixed set of types that are used as controllers.
/// </summary>
public class StaticControllerTypeProvider : IControllerTypeProvider
{
/// <summary>
/// Initializes a new instance of <see cref="StaticControllerTypeProvider"/>.
/// </summary>
public StaticControllerTypeProvider()
: this(Enumerable.Empty<TypeInfo>())
{
}
/// <summary>
/// Initializes a new instance of <see cref="StaticControllerTypeProvider"/>.
/// </summary>
/// <param name="controllerTypes">The sequence of controller <see cref="TypeInfo"/>.</param>
public StaticControllerTypeProvider(IEnumerable<TypeInfo> controllerTypes)
{
if (controllerTypes == null)
{
throw new ArgumentNullException(nameof(controllerTypes));
}
ControllerTypes = new List<TypeInfo>(controllerTypes);
}
/// <summary>
/// Gets the list of controller <see cref="TypeInfo"/>s.
/// </summary>
public IList<TypeInfo> ControllerTypes { get; }
/// <inheritdoc />
IEnumerable<TypeInfo> IControllerTypeProvider.ControllerTypes => ControllerTypes;
}
}

View File

@ -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
}
/// <summary>
/// Register the specified <paramref name="controllerTypes"/> as services and as a source for controller
/// discovery.
/// Registers discovered controllers as services in the <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
/// <param name="controllerTypes">A sequence of controller <see cref="Type"/>s to register.</param>
/// <returns>The <see cref="IMvcBuilder"/>.</returns>
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);
/// <summary>
/// Register the specified <paramref name="controllerTypes"/> as services and as a source for controller
/// discovery.
/// </summary>
/// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
/// <param name="controllerTypes">A sequence of controller <see cref="Type"/>s to register.</param>
/// <returns>The <see cref="IMvcBuilder"/>.</returns>
public static IMvcBuilder AddControllersAsServices(
this IMvcBuilder builder,
IEnumerable<Type> 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<IControllerActivator, ServiceBasedControllerActivator>());
/// <summary>
/// Registers controller types from the specified <paramref name="controllerAssemblies"/> as services and as a source
/// for controller discovery.
/// </summary>
/// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
/// <param name="controllerAssemblies">Assemblies to scan.</param>
/// <returns>The <see cref="IMvcBuilder"/>.</returns>
public static IMvcBuilder AddControllersAsServices(
this IMvcBuilder builder,
params Assembly[] controllerAssemblies)
{
return builder.AddControllersAsServices(controllerAssemblies.AsEnumerable());
}
/// <summary>
/// Registers controller types from the specified <paramref name="controllerAssemblies"/> as services and as a source
/// for controller discovery.
/// </summary>
/// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
/// <param name="controllerAssemblies">Assemblies to scan.</param>
/// <returns>The <see cref="IMvcBuilder"/>.</returns>
public static IMvcBuilder AddControllersAsServices(
this IMvcBuilder builder,
IEnumerable<Assembly> controllerAssemblies)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
ControllersAsServices.AddControllersAsServices(builder.Services, controllerAssemblies);
return builder;
}
}

View File

@ -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
}
/// <summary>
/// Register the specified <paramref name="controllerTypes"/> as services and as a source for controller
/// discovery.
/// Registers discovered controllers as services in the <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="builder">The <see cref="IMvcCoreBuilder"/>.</param>
/// <param name="controllerTypes">A sequence of controller <see cref="Type"/>s to register.</param>
/// <returns>The <see cref="IMvcCoreBuilder"/>.</returns>
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);
/// <summary>
/// Register the specified <paramref name="controllerTypes"/> as services and as a source for controller
/// discovery.
/// </summary>
/// <param name="builder">The <see cref="IMvcCoreBuilder"/>.</param>
/// <param name="controllerTypes">A sequence of controller <see cref="Type"/>s to register.</param>
/// <returns>The <see cref="IMvcCoreBuilder"/>.</returns>
public static IMvcCoreBuilder AddControllersAsServices(
this IMvcCoreBuilder builder,
IEnumerable<Type> 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<IControllerActivator, ServiceBasedControllerActivator>());
/// <summary>
/// Registers controller types from the specified <paramref name="controllerAssemblies"/> as services and as a source
/// for controller discovery.
/// </summary>
/// <param name="builder">The <see cref="IMvcCoreBuilder"/>.</param>
/// <param name="controllerAssemblies">Assemblies to scan.</param>
/// <returns>The <see cref="IMvcCoreBuilder"/>.</returns>
public static IMvcCoreBuilder AddControllersAsServices(
this IMvcCoreBuilder builder,
params Assembly[] controllerAssemblies)
{
return builder.AddControllersAsServices(controllerAssemblies.AsEnumerable());
return builder;
}
/// <summary>
@ -193,25 +165,5 @@ namespace Microsoft.Extensions.DependencyInjection
return builder;
}
/// <summary>
/// Registers controller types from the specified <paramref name="controllerAssemblies"/> as services and as a source
/// for controller discovery.
/// </summary>
/// <param name="builder">The <see cref="IMvcCoreBuilder"/>.</param>
/// <param name="controllerAssemblies">Assemblies to scan.</param>
/// <returns>The <see cref="IMvcCoreBuilder"/>.</returns>
public static IMvcCoreBuilder AddControllersAsServices(
this IMvcCoreBuilder builder,
IEnumerable<Assembly> controllerAssemblies)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
ControllersAsServices.AddControllersAsServices(builder.Services, controllerAssemblies);
return builder;
}
}
}

View File

@ -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<ControllerFeatureProvider>().Any())
{
manager.FeatureProviders.Add(new ControllerFeatureProvider());
}
}
private static ApplicationPartManager GetApplicationPartManager(IServiceCollection services)
{
var manager = GetServiceFromCollection<ApplicationPartManager>(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<IAssemblyProvider, DefaultAssemblyProvider>();
services.TryAddTransient<IControllerTypeProvider, DefaultControllerTypeProvider>();
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApplicationModelProvider, DefaultApplicationModelProvider>());
services.TryAddEnumerable(

View File

@ -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<IApplicationModelConvention> _conventions;
public ControllerActionDescriptorProvider(
IControllerTypeProvider controllerTypeProvider,
ApplicationPartManager partManager,
IEnumerable<IApplicationModelProvider> applicationModelProviders,
IOptions<MvcOptions> 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<TypeInfo> GetControllerTypes()
{
var feature = new ControllerFeature();
_partManager.PopulateFeature(feature);
return feature.Controllers;
}
}
}

View File

@ -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<Type> 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<StaticControllerTypeProvider>()
.FirstOrDefault()
?? new StaticControllerTypeProvider();
foreach (var type in types)
{
services.TryAddTransient(type, type);
controllerTypeProvider.ControllerTypes.Add(type.GetTypeInfo());
}
services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
services.Replace(ServiceDescriptor.Singleton<IControllerTypeProvider>(controllerTypeProvider));
}
public static void AddControllersAsServices(IServiceCollection services, IEnumerable<Assembly> 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()));
}
}
}

View File

@ -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()
{

View File

@ -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<string>).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<ApplicationPart>();
otherPart
.As<IApplicationPartTypeProvider>()
.Setup(t => t.Types)
.Returns(new[] { typeof(PocoController).GetTypeInfo() });
var parts = new[] {
Mock.Of<ApplicationPart>(),
new TestApplicationPart(typeof(NoSuffix).GetTypeInfo()),
otherPart.Object
};
var feature = new ControllerFeature();
var expected = new List<TypeInfo>
{
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<ApplicationPart>();
otherPart
.As<IApplicationPartTypeProvider>()
.Setup(t => t.Types)
.Returns(new[] { typeof(PocoController).GetTypeInfo() });
var parts = new[] {
Mock.Of<ApplicationPart>(),
new TestApplicationPart(typeof(NoSuffix)),
otherPart.Object
};
var feature = new ControllerFeature();
var expected = new List<TypeInfo>
{
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<T> : Controller
{
}
public class DerivedGenericController : OpenGenericController<string>
{
}
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
{
}
}

View File

@ -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<Assembly> CandidateAssemblies = new HashSet<Assembly>
{
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<string>).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<T> : Controller
{
}
public class DerivedGenericController : OpenGenericController<string>
{
}
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
{
}
}

View File

@ -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<IServiceCollection>(),
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<IMvcBuilder>();
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<StaticControllerTypeProvider>(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;
}
}
}

View File

@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Mvc.DependencyInjection
Mock.Of<IServiceCollection>(),
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";
}
}
}

View File

@ -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<MvcOptions>();
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<TypeInfo> 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();

View File

@ -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<MvcOptions>();
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<MvcOptions>();
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<TypeInfo> controllerTypes)
{
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new TestApplicationPart(controllerTypes));
manager.FeatureProviders.Add(new TestFeatureProvider());
return manager;
}
private IEnumerable<ActionDescriptor> GetDescriptors(params TypeInfo[] controllerTypeInfos)
{
var provider = GetProvider(controllerTypeInfos);

View File

@ -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<StaticControllerTypeProvider>(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<StaticControllerTypeProvider>(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
{
}
}
}

View File

@ -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<TypeInfo>();
}
public TestApplicationPart(params TypeInfo[] types)
{
Types = types;
}
public TestApplicationPart(IEnumerable<TypeInfo> types)
{
Types = types;
}
public TestApplicationPart(IEnumerable<Type> 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<TypeInfo> Types { get; }
}
}

View File

@ -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<ControllerFeature>
{
private readonly Func<TypeInfo, bool> _filter;
public TestFeatureProvider()
: this(t => true)
{
}
public TestFeatureProvider(Func<TypeInfo, bool> filter)
{
_filter = filter;
}
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
foreach (var type in parts.OfType<IApplicationPartTypeProvider>().SelectMany(t => t.Types).Where(_filter))
{
feature.Controllers.Add(type);
}
}
}
}

View File

@ -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);
}

View File

@ -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<TypeInfo> types)
{
Types = types;
}
public override IEnumerable<TypeInfo> ControllerTypes
public override string Name => "Test";
public IEnumerable<TypeInfo> Types { get; }
}
private class TestProvider : IApplicationFeatureProvider<ControllerFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
get
foreach (var type in parts.OfType<IApplicationPartTypeProvider>().SelectMany(t => t.Types))
{
return base.ControllerTypes
.Where(typeInfo => typeInfo.Namespace == "System.Web.Http.TestControllers");
feature.Controllers.Add(type);
}
}
}
private class NamespaceFilteredControllersFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
var controllers = feature.Controllers.ToList();
foreach (var controller in controllers)
{
if (controller.Namespace != "System.Web.Http.TestControllers")
{
feature.Controllers.Remove(controller);
}
}
}
}

View File

@ -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<QueryValueService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
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<TypeInfo> Types { get; }
}
public void Configure(IApplicationBuilder app)
{
app.UseCultureReplacer();