[Fixes #4087] Add AddViewComponentsAsServices() and ServiceBasedViewComponentActivator

* Added ViewComponentFeture and ViewComponentFeatureProvider to perform view component discovery.
* Changed view component discovery to use application parts.
* Changed ViewComponentDescriptorProvider to make use of Application parts.
* Added AddViewComponentsAsServices method on IMvcBuilder that performs view component
  discovery through the ApplicationPartManager and registers those view components as
  services in the service collection. Assemblies should be added to the ApplicationPartManager
  in order to discover view components in them in them.
This commit is contained in:
jacalvar 2016-03-24 13:05:36 -07:00
parent 87e89befc8
commit 5246125cb7
19 changed files with 530 additions and 123 deletions

View File

@ -2,7 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Microsoft.Extensions.DependencyInjection
{
@ -16,6 +19,7 @@ namespace Microsoft.Extensions.DependencyInjection
/// </summary>
/// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
/// <param name="setupAction">The <see cref="MvcViewOptions"/> which need to be configured.</param>
/// <returns>The <see cref="IMvcBuilder"/>.</returns>
public static IMvcBuilder AddViewOptions(
this IMvcBuilder builder,
Action<MvcViewOptions> setupAction)
@ -33,5 +37,30 @@ namespace Microsoft.Extensions.DependencyInjection
builder.Services.Configure(setupAction);
return builder;
}
/// <summary>
/// Registers discovered view components as services in the <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
/// <returns>The <see cref="IMvcBuilder"/>.</returns>
public static IMvcBuilder AddViewComponentsAsServices(this IMvcBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
var feature = new ViewComponentFeature();
builder.PartManager.PopulateFeature(feature);
foreach (var viewComponent in feature.ViewComponents.Select(vc => vc.AsType()))
{
builder.Services.TryAddTransient(viewComponent, viewComponent);
}
builder.Services.Replace(ServiceDescriptor.Singleton<IViewComponentActivator, ServiceBasedViewComponentActivator>());
return builder;
}
}
}

View File

@ -3,7 +3,9 @@
using System;
using System.Buffers;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Rendering;
@ -26,10 +28,19 @@ namespace Microsoft.Extensions.DependencyInjection
}
builder.AddDataAnnotations();
AddViewComponentApplicationPartsProviders(builder.PartManager);
AddViewServices(builder.Services);
return builder;
}
private static void AddViewComponentApplicationPartsProviders(ApplicationPartManager manager)
{
if (!manager.FeatureProviders.OfType<ViewComponentFeatureProvider>().Any())
{
manager.FeatureProviders.Add(new ViewComponentFeatureProvider());
}
}
public static IMvcCoreBuilder AddViews(
this IMvcCoreBuilder builder,
Action<MvcViewOptions> setupAction)

View File

@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace Microsoft.AspNetCore.Mvc.ViewComponents
@ -18,64 +18,47 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
{
private const string AsyncMethodName = "InvokeAsync";
private const string SyncMethodName = "Invoke";
private readonly IAssemblyProvider _assemblyProvider;
private readonly ApplicationPartManager _partManager;
/// <summary>
/// Creates a new <see cref="DefaultViewComponentDescriptorProvider"/>.
/// </summary>
/// <param name="assemblyProvider">The <see cref="IAssemblyProvider"/>.</param>
public DefaultViewComponentDescriptorProvider(IAssemblyProvider assemblyProvider)
/// <param name="partManager">The <see cref="ApplicationPartManager"/>.</param>
public DefaultViewComponentDescriptorProvider(ApplicationPartManager partManager)
{
_assemblyProvider = assemblyProvider;
if (partManager == null)
{
throw new ArgumentNullException(nameof(partManager));
}
_partManager = partManager;
}
/// <inheritdoc />
public virtual IEnumerable<ViewComponentDescriptor> GetViewComponents()
{
var types = GetCandidateTypes();
return types
.Where(IsViewComponentType)
.Select(CreateDescriptor);
return GetCandidateTypes().Select(CreateDescriptor);
}
/// <summary>
/// Gets the candidate <see cref="TypeInfo"/> instances. The results of this will be provided to
/// <see cref="IsViewComponentType"/> for filtering.
/// Gets the candidate <see cref="TypeInfo"/> instances provided by the <see cref="ApplicationPartManager"/>.
/// </summary>
/// <returns>A list of <see cref="TypeInfo"/> instances.</returns>
protected virtual IEnumerable<TypeInfo> GetCandidateTypes()
{
var assemblies = _assemblyProvider.CandidateAssemblies;
return assemblies.SelectMany(a => a.ExportedTypes).Select(t => t.GetTypeInfo());
}
/// <summary>
/// Determines whether or not the given <see cref="TypeInfo"/> is a view component class.
/// </summary>
/// <param name="typeInfo">The <see cref="TypeInfo"/>.</param>
/// <returns>
/// <c>true</c> if <paramref name="typeInfo"/>represents a view component class, otherwise <c>false</c>.
/// </returns>
protected virtual bool IsViewComponentType(TypeInfo typeInfo)
{
if (typeInfo == null)
{
throw new ArgumentNullException(nameof(typeInfo));
}
return ViewComponentConventions.IsComponent(typeInfo);
var feature = new ViewComponentFeature();
_partManager.PopulateFeature(feature);
return feature.ViewComponents;
}
private static ViewComponentDescriptor CreateDescriptor(TypeInfo typeInfo)
{
var type = typeInfo.AsType();
var candidate = new ViewComponentDescriptor
{
FullName = ViewComponentConventions.GetComponentFullName(typeInfo),
ShortName = ViewComponentConventions.GetComponentName(typeInfo),
TypeInfo = typeInfo,
MethodInfo = FindMethod(type)
MethodInfo = FindMethod(typeInfo.AsType())
};
return candidate;

View File

@ -0,0 +1,33 @@
// 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 Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc.ViewComponents
{
/// <summary>
/// A <see cref="IViewComponentActivator"/> that retrieves view components as services from the request's
/// <see cref="IServiceProvider"/>.
/// </summary>
public class ServiceBasedViewComponentActivator : IViewComponentActivator
{
/// <inheritdoc />
public object Create(ViewComponentContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var viewComponentType = context.ViewComponentDescriptor.TypeInfo.AsType();
return context.ViewContext.HttpContext.RequestServices.GetRequiredService(viewComponentType);
}
/// <inheritdoc />
public virtual void Release(ViewComponentContext context, object viewComponent)
{
}
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc.ViewComponents
{
/// <summary>
/// The list of view component types in an MVC application.The <see cref="ViewComponentFeature"/> can be populated
/// using the <see cref="ApplicationPartManager"/> that is available during startup at <see cref="IMvcBuilder.PartManager"/>
/// and <see cref="IMvcCoreBuilder.PartManager"/> or at a later stage by requiring the <see cref="ApplicationPartManager"/>
/// as a dependency in a component.
/// </summary>
public class ViewComponentFeature
{
/// <summary>
/// Gets the list of view component types in an MVC application.
/// </summary>
public IList<TypeInfo> ViewComponents { get; } = new List<TypeInfo>();
}
}

View File

@ -0,0 +1,38 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
namespace Microsoft.AspNetCore.Mvc.ViewComponents
{
/// <summary>
/// Discovers view components from a list of <see cref="ApplicationPart"/> instances.
/// </summary>
public class ViewComponentFeatureProvider : IApplicationFeatureProvider<ViewComponentFeature>
{
/// <inheritdoc />
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewComponentFeature feature)
{
if (parts == null)
{
throw new ArgumentNullException(nameof(parts));
}
if (feature == null)
{
throw new ArgumentNullException(nameof(feature));
}
foreach (var type in parts.OfType<IApplicationPartTypeProvider>().SelectMany(p => p.Types))
{
if (ViewComponentConventions.IsComponent(type) && ! feature.ViewComponents.Contains(type))
{
feature.ViewComponents.Add(type);
}
}
}
}
}

View File

@ -280,7 +280,7 @@ namespace Microsoft.AspNetCore.Mvc.Controllers
}
[Fact]
public void GetFeature_OnlyRunsOnParts_ThatImplementIExportTypes()
public void GetFeature_OnlyRunsOnParts_ThatImplementIApplicationPartTypeProvider()
{
// Arrange
var otherPart = new Mock<ApplicationPart>();

View File

@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.TestHost;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
@ -72,7 +73,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new AssemblyPart(startupAssembly));
manager.FeatureProviders.Add(new ControllerFeatureProvider());
manager.FeatureProviders.Add(new ViewComponentFeatureProvider());
services.AddSingleton(manager);
}

View File

@ -0,0 +1,35 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class ViewComponentFromServicesTest : IClassFixture<MvcTestFixture<ControllersFromServicesWebSite.Startup>>
{
public ViewComponentFromServicesTest(MvcTestFixture<ControllersFromServicesWebSite.Startup> fixture)
{
Client = fixture.Client;
}
public HttpClient Client { get; }
[Fact]
public async Task ViewComponentsWithConstructorInjectionAreCreatedAndActivated()
{
// Arrange
var expected = "Value = 3";
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/another/InServicesViewComponent");
// Act
var response = await Client.SendAsync(request);
var responseText = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(expected, responseText);
}
}
}

View File

@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Cors.Internal;
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
@ -18,6 +19,7 @@ using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Routing;
@ -122,6 +124,48 @@ namespace Microsoft.AspNetCore.Mvc
}
}
[Fact]
public void AddMvcTwice_DoesNotAddApplicationFeatureProvidersTwice()
{
// Arrange
var services = new ServiceCollection();
var providers = new IApplicationFeatureProvider[]
{
new ControllerFeatureProvider(),
new ViewComponentFeatureProvider()
};
// Act
services.AddMvc();
services.AddMvc();
// Assert
var descriptor = Assert.Single(services, d => d.ServiceType == typeof(ApplicationPartManager));
Assert.Equal(ServiceLifetime.Singleton, descriptor.Lifetime);
Assert.NotNull(descriptor.ImplementationInstance);
var manager = Assert.IsType<ApplicationPartManager>(descriptor.ImplementationInstance);
Assert.Equal(2, manager.FeatureProviders.Count);
Assert.IsType<ControllerFeatureProvider>(manager.FeatureProviders[0]);
Assert.IsType<ViewComponentFeatureProvider>(manager.FeatureProviders[1]);
}
[Fact]
public void AddMvcCore_ReusesExistingApplicationPartManagerInstance_IfFoundOnServiceCollection()
{
// Arrange
var services = new ServiceCollection();
var manager = new ApplicationPartManager();
services.AddSingleton(manager);
// Act
services.AddMvc();
// Assert
var descriptor = Assert.Single(services, d => d.ServiceType == typeof(ApplicationPartManager));
Assert.Same(manager, descriptor.ImplementationInstance);
}
private IEnumerable<Type> SingleRegistrationServiceTypes
{
get

View File

@ -0,0 +1,95 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Xunit;
namespace Microsoft.Extensions.DependencyInjection
{
public class MvcViewFeaturesMvcBuilderExtensionsTest
{
[Fact]
public void AddViewComponentsAsServices_ReplacesViewComponentActivator()
{
// Arrange
var services = new ServiceCollection();
var builder = services
.AddMvc()
.ConfigureApplicationPartManager(manager =>
{
manager.ApplicationParts.Add(new TestApplicationPart());
manager.FeatureProviders.Add(new ViewComponentFeatureProvider());
});
// Act
builder.AddViewComponentsAsServices();
// Assert
var descriptor = Assert.Single(services.ToList(), d => d.ServiceType == typeof(IViewComponentActivator));
Assert.Equal(typeof(ServiceBasedViewComponentActivator), descriptor.ImplementationType);
}
[Fact]
public void AddViewComponentsAsServices_RegistersDiscoveredViewComponents()
{
// Arrange
var services = new ServiceCollection();
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new TestApplicationPart(
typeof(ConventionsViewComponent),
typeof(AttributeViewComponent)));
manager.FeatureProviders.Add(new TestProvider());
var builder = new MvcBuilder(services, manager);
// Act
builder.AddViewComponentsAsServices();
// Assert
var collection = services.ToList();
Assert.Equal(3, collection.Count);
Assert.Equal(typeof(ConventionsViewComponent), collection[0].ServiceType);
Assert.Equal(typeof(ConventionsViewComponent), collection[0].ImplementationType);
Assert.Equal(ServiceLifetime.Transient, collection[0].Lifetime);
Assert.Equal(typeof(AttributeViewComponent), collection[1].ServiceType);
Assert.Equal(typeof(AttributeViewComponent), collection[1].ImplementationType);
Assert.Equal(ServiceLifetime.Transient, collection[1].Lifetime);
Assert.Equal(typeof(IViewComponentActivator), collection[2].ServiceType);
Assert.Equal(typeof(ServiceBasedViewComponentActivator), collection[2].ImplementationType);
Assert.Equal(ServiceLifetime.Singleton, collection[2].Lifetime);
}
public class ConventionsViewComponent
{
public string Invoke() => "Hello world";
}
[ViewComponent(Name = "AttributesAreGreat")]
public class AttributeViewComponent
{
public Task<string> InvokeAsync() => Task.FromResult("Hello world");
}
private class TestProvider : IApplicationFeatureProvider<ViewComponentFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewComponentFeature feature)
{
foreach (var type in parts.OfType<IApplicationPartTypeProvider>().SelectMany(p => p.Types))
{
feature.ViewComponents.Add(type);
}
}
}
}
}

View File

@ -0,0 +1,44 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
namespace Microsoft.AspNetCore.Mvc
{
public class TestApplicationPart : ApplicationPart, IApplicationPartTypeProvider
{
public TestApplicationPart()
{
Types = Enumerable.Empty<TypeInfo>();
}
public TestApplicationPart(params TypeInfo[] types)
{
Types = types;
}
public TestApplicationPart(IEnumerable<TypeInfo> types)
{
Types = types;
}
public TestApplicationPart(IEnumerable<Type> types)
:this(types.Select(t => t.GetTypeInfo()))
{
}
public TestApplicationPart(params Type[] types)
: this(types.Select(t => t.GetTypeInfo()))
{
}
public override string Name => "Test part";
public IEnumerable<TypeInfo> Types { get; }
}
}

View File

@ -6,47 +6,13 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ViewComponents
{
public class DefaultViewComponentDescriptorProviderTest
{
[Fact]
public void GetDescriptor_DefaultConventions()
{
// Arrange
var provider = CreateProvider(typeof(ConventionsViewComponent));
// Act
var descriptors = provider.GetViewComponents();
// Assert
var descriptor = Assert.Single(descriptors);
Assert.Same(typeof(ConventionsViewComponent).GetTypeInfo(), descriptor.TypeInfo);
Assert.Equal("Microsoft.AspNetCore.Mvc.ViewComponents.Conventions", descriptor.FullName);
Assert.Equal("Conventions", descriptor.ShortName);
Assert.Same(typeof(ConventionsViewComponent).GetMethod("Invoke"), descriptor.MethodInfo);
}
[Fact]
public void GetDescriptor_WithAttribute()
{
// Arrange
var provider = CreateProvider(typeof(AttributeViewComponent));
// Act
var descriptors = provider.GetViewComponents();
// Assert
var descriptor = Assert.Single(descriptors);
Assert.Equal(typeof(AttributeViewComponent).GetTypeInfo(), descriptor.TypeInfo);
Assert.Equal("AttributesAreGreat", descriptor.FullName);
Assert.Equal("AttributesAreGreat", descriptor.ShortName);
Assert.Same(typeof(AttributeViewComponent).GetMethod("InvokeAsync"), descriptor.MethodInfo);
}
[Theory]
[InlineData(typeof(NoMethodsViewComponent))]
[InlineData(typeof(NonPublicInvokeAsyncViewComponent))]
@ -120,17 +86,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
Assert.Equal(expected, ex.Message);
}
private class ConventionsViewComponent
{
public string Invoke() => "Hello world";
}
[ViewComponent(Name = "AttributesAreGreat")]
private class AttributeViewComponent
{
public Task<string> InvokeAsync() => Task.FromResult("Hello world");
}
private class MultipleInvokeViewComponent
{
public IViewComponentResult Invoke() => null;
@ -211,34 +166,27 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
private class FilteredViewComponentDescriptorProvider : DefaultViewComponentDescriptorProvider
{
public FilteredViewComponentDescriptorProvider(params Type[] allowedTypes)
: base(GetAssemblyProvider())
: base(GetApplicationPartManager(allowedTypes.Select(t => t.GetTypeInfo())))
{
AllowedTypes = allowedTypes;
}
public Type[] AllowedTypes { get; }
protected override bool IsViewComponentType(TypeInfo typeInfo)
private static ApplicationPartManager GetApplicationPartManager(IEnumerable<TypeInfo> types)
{
return AllowedTypes.Contains(typeInfo.AsType());
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new TestApplicationPart(types));
manager.FeatureProviders.Add(new TestFeatureProvider());
return manager;
}
// Need to override this since the default provider does not support private classes.
protected override IEnumerable<TypeInfo> GetCandidateTypes()
private class TestFeatureProvider : IApplicationFeatureProvider<ViewComponentFeature>
{
return
GetAssemblyProvider()
.CandidateAssemblies
.SelectMany(a => a.DefinedTypes);
}
private static IAssemblyProvider GetAssemblyProvider()
{
var assemblyProvider = new StaticAssemblyProvider();
assemblyProvider.CandidateAssemblies.Add(
typeof(FilteredViewComponentDescriptorProvider).GetTypeInfo().Assembly);
return assemblyProvider;
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewComponentFeature feature)
{
foreach (var type in parts.OfType<IApplicationPartTypeProvider>().SelectMany(p => p.Types))
{
feature.ViewComponents.Add(type);
}
}
}
}
}

View File

@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ViewComponents
@ -188,39 +188,36 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
public string Invoke() => "Hello";
}
}
// This will only consider types nested inside this class as ViewComponent classes
private class FilteredViewComponentDescriptorProvider : DefaultViewComponentDescriptorProvider
{
public FilteredViewComponentDescriptorProvider()
: base(GetAssemblyProvider())
: this(typeof(ViewComponentContainer).GetNestedTypes(bindingAttr: BindingFlags.Public))
{
AllowedTypes = typeof(ViewComponentContainer).GetNestedTypes(bindingAttr: BindingFlags.Public);
}
public Type[] AllowedTypes { get; }
protected override bool IsViewComponentType(TypeInfo typeInfo)
public FilteredViewComponentDescriptorProvider(params Type[] allowedTypes)
: base(GetApplicationPartManager(allowedTypes.Select(t => t.GetTypeInfo())))
{
return AllowedTypes.Contains(typeInfo.AsType());
}
// Need to override this since the default provider does not support private classes.
protected override IEnumerable<TypeInfo> GetCandidateTypes()
private static ApplicationPartManager GetApplicationPartManager(IEnumerable<TypeInfo> types)
{
return
GetAssemblyProvider()
.CandidateAssemblies
.SelectMany(a => a.DefinedTypes);
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new TestApplicationPart(types));
manager.FeatureProviders.Add(new TestFeatureProvider());
return manager;
}
private static IAssemblyProvider GetAssemblyProvider()
private class TestFeatureProvider : IApplicationFeatureProvider<ViewComponentFeature>
{
var assemblyProvider = new StaticAssemblyProvider();
assemblyProvider.CandidateAssemblies.Add(
typeof(ViewComponentContainer).GetTypeInfo().Assembly);
return assemblyProvider;
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewComponentFeature feature)
{
foreach (var type in parts.OfType<IApplicationPartTypeProvider>().SelectMany(p => p.Types))
{
feature.ViewComponents.Add(type);
}
}
}
}
}

View File

@ -0,0 +1,79 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.Mvc.ViewFeatures.ViewComponents.ViewComponentsFeatureTest;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.ViewComponents
{
public class ViewComponentFeatureProviderTest
{
[Fact]
public void GetDescriptor_DefaultConventions()
{
// Arrange
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new TestPart(typeof(ConventionsViewComponent)));
manager.FeatureProviders.Add(new ViewComponentFeatureProvider());
var feature = new ViewComponentFeature();
// Act
manager.PopulateFeature(feature);
// Assert
Assert.Equal(new[] { typeof(ConventionsViewComponent).GetTypeInfo() }, feature.ViewComponents.ToArray());
}
[Fact]
public void GetDescriptor_WithAttribute()
{
// Arrange
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new TestPart(typeof(AttributeViewComponent)));
manager.FeatureProviders.Add(new ViewComponentFeatureProvider());
var feature = new ViewComponentFeature();
// Act
manager.PopulateFeature(feature);
// Assert
Assert.Equal(new[] { typeof(AttributeViewComponent).GetTypeInfo() }, feature.ViewComponents.ToArray());
}
private class TestPart : ApplicationPart, IApplicationPartTypeProvider
{
public TestPart(params Type[] types)
{
Types = types.Select(t => t.GetTypeInfo());
}
public override string Name => "Test";
public IEnumerable<TypeInfo> Types { get; }
}
}
}
// These tests need to be public for the test to be valid
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.ViewComponents.ViewComponentsFeatureTest
{
public class ConventionsViewComponent
{
public string Invoke() => "Hello world";
}
[ViewComponent(Name = "AttributesAreGreat")]
public class AttributeViewComponent
{
public Task<string> InvokeAsync() => Task.FromResult("Hello world");
}
}

View File

@ -13,5 +13,11 @@ namespace ControllersFromServicesWebSite
{
return 1;
}
[HttpGet("InServicesViewComponent")]
public IActionResult ViewComponentAction()
{
return ViewComponent("ComponentFromServices");
}
}
}

View File

@ -0,0 +1,22 @@
// 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;
namespace ControllersFromServicesWebSite.Components
{
public class ComponentFromServicesViewComponent : ViewComponent
{
private readonly ValueService _value;
public ComponentFromServicesViewComponent(ValueService value)
{
_value = value;
}
public string Invoke()
{
return $"Value = {_value.Value}";
}
}
}

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ControllersFromServicesClassLibrary;
using ControllersFromServicesWebSite.Components;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
@ -22,10 +23,14 @@ namespace ControllersFromServicesWebSite
.AddMvc()
.ConfigureApplicationPartManager(manager => manager.ApplicationParts.Clear())
.AddApplicationPart(typeof(TimeScheduleController).GetTypeInfo().Assembly)
.ConfigureApplicationPartManager(manager => manager.ApplicationParts.Add(new TypesPart(typeof(AnotherController))))
.AddControllersAsServices();
.ConfigureApplicationPartManager(manager => manager.ApplicationParts.Add(new TypesPart(
typeof(AnotherController),
typeof(ComponentFromServicesViewComponent))))
.AddControllersAsServices()
.AddViewComponentsAsServices();
services.AddTransient<QueryValueService>();
services.AddTransient<ValueService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}

View File

@ -0,0 +1,10 @@
// 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 ControllersFromServicesWebSite
{
public class ValueService
{
public int Value { get; } = 3;
}
}