[Fixes #4089] Add support for application parts

This commit introduces application parts as a concept on MVC.

An application part is an abstraction that allows you to expose some
feature or corncern in a way that is decoupled from their underlying source.
Examples of this include types in an assembly, emdeded resources, files on
disk etc.

Application parts are configured during startup by adding or removing them from
the application part manager available as part of IMvcBuilder and IMvcCoreBuilder.

The application part manager provides the ability to populate features from the
list of available application parts by using a list of application feature providers.
Application feature providers are responsible for populating a given feature given a
list of application parts.

Examples of application providers can be a ControllerFeatureProvider
that goes through the list of application parts, sees which one of those parts exposes types,
determines which of those types are controller types, and adds them to a ControllerFeature
that holds a list of all the types that will be considered controllers in the application.
This commit is contained in:
jacalvar 2016-03-19 03:00:32 -07:00
parent 7fbd407ad5
commit 1bd66ffda0
27 changed files with 681 additions and 21 deletions

View File

@ -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
{
/// <summary>
/// A part of an MVC application.
/// </summary>
public abstract class ApplicationPart
{
/// <summary>
/// Gets the <see cref="ApplicationPart"/> name.
/// </summary>
public abstract string Name { get; }
}
}

View File

@ -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
{
/// <summary>
/// Manages the parts and features of an MVC application.
/// </summary>
public class ApplicationPartManager
{
/// <summary>
/// Gets the list of <see cref="IApplicationFeatureProvider"/>s.
/// </summary>
public IList<IApplicationFeatureProvider> FeatureProviders { get; } =
new List<IApplicationFeatureProvider>();
/// <summary>
/// Gets the list of <see cref="ApplicationPart"/>s.
/// </summary>
public IList<ApplicationPart> ApplicationParts { get; } =
new List<ApplicationPart>();
/// <summary>
/// Populates the given <paramref name="feature"/> using the list of
/// <see cref="IApplicationFeatureProvider{TFeature}"/>s configured on the
/// <see cref="ApplicationPartManager"/>.
/// </summary>
/// <typeparam name="TFeature">The type of the feature.</typeparam>
/// <param name="feature">The feature instance to populate.</param>
public void PopulateFeature<TFeature>(TFeature feature)
{
if (feature == null)
{
throw new ArgumentNullException(nameof(feature));
}
foreach (var provider in FeatureProviders.OfType<IApplicationFeatureProvider<TFeature>>())
{
provider.PopulateFeature(ApplicationParts, feature);
}
}
}
}

View File

@ -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
{
/// <summary>
/// An <see cref="ApplicationPart"/> backed by an <see cref="Assembly"/>.
/// </summary>
public class AssemblyPart : ApplicationPart
{
/// <summary>
/// Initalizes a new <see cref="AssemblyPart"/> instance.
/// </summary>
/// <param name="assembly"></param>
public AssemblyPart(Assembly assembly)
{
if (assembly == null)
{
throw new ArgumentNullException(nameof(assembly));
}
Assembly = assembly;
}
/// <summary>
/// Gets the <see cref="Assembly"/> of the <see cref="ApplicationPart"/>.
/// </summary>
public Assembly Assembly { get; }
/// <summary>
/// Gets the name of the <see cref="ApplicationPart"/>.
/// </summary>
public override string Name => Assembly.GetName().Name;
}
}

View File

@ -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
{
/// <summary>
/// Marker interface for <see cref="IApplicationFeatureProvider"/>
/// implementations.
/// </summary>
public interface IApplicationFeatureProvider
{
}
}

View File

@ -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
{
/// <summary>
/// A provider for a given <typeparamref name="TFeature"/> feature.
/// </summary>
/// <typeparam name="TFeature">The type of the feature.</typeparam>
public interface IApplicationFeatureProvider<TFeature> : IApplicationFeatureProvider
{
/// <summary>
/// Updates the <paramref name="feature"/> intance.
/// </summary>
/// <param name="parts">The list of <see cref="ApplicationPart"/>s of the
/// application.
/// </param>
/// <param name="feature">The feature instance to populate.</param>
void PopulateFeature(IEnumerable<ApplicationPart> parts, TFeature feature);
}
}

View File

@ -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
{
/// <summary>
@ -12,5 +14,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// Gets the <see cref="IServiceCollection"/> where MVC services are configured.
/// </summary>
IServiceCollection Services { get; }
/// <summary>
/// Gets the <see cref="ApplicationPartManager"/> where <see cref="ApplicationPart"/>s
/// are configured.
/// </summary>
ApplicationPartManager PartManager { get; }
}
}

View File

@ -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
{
/// <summary>
@ -12,5 +14,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// Gets the <see cref="IServiceCollection"/> where essential MVC services are configured.
/// </summary>
IServiceCollection Services { get; }
/// <summary>
/// Gets the <see cref="ApplicationPartManager"/> where <see cref="ApplicationPart"/>s
/// are configured.
/// </summary>
ApplicationPartManager PartManager { get; }
}
}

View File

@ -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;
}
/// <summary>
/// Adds an <see cref="ApplicationPart"/> to the list of <see cref="ApplicationPartManager.ApplicationParts"/> on the
/// <see cref="IMvcBuilder.PartManager"/>.
/// </summary>
/// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
/// <param name="assembly">The <see cref="Assembly"/> of the <see cref="ApplicationPart"/>.</param>
/// <returns>The <see cref="IMvcBuilder"/>.</returns>
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;
}
/// <summary>
/// Configures the <see cref="ApplicationPartManager"/> of the <see cref="IMvcBuilder.PartManager"/> using
/// the given <see cref="Action{ApplicationPartManager}"/>.
/// </summary>
/// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
/// <param name="setupAction">The <see cref="Action{ApplicationPartManager}"/></param>
/// <returns>The <see cref="IMvcBuilder"/>.</returns>
public static IMvcBuilder ConfigureApplicationPartManager(
this IMvcBuilder builder,
Action<ApplicationPartManager> setupAction)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
setupAction(builder.PartManager);
return builder;
}
/// <summary>
/// Register the specified <paramref name="controllerTypes"/> as services and as a source for controller
/// discovery.

View File

@ -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());
}
/// <summary>
/// Adds an <see cref="ApplicationPart"/> to the list of <see cref="ApplicationPartManager.ApplicationParts"/> on the
/// <see cref="IMvcCoreBuilder.PartManager"/>.
/// </summary>
/// <param name="builder">The <see cref="IMvcCoreBuilder"/>.</param>
/// <param name="assembly">The <see cref="Assembly"/> of the <see cref="ApplicationPart"/>.</param>
/// <returns>The <see cref="IMvcCoreBuilder"/>.</returns>
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;
}
/// <summary>
/// Configures the <see cref="ApplicationPartManager"/> of the <see cref="IMvcCoreBuilder.PartManager"/> using
/// the given <see cref="Action{ApplicationPartManager}"/>.
/// </summary>
/// <param name="builder">The <see cref="IMvcCoreBuilder"/>.</param>
/// <param name="setupAction">The <see cref="Action{ApplicationPartManager}"/></param>
/// <returns>The <see cref="IMvcCoreBuilder"/>.</returns>
public static IMvcCoreBuilder ConfigureApplicationPartManager(
this IMvcCoreBuilder builder,
Action<ApplicationPartManager> setupAction)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
setupAction(builder.PartManager);
return builder;
}
/// <summary>
/// Registers controller types from the specified <paramref name="controllerAssemblies"/> as services and as a source
/// for controller discovery.

View File

@ -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<ApplicationPartManager>(services);
if (manager == null)
{
manager = new ApplicationPartManager();
var environment = GetServiceFromCollection<IHostingEnvironment>(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<T>(IServiceCollection services)
{
return (T)services
.FirstOrDefault(d => d.ServiceType == typeof(T))
?.ImplementationInstance;
}
/// <summary>

View File

@ -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 <see cref="MvcBuilder"/> instance.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
public MvcBuilder(IServiceCollection services)
/// <param name="manager">The <see cref="ApplicationPartManager"/> of the application.</param>
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;
}
/// <inheritdoc />
public IServiceCollection Services { get; }
/// <inheritdoc />
public ApplicationPartManager PartManager { get; }
}
}

View File

@ -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
{
/// <summary>
/// Initializes a new instance of <see cref="MvcCoreBuilder"/>.
/// Initializes a new <see cref="MvcCoreBuilder"/> instance.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
public MvcCoreBuilder(IServiceCollection services)
/// <param name="manager">The <see cref="ApplicationPartManager"/> of the application.</param>
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;
}
/// <inheritdoc />
public ApplicationPartManager PartManager { get; }
/// <inheritdoc />
public IServiceCollection Services { get; }
}

View File

@ -16,14 +16,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal
/// <summary>
/// Sets up default options for <see cref="MvcOptions"/>.
/// </summary>
public class MvcCoreMvcOptionsSetup : ConfigureOptions<MvcOptions>
public class MvcCoreMvcOptionsSetup : IConfigureOptions<MvcOptions>
{
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());

View File

@ -45,7 +45,7 @@ namespace Microsoft.Extensions.DependencyInjection
builder.AddCors();
return new MvcBuilder(builder.Services);
return new MvcBuilder(builder.Services, builder.PartManager);
}
/// <summary>

View File

@ -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<string> Values { get; } = new List<string>();
}
private class ViewComponentsFeature
{
public IList<string> Values { get; } = new List<string>();
}
private class NotControllersedFeatureProvider : IApplicationFeatureProvider<ViewComponentsFeature>
{
private readonly Action<ViewComponentsFeature, string> _operation;
public NotControllersedFeatureProvider(Action<ViewComponentsFeature, string> operation)
{
_operation = operation;
}
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewComponentsFeature feature)
{
foreach (var part in parts.OfType<ViewComponentsPart>())
{
_operation(feature, part.Value);
}
}
}
private class ControllersFeatureProvider : IApplicationFeatureProvider<ControllersFeature>
{
private readonly Action<ControllersFeature, string> _operation;
public ControllersFeatureProvider(Action<ControllersFeature, string> operation)
{
_operation = operation;
}
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllersFeature feature)
{
foreach (var part in parts.OfType<ControllersPart>())
{
_operation(feature, part.Value);
}
}
}
}
}

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

View File

@ -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<IServiceCollection>(), 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<AssemblyPart>(part);
Assert.Equal(assembly, assemblyPart.Assembly);
}
[Fact]
public void ConfigureApplicationParts_InvokesSetupAction()
{
// Arrange
var builder = new MvcBuilder(
Mock.Of<IServiceCollection>(),
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";
}
}
}

View File

@ -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<IServiceCollection>(), 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<AssemblyPart>(part);
Assert.Equal(assembly, assemblyPart.Assembly);
}
[Fact]
public void ConfigureApplicationParts_InvokesSetupAction()
{
// Arrange
var builder = new MvcCoreBuilder(
Mock.Of<IServiceCollection>(),
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";
}
}
}

View File

@ -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<TestableComplexTypeModelBinder>(){ CallBase = true };
var binder = new Mock<TestableComplexTypeModelBinder>() { CallBase = true };
binder
.Setup(b => b.CreateModelPublic(It.IsAny<ModelBindingContext>()))
.Verifiable();
@ -1067,7 +1067,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
private static TestableComplexTypeModelBinder CreateBinder(ModelMetadata metadata)
{
var options = new TestOptionsManager<MvcOptions>();
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<ComplexTypeModelBinderProvider>(options.Value.ModelBinderProviders[lastIndex]);

View File

@ -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<IAssemblyProvider>(assemblyProvider);
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new AssemblyPart(startupAssembly));
services.AddSingleton(manager);
}
private class StaticAssemblyProvider : IAssemblyProvider
{
public IList<Assembly> CandidateAssemblies { get; } = new List<Assembly>();
IEnumerable<Assembly> IAssemblyProvider.CandidateAssemblies => CandidateAssemblies;
}
}
}

View File

@ -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<MvcOptions> updateOptions = null)
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(new ApplicationPartManager());
serviceCollection.AddMvc();
serviceCollection
.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>()

View File

@ -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<ICompositeMetadataDetailsProvider, DefaultCompositeMetadataDetailsProvider>();
collection.AddSingleton<IModelMetadataProvider, DefaultModelMetadataProvider>();

View File

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

View File

@ -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<TestTagHelper>((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<TestTagHelper>((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<TestTagHelper>((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<TestTagHelper>((h, vc) =>
{
h.Name = "Test 1";

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

View File

@ -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<MvcOptions>(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<IServiceCollection> action = null)
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(new ApplicationPartManager());
serviceCollection.AddMvc();
serviceCollection
.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>()

View File

@ -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<IHostingEnvironment>(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<IHostingEnvironment>(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<IHostingEnvironment>(GetHostingEnvironment());
// Act
services.AddMvc();
@ -123,6 +127,7 @@ namespace Microsoft.AspNetCore.Mvc
get
{
var services = new ServiceCollection();
services.AddSingleton<IHostingEnvironment>(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<IHostingEnvironment>();
environment
.Setup(e => e.ApplicationName)
.Returns(typeof(MvcServiceCollectionExtensionsTest).GetTypeInfo().Assembly.GetName().Name);
return environment.Object;
}
}
}