diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPart.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPart.cs
new file mode 100644
index 0000000000..807af25a9d
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPart.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Mvc.ApplicationParts
+{
+ ///
+ /// A part of an MVC application.
+ ///
+ public abstract class ApplicationPart
+ {
+ ///
+ /// Gets the name.
+ ///
+ public abstract string Name { get; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartManager.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartManager.cs
new file mode 100644
index 0000000000..4d0c190931
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartManager.cs
@@ -0,0 +1,47 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Mvc.ApplicationParts
+{
+ ///
+ /// Manages the parts and features of an MVC application.
+ ///
+ public class ApplicationPartManager
+ {
+ ///
+ /// Gets the list of s.
+ ///
+ public IList FeatureProviders { get; } =
+ new List();
+
+ ///
+ /// Gets the list of s.
+ ///
+ public IList ApplicationParts { get; } =
+ new List();
+
+ ///
+ /// Populates the given using the list of
+ /// s configured on the
+ /// .
+ ///
+ /// The type of the feature.
+ /// The feature instance to populate.
+ public void PopulateFeature(TFeature feature)
+ {
+ if (feature == null)
+ {
+ throw new ArgumentNullException(nameof(feature));
+ }
+
+ foreach (var provider in FeatureProviders.OfType>())
+ {
+ provider.PopulateFeature(ApplicationParts, feature);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs
new file mode 100644
index 0000000000..0147f59eb7
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs
@@ -0,0 +1,39 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Mvc.ApplicationParts
+{
+ ///
+ /// An backed by an .
+ ///
+ public class AssemblyPart : ApplicationPart
+ {
+ ///
+ /// Initalizes a new instance.
+ ///
+ ///
+ public AssemblyPart(Assembly assembly)
+ {
+ if (assembly == null)
+ {
+ throw new ArgumentNullException(nameof(assembly));
+ }
+
+ Assembly = assembly;
+ }
+
+ ///
+ /// Gets the of the .
+ ///
+ public Assembly Assembly { get; }
+
+ ///
+ /// Gets the name of the .
+ ///
+ public override string Name => Assembly.GetName().Name;
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProvider.cs
new file mode 100644
index 0000000000..2ee9ece9f0
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProvider.cs
@@ -0,0 +1,13 @@
+// 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.
+
+namespace Microsoft.AspNetCore.Mvc.ApplicationParts
+{
+ ///
+ /// Marker interface for
+ /// implementations.
+ ///
+ public interface IApplicationFeatureProvider
+ {
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProviderOfT.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProviderOfT.cs
new file mode 100644
index 0000000000..557c6287af
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProviderOfT.cs
@@ -0,0 +1,23 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Mvc.ApplicationParts
+{
+ ///
+ /// A provider for a given feature.
+ ///
+ /// The type of the feature.
+ public interface IApplicationFeatureProvider : IApplicationFeatureProvider
+ {
+ ///
+ /// Updates the intance.
+ ///
+ /// The list of s of the
+ /// application.
+ ///
+ /// The feature instance to populate.
+ void PopulateFeature(IEnumerable parts, TFeature feature);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcBuilder.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcBuilder.cs
index 5e12b9da62..c1419690a4 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcBuilder.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcBuilder.cs
@@ -1,6 +1,8 @@
// 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 Microsoft.AspNetCore.Mvc.ApplicationParts;
+
namespace Microsoft.Extensions.DependencyInjection
{
///
@@ -12,5 +14,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// Gets the where MVC services are configured.
///
IServiceCollection Services { get; }
+
+ ///
+ /// Gets the where s
+ /// are configured.
+ ///
+ ApplicationPartManager PartManager { get; }
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcCoreBuilder.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcCoreBuilder.cs
index 4c621afe1b..9263ebb514 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcCoreBuilder.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcCoreBuilder.cs
@@ -1,6 +1,8 @@
// 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 Microsoft.AspNetCore.Mvc.ApplicationParts;
+
namespace Microsoft.Extensions.DependencyInjection
{
///
@@ -12,5 +14,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// Gets the where essential MVC services are configured.
///
IServiceCollection Services { get; }
+
+ ///
+ /// Gets the where s
+ /// are configured.
+ ///
+ ApplicationPartManager PartManager { get; }
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs
index 4c9c359b77..4c57152edc 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs
@@ -3,11 +3,12 @@
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.Formatters;
using Microsoft.AspNetCore.Mvc.Internal;
-using System.Linq;
namespace Microsoft.Extensions.DependencyInjection
{
@@ -58,6 +59,56 @@ namespace Microsoft.Extensions.DependencyInjection
return builder;
}
+ ///
+ /// Adds an to the list of on the
+ /// .
+ ///
+ /// The .
+ /// The of the .
+ /// The .
+ public static IMvcBuilder AddApplicationPart(this IMvcBuilder builder, Assembly assembly)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ if (assembly == null)
+ {
+ throw new ArgumentNullException(nameof(assembly));
+ }
+
+ builder.ConfigureApplicationPartManager(manager => manager.ApplicationParts.Add(new AssemblyPart(assembly)));
+
+ return builder;
+ }
+
+ ///
+ /// Configures the of the using
+ /// the given .
+ ///
+ /// The .
+ /// The
+ /// The .
+ public static IMvcBuilder ConfigureApplicationPartManager(
+ this IMvcBuilder builder,
+ Action setupAction)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ if (setupAction == null)
+ {
+ throw new ArgumentNullException(nameof(setupAction));
+ }
+
+ setupAction(builder.PartManager);
+
+ return builder;
+ }
+
///
/// Register the specified as services and as a source for controller
/// discovery.
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs
index c4d57c6530..3ef94e1343 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs
@@ -3,14 +3,15 @@
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.Formatters;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.DependencyInjection.Extensions;
-using System.Linq;
namespace Microsoft.Extensions.DependencyInjection
{
@@ -143,6 +144,56 @@ namespace Microsoft.Extensions.DependencyInjection
return builder.AddControllersAsServices(controllerAssemblies.AsEnumerable());
}
+ ///
+ /// Adds an to the list of on the
+ /// .
+ ///
+ /// The .
+ /// The of the .
+ /// The .
+ public static IMvcCoreBuilder AddApplicationPart(this IMvcCoreBuilder builder, Assembly assembly)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ if (assembly == null)
+ {
+ throw new ArgumentNullException(nameof(assembly));
+ }
+
+ builder.ConfigureApplicationPartManager(manager => manager.ApplicationParts.Add(new AssemblyPart(assembly)));
+
+ return builder;
+ }
+
+ ///
+ /// Configures the of the using
+ /// the given .
+ ///
+ /// The .
+ /// The
+ /// The .
+ public static IMvcCoreBuilder ConfigureApplicationPartManager(
+ this IMvcCoreBuilder builder,
+ Action setupAction)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ if (setupAction == null)
+ {
+ throw new ArgumentNullException(nameof(setupAction));
+ }
+
+ setupAction(builder.PartManager);
+
+ return builder;
+ }
+
///
/// Registers controller types from the specified as services and as a source
/// for controller discovery.
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs
index 36d978c428..6c21cf5c65 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs
@@ -3,10 +3,13 @@
using System;
using System.Buffers;
+using System.Linq;
+using Microsoft.AspNetCore.Hosting;
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.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
@@ -39,10 +42,45 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentNullException(nameof(services));
}
+ var partManager = GetApplicationPartManager(services);
+ services.TryAddSingleton(partManager);
+
ConfigureDefaultServices(services);
AddMvcCoreServices(services);
- return new MvcCoreBuilder(services);
+ var builder = new MvcCoreBuilder(services, partManager);
+
+ return builder;
+ }
+
+ private static ApplicationPartManager GetApplicationPartManager(IServiceCollection services)
+ {
+ var manager = GetServiceFromCollection(services);
+ if (manager == null)
+ {
+ manager = new ApplicationPartManager();
+
+ var environment = GetServiceFromCollection(services);
+ if (environment == null)
+ {
+ return manager;
+ }
+
+ var assemblies = new DefaultAssemblyProvider(environment).CandidateAssemblies;
+ foreach (var assembly in assemblies)
+ {
+ manager.ApplicationParts.Add(new AssemblyPart(assembly));
+ }
+ }
+
+ return manager;
+ }
+
+ private static T GetServiceFromCollection(IServiceCollection services)
+ {
+ return (T)services
+ .FirstOrDefault(d => d.ServiceType == typeof(T))
+ ?.ImplementationInstance;
}
///
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcBuilder.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcBuilder.cs
index 84cc8468eb..d2a0db532e 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcBuilder.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcBuilder.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc.Internal
@@ -15,17 +16,27 @@ namespace Microsoft.AspNetCore.Mvc.Internal
/// Initializes a new instance.
///
/// The to add services to.
- public MvcBuilder(IServiceCollection services)
+ /// The of the application.
+ public MvcBuilder(IServiceCollection services, ApplicationPartManager manager)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
+ if (manager == null)
+ {
+ throw new ArgumentNullException(nameof(manager));
+ }
+
Services = services;
+ PartManager = manager;
}
///
public IServiceCollection Services { get; }
+
+ ///
+ public ApplicationPartManager PartManager { get; }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreBuilder.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreBuilder.cs
index eb64507a4e..5f2560179a 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreBuilder.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreBuilder.cs
@@ -2,6 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc.ApplicationParts;
+using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc.Internal
@@ -12,19 +15,31 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public class MvcCoreBuilder : IMvcCoreBuilder
{
///
- /// Initializes a new instance of .
+ /// Initializes a new instance.
///
/// The to add services to.
- public MvcCoreBuilder(IServiceCollection services)
+ /// The of the application.
+ public MvcCoreBuilder(
+ IServiceCollection services,
+ ApplicationPartManager manager)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
+ if (manager == null)
+ {
+ throw new ArgumentNullException(nameof(manager));
+ }
+
Services = services;
+ PartManager = manager;
}
+ ///
+ public ApplicationPartManager PartManager { get; }
+
///
public IServiceCollection Services { get; }
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
index 44ddbea572..537a82a16c 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
@@ -16,14 +16,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal
///
/// Sets up default options for .
///
- public class MvcCoreMvcOptionsSetup : ConfigureOptions
+ public class MvcCoreMvcOptionsSetup : IConfigureOptions
{
+ private readonly IHttpRequestStreamReaderFactory _readerFactory;
+
public MvcCoreMvcOptionsSetup(IHttpRequestStreamReaderFactory readerFactory)
- : base((options) => ConfigureMvc(options, readerFactory))
{
+ if (readerFactory == null)
+ {
+ throw new ArgumentNullException(nameof(readerFactory));
+ }
+
+ _readerFactory = readerFactory;
}
- public static void ConfigureMvc(MvcOptions options, IHttpRequestStreamReaderFactory readerFactory)
+ public void Configure(MvcOptions options)
{
// Set up default error messages
var messageProvider = options.ModelBindingMessageProvider;
@@ -38,7 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Set up ModelBinding
options.ModelBinderProviders.Add(new BinderTypeModelBinderProvider());
options.ModelBinderProviders.Add(new ServicesModelBinderProvider());
- options.ModelBinderProviders.Add(new BodyModelBinderProvider(readerFactory));
+ options.ModelBinderProviders.Add(new BodyModelBinderProvider(_readerFactory));
options.ModelBinderProviders.Add(new HeaderModelBinderProvider());
options.ModelBinderProviders.Add(new SimpleTypeModelBinderProvider());
options.ModelBinderProviders.Add(new CancellationTokenModelBinderProvider());
diff --git a/src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs
index 0dfe509e96..355478f12a 100644
--- a/src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs
@@ -45,7 +45,7 @@ namespace Microsoft.Extensions.DependencyInjection
builder.AddCors();
- return new MvcBuilder(builder.Services);
+ return new MvcBuilder(builder.Services, builder.PartManager);
}
///
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationPartManagerTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationPartManagerTest.cs
new file mode 100644
index 0000000000..27735d2693
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationPartManagerTest.cs
@@ -0,0 +1,150 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.ApplicationParts
+{
+ public class ApplicationPartManagerTest
+ {
+ [Fact]
+ public void PopulateFeature_InvokesAllProvidersSequentially_ForAGivenFeature()
+ {
+ // Arrange
+ var manager = new ApplicationPartManager();
+ manager.ApplicationParts.Add(new ControllersPart("ControllersPartA"));
+ manager.ApplicationParts.Add(new ViewComponentsPart("ViewComponentsPartB"));
+ manager.ApplicationParts.Add(new ControllersPart("ControllersPartC"));
+ manager.FeatureProviders.Add(
+ new ControllersFeatureProvider((f, v) => f.Values.Add($"ControllersFeatureProvider1{v}")));
+ manager.FeatureProviders.Add(
+ new ControllersFeatureProvider((f, v) => f.Values.Add($"ControllersFeatureProvider2{v}")));
+
+ var feature = new ControllersFeature();
+ var expectedResults = new[] {
+ "ControllersFeatureProvider1ControllersPartA",
+ "ControllersFeatureProvider1ControllersPartC",
+ "ControllersFeatureProvider2ControllersPartA",
+ "ControllersFeatureProvider2ControllersPartC"
+ };
+
+ // Act
+ manager.PopulateFeature(feature);
+
+ // Assert
+ Assert.Equal(expectedResults, feature.Values.ToArray());
+ }
+
+ [Fact]
+ public void PopulateFeature_InvokesOnlyProviders_ForAGivenFeature()
+ {
+ // Arrange
+ var manager = new ApplicationPartManager();
+ manager.ApplicationParts.Add(new ControllersPart("ControllersPart"));
+ manager.FeatureProviders.Add(
+ new ControllersFeatureProvider((f, v) => f.Values.Add($"ControllersFeatureProvider{v}")));
+ manager.FeatureProviders.Add(
+ new NotControllersedFeatureProvider((f, v) => f.Values.Add($"ViewComponentsFeatureProvider{v}")));
+
+ var feature = new ControllersFeature();
+ var expectedResults = new[] { "ControllersFeatureProviderControllersPart" };
+
+ // Act
+ manager.PopulateFeature(feature);
+
+ // Assert
+ Assert.Equal(expectedResults, feature.Values.ToArray());
+ }
+
+ [Fact]
+ public void PopulateFeature_SkipProviders_ForOtherFeatures()
+ {
+ // Arrange
+ var manager = new ApplicationPartManager();
+ manager.ApplicationParts.Add(new ViewComponentsPart("ViewComponentsPart"));
+ manager.FeatureProviders.Add(
+ new ControllersFeatureProvider((f, v) => f.Values.Add($"ControllersFeatureProvider{v}")));
+
+ var feature = new ControllersFeature();
+
+ // Act
+ manager.PopulateFeature(feature);
+
+ // Assert
+ Assert.Empty(feature.Values.ToArray());
+ }
+
+ private class ControllersPart : ApplicationPart
+ {
+ public ControllersPart(string value)
+ {
+ Value = value;
+ }
+
+ public override string Name => "Test";
+
+ public string Value { get; }
+ }
+
+ private class ViewComponentsPart : ApplicationPart
+ {
+ public ViewComponentsPart(string value)
+ {
+ Value = value;
+ }
+
+ public override string Name => "Other";
+
+ public string Value { get; }
+ }
+
+ private class ControllersFeature
+ {
+ public IList Values { get; } = new List();
+ }
+
+ private class ViewComponentsFeature
+ {
+ public IList Values { get; } = new List();
+ }
+
+ private class NotControllersedFeatureProvider : IApplicationFeatureProvider
+ {
+ private readonly Action _operation;
+
+ public NotControllersedFeatureProvider(Action operation)
+ {
+ _operation = operation;
+ }
+
+ public void PopulateFeature(IEnumerable parts, ViewComponentsFeature feature)
+ {
+ foreach (var part in parts.OfType())
+ {
+ _operation(feature, part.Value);
+ }
+ }
+ }
+
+ private class ControllersFeatureProvider : IApplicationFeatureProvider
+ {
+ private readonly Action _operation;
+
+ public ControllersFeatureProvider(Action operation)
+ {
+ _operation = operation;
+ }
+
+ public void PopulateFeature(IEnumerable parts, ControllersFeature feature)
+ {
+ foreach (var part in parts.OfType())
+ {
+ _operation(feature, part.Value);
+ }
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/AssemblyPartTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/AssemblyPartTest.cs
new file mode 100644
index 0000000000..591026fa5d
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/AssemblyPartTest.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.Reflection;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.ApplicationParts
+{
+ public class AssemblyPartTest
+ {
+ [Fact]
+ public void AssemblyPart_Name_ReturnsAssemblyName()
+ {
+ // Arrange
+ var part = new AssemblyPart(typeof(AssemblyPartTest).GetTypeInfo().Assembly);
+
+ // Act
+ var name = part.Name;
+
+ // Assert
+ Assert.Equal("Microsoft.AspNetCore.Mvc.Core.Test", name);
+ }
+
+ [Fact]
+ public void AssemblyPart_Assembly_ReturnsAssembly()
+ {
+ // Arrange
+ var assembly = typeof(AssemblyPartTest).GetTypeInfo().Assembly;
+ var part = new AssemblyPart(assembly);
+
+ // Act & Assert
+ Assert.Equal(part.Assembly, assembly);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs
index 3c1f1f611e..853330b643 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs
@@ -3,7 +3,9 @@
using System.Linq;
using System.Reflection;
+using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.MvcServiceCollectionExtensionsTestControllers;
using Microsoft.Extensions.DependencyInjection;
using Moq;
@@ -13,6 +15,46 @@ namespace Microsoft.AspNetCore.Mvc
{
public class MvcBuilderExtensionsTest
{
+ [Fact]
+ public void AddApplicationPart_AddsAnApplicationPart_ToTheListOfPartsOnTheBuilder()
+ {
+ // Arrange
+ var manager = new ApplicationPartManager();
+ var builder = new MvcBuilder(Mock.Of(), manager);
+
+ var assembly = typeof(MvcBuilder).GetTypeInfo().Assembly;
+
+ // Act
+ var result = builder.AddApplicationPart(assembly);
+
+ // Assert
+ Assert.Same(result, builder);
+ var part = Assert.Single(builder.PartManager.ApplicationParts);
+ var assemblyPart = Assert.IsType(part);
+ Assert.Equal(assembly, assemblyPart.Assembly);
+ }
+
+ [Fact]
+ public void ConfigureApplicationParts_InvokesSetupAction()
+ {
+ // Arrange
+ var builder = new MvcBuilder(
+ Mock.Of(),
+ new ApplicationPartManager());
+
+ var part = new TestPart();
+
+ // Act
+ var result = builder.ConfigureApplicationPartManager(manager =>
+ {
+ manager.ApplicationParts.Add(part);
+ });
+
+ // Assert
+ Assert.Same(result, builder);
+ Assert.Equal(new ApplicationPart[] { part }, builder.PartManager.ApplicationParts.ToArray());
+ }
+
[Fact]
public void WithControllersAsServices_AddsTypesToControllerTypeProviderAndServiceCollection()
{
@@ -50,6 +92,11 @@ namespace Microsoft.AspNetCore.Mvc
Assert.Equal(controllerTypes, typeProvider.ControllerTypes.OrderBy(c => c.Name).Select(t => t.AsType()));
Assert.Equal(ServiceLifetime.Singleton, services[3].Lifetime);
}
+
+ private class TestPart : ApplicationPart
+ {
+ public override string Name => "Test";
+ }
}
}
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreBuilderExtensionsTest.cs
new file mode 100644
index 0000000000..1cf271d476
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreBuilderExtensionsTest.cs
@@ -0,0 +1,60 @@
+// 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.Linq;
+using System.Reflection;
+using Microsoft.AspNetCore.Mvc.ApplicationParts;
+using Microsoft.AspNetCore.Mvc.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.DependencyInjection
+{
+ public class MvcCoreBuilderExtensionsTest
+ {
+ [Fact]
+ public void AddApplicationPart_AddsAnApplicationPart_ToTheListOfPartsOnTheBuilder()
+ {
+ // Arrange
+ var manager = new ApplicationPartManager();
+ var builder = new MvcCoreBuilder(Mock.Of(), manager);
+ var assembly = typeof(MvcCoreBuilder).GetTypeInfo().Assembly;
+
+ // Act
+ var result = builder.AddApplicationPart(assembly);
+
+ // Assert
+ Assert.Same(result, builder);
+ var part = Assert.Single(builder.PartManager.ApplicationParts);
+ var assemblyPart = Assert.IsType(part);
+ Assert.Equal(assembly, assemblyPart.Assembly);
+ }
+
+ [Fact]
+ public void ConfigureApplicationParts_InvokesSetupAction()
+ {
+ // Arrange
+ var builder = new MvcCoreBuilder(
+ Mock.Of(),
+ new ApplicationPartManager());
+
+ var part = new TestPart();
+
+ // Act
+ var result = builder.ConfigureApplicationPartManager(manager =>
+ {
+ manager.ApplicationParts.Add(part);
+ });
+
+ // Assert
+ 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/ModelBinding/Binders/ComplexTypeModelBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs
index 9079bf0544..0c965860f3 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs
@@ -362,7 +362,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
var bindingContext = CreateContext(GetMetadataForType(typeof(Person)), new Person());
var originalModel = bindingContext.Model;
- var binder = new Mock(){ CallBase = true };
+ var binder = new Mock() { CallBase = true };
binder
.Setup(b => b.CreateModelPublic(It.IsAny()))
.Verifiable();
@@ -1067,7 +1067,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
private static TestableComplexTypeModelBinder CreateBinder(ModelMetadata metadata)
{
var options = new TestOptionsManager();
- MvcCoreMvcOptionsSetup.ConfigureMvc(options.Value, new TestHttpRequestStreamReaderFactory());
+ var setup = new MvcCoreMvcOptionsSetup(new TestHttpRequestStreamReaderFactory());
+ setup.Configure(options.Value);
var lastIndex = options.Value.ModelBinderProviders.Count - 1;
Assert.IsType(options.Value.ModelBinderProviders[lastIndex]);
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/MvcTestFixture.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/MvcTestFixture.cs
index a41b6f0d25..f700b403a0 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/MvcTestFixture.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/MvcTestFixture.cs
@@ -2,10 +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.IO;
using System.Net.Http;
using System.Reflection;
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;
@@ -68,6 +71,17 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
var assemblyProvider = new StaticAssemblyProvider();
assemblyProvider.CandidateAssemblies.Add(startupAssembly);
services.AddSingleton(assemblyProvider);
+
+ var manager = new ApplicationPartManager();
+ manager.ApplicationParts.Add(new AssemblyPart(startupAssembly));
+ services.AddSingleton(manager);
+ }
+
+ private class StaticAssemblyProvider : IAssemblyProvider
+ {
+ public IList CandidateAssemblies { get; } = new List();
+
+ IEnumerable IAssemblyProvider.CandidateAssemblies => CandidateAssemblies;
}
}
}
diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs
index 12a3769f53..326643696e 100644
--- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs
+++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs
@@ -3,6 +3,7 @@
using System;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
@@ -99,6 +100,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
private static IServiceProvider GetServices(Action updateOptions = null)
{
var serviceCollection = new ServiceCollection();
+ serviceCollection.AddSingleton(new ApplicationPartManager());
serviceCollection.AddMvc();
serviceCollection
.AddSingleton()
diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TestMvcOptions.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TestMvcOptions.cs
index 52a97647b5..9815f15eeb 100644
--- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TestMvcOptions.cs
+++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TestMvcOptions.cs
@@ -5,6 +5,7 @@ using System.Buffers;
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal;
+using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
@@ -20,7 +21,9 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
public TestMvcOptions()
{
Value = new MvcOptions();
- MvcCoreMvcOptionsSetup.ConfigureMvc(Value, new TestHttpRequestStreamReaderFactory());
+ var optionsSetup = new MvcCoreMvcOptionsSetup(new TestHttpRequestStreamReaderFactory());
+ optionsSetup.Configure(Value);
+
var collection = new ServiceCollection().AddOptions();
collection.AddSingleton();
collection.AddSingleton();
diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperActivatorTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperActivatorTest.cs
index 5ccc549f04..eee737ee0b 100644
--- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperActivatorTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperActivatorTest.cs
@@ -2,8 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
+using System.Reflection;
+using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
+using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperFactoryTest.cs
index 2d40c3b4a1..28c2d4da61 100644
--- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperFactoryTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperFactoryTest.cs
@@ -4,8 +4,8 @@
using System;
using System.IO;
using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc.Abstractions;
+using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
@@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
{
// Arrange
var services = new ServiceCollection();
- var builder = new MvcCoreBuilder(services);
+ var builder = new MvcCoreBuilder(services, new ApplicationPartManager());
builder.InitializeTagHelper((h, vc) =>
{
h.Name = name;
@@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
{
// Arrange
var services = new ServiceCollection();
- var builder = new MvcCoreBuilder(services);
+ var builder = new MvcCoreBuilder(services, new ApplicationPartManager());
builder.InitializeTagHelper((h, _) => h.ViewContext = MakeViewContext(MakeHttpContext()));
var httpContext = MakeHttpContext(services.BuildServiceProvider());
var viewContext = MakeViewContext(httpContext);
@@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
{
// Arrange
var services = new ServiceCollection();
- var builder = new MvcCoreBuilder(services);
+ var builder = new MvcCoreBuilder(services, new ApplicationPartManager());
builder.InitializeTagHelper((h, vc) =>
{
h.Name = "Test 1";
@@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
{
// Arrange
var services = new ServiceCollection();
- var builder = new MvcCoreBuilder(services);
+ var builder = new MvcCoreBuilder(services, new ApplicationPartManager());
builder.InitializeTagHelper((h, vc) =>
{
h.Name = "Test 1";
diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs
index 0a0f53cd26..bd6b59f1ca 100644
--- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineOptionsTest.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 Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@@ -18,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
var fileProvider = new TestFileProvider();
// Act
- var builder = new MvcBuilder(services);
+ var builder = new MvcBuilder(services, new ApplicationPartManager());
builder.AddRazorOptions(options =>
{
options.FileProviders.Add(fileProvider);
diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs
index a270bbc19e..4ae1d69223 100644
--- a/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs
@@ -9,6 +9,7 @@ using System.Xml;
using System.Xml.Linq;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Internal;
@@ -148,7 +149,7 @@ namespace Microsoft.AspNetCore.Mvc
// Arrange & Act
var options = GetOptions(services =>
{
- var builder = new MvcCoreBuilder(services);
+ var builder = new MvcCoreBuilder(services, new ApplicationPartManager());
builder.AddXmlDataContractSerializerFormatters();
});
@@ -238,6 +239,7 @@ namespace Microsoft.AspNetCore.Mvc
private static IServiceProvider GetServiceProvider(Action action = null)
{
var serviceCollection = new ServiceCollection();
+ serviceCollection.AddSingleton(new ApplicationPartManager());
serviceCollection.AddMvc();
serviceCollection
.AddSingleton()
diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs
index d7ae2a0a57..ff4492cc08 100644
--- a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
@@ -41,6 +42,7 @@ namespace Microsoft.AspNetCore.Mvc
{
// Arrange
var services = new ServiceCollection();
+ services.AddSingleton(GetHostingEnvironment());
// Register a mock implementation of each service, AddMvcServices should add another implemenetation.
foreach (var serviceType in MutliRegistrationServiceTypes)
@@ -69,6 +71,7 @@ namespace Microsoft.AspNetCore.Mvc
{
// Arrange
var services = new ServiceCollection();
+ services.AddSingleton(GetHostingEnvironment());
// Register a mock implementation of each service, AddMvcServices should not replace it.
foreach (var serviceType in SingleRegistrationServiceTypes)
@@ -92,6 +95,7 @@ namespace Microsoft.AspNetCore.Mvc
{
// Arrange
var services = new ServiceCollection();
+ services.AddSingleton(GetHostingEnvironment());
// Act
services.AddMvc();
@@ -123,6 +127,7 @@ namespace Microsoft.AspNetCore.Mvc
get
{
var services = new ServiceCollection();
+ services.AddSingleton(GetHostingEnvironment());
services.AddMvc();
var multiRegistrationServiceTypes = MutliRegistrationServiceTypes;
@@ -266,5 +271,15 @@ namespace Microsoft.AspNetCore.Mvc
$"Found multiple instances of {implementationType} registered as {serviceType}");
}
}
+
+ private IHostingEnvironment GetHostingEnvironment()
+ {
+ var environment = new Mock();
+ environment
+ .Setup(e => e.ApplicationName)
+ .Returns(typeof(MvcServiceCollectionExtensionsTest).GetTypeInfo().Assembly.GetName().Name);
+
+ return environment.Object;
+ }
}
}