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; + } } }