[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:
parent
87e89befc8
commit
5246125cb7
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -13,5 +13,11 @@ namespace ControllersFromServicesWebSite
|
|||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
[HttpGet("InServicesViewComponent")]
|
||||
public IActionResult ViewComponentAction()
|
||||
{
|
||||
return ViewComponent("ComponentFromServices");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue