diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcBuilderExtensions.cs
index 237ff3cf29..3ba1879aa3 100644
--- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcBuilderExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcBuilderExtensions.cs
@@ -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 @@ public static class MvcViewFeaturesMvcBuilderExtensions
///
/// The .
/// The which need to be configured.
+ /// The .
public static IMvcBuilder AddViewOptions(
this IMvcBuilder builder,
Action setupAction)
@@ -33,5 +37,30 @@ public static IMvcBuilder AddViewOptions(
builder.Services.Configure(setupAction);
return builder;
}
+
+ ///
+ /// Registers discovered view components as services in the .
+ ///
+ /// The .
+ /// The .
+ 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());
+
+ return builder;
+ }
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs
index 178b9f6265..05c913720f 100644
--- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs
@@ -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 @@ public static IMvcCoreBuilder AddViews(this IMvcCoreBuilder builder)
}
builder.AddDataAnnotations();
+ AddViewComponentApplicationPartsProviders(builder.PartManager);
AddViewServices(builder.Services);
return builder;
}
+ private static void AddViewComponentApplicationPartsProviders(ApplicationPartManager manager)
+ {
+ if (!manager.FeatureProviders.OfType().Any())
+ {
+ manager.FeatureProviders.Add(new ViewComponentFeatureProvider());
+ }
+ }
+
public static IMvcCoreBuilder AddViews(
this IMvcCoreBuilder builder,
Action setupAction)
diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentDescriptorProvider.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentDescriptorProvider.cs
index ab54680b1a..8c3ee66855 100644
--- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentDescriptorProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentDescriptorProvider.cs
@@ -6,7 +6,7 @@
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 @@ public class DefaultViewComponentDescriptorProvider : IViewComponentDescriptorPr
{
private const string AsyncMethodName = "InvokeAsync";
private const string SyncMethodName = "Invoke";
- private readonly IAssemblyProvider _assemblyProvider;
+ private readonly ApplicationPartManager _partManager;
///
/// Creates a new .
///
- /// The .
- public DefaultViewComponentDescriptorProvider(IAssemblyProvider assemblyProvider)
+ /// The .
+ public DefaultViewComponentDescriptorProvider(ApplicationPartManager partManager)
{
- _assemblyProvider = assemblyProvider;
+ if (partManager == null)
+ {
+ throw new ArgumentNullException(nameof(partManager));
+ }
+
+ _partManager = partManager;
}
///
public virtual IEnumerable GetViewComponents()
{
- var types = GetCandidateTypes();
-
- return types
- .Where(IsViewComponentType)
- .Select(CreateDescriptor);
+ return GetCandidateTypes().Select(CreateDescriptor);
}
///
- /// Gets the candidate instances. The results of this will be provided to
- /// for filtering.
+ /// Gets the candidate instances provided by the .
///
/// A list of instances.
protected virtual IEnumerable GetCandidateTypes()
{
- var assemblies = _assemblyProvider.CandidateAssemblies;
- return assemblies.SelectMany(a => a.ExportedTypes).Select(t => t.GetTypeInfo());
- }
-
- ///
- /// Determines whether or not the given is a view component class.
- ///
- /// The .
- ///
- /// true if represents a view component class, otherwise false.
- ///
- 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;
diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ServiceBasedViewComponentActivator.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ServiceBasedViewComponentActivator.cs
new file mode 100644
index 0000000000..deb26a9e44
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ServiceBasedViewComponentActivator.cs
@@ -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
+{
+ ///
+ /// A that retrieves view components as services from the request's
+ /// .
+ ///
+ public class ServiceBasedViewComponentActivator : IViewComponentActivator
+ {
+ ///
+ 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);
+ }
+
+ ///
+ public virtual void Release(ViewComponentContext context, object viewComponent)
+ {
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentFeature.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentFeature.cs
new file mode 100644
index 0000000000..6abb947f21
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentFeature.cs
@@ -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
+{
+ ///
+ /// The list of view component types in an MVC application.The can be populated
+ /// using the that is available during startup at
+ /// and or at a later stage by requiring the
+ /// as a dependency in a component.
+ ///
+ public class ViewComponentFeature
+ {
+ ///
+ /// Gets the list of view component types in an MVC application.
+ ///
+ public IList ViewComponents { get; } = new List();
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentFeatureProvider.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentFeatureProvider.cs
new file mode 100644
index 0000000000..eb953fb5af
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentFeatureProvider.cs
@@ -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
+{
+ ///
+ /// Discovers view components from a list of instances.
+ ///
+ public class ViewComponentFeatureProvider : IApplicationFeatureProvider
+ {
+ ///
+ public void PopulateFeature(IEnumerable 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().SelectMany(p => p.Types))
+ {
+ if (ViewComponentConventions.IsComponent(type) && ! feature.ViewComponents.Contains(type))
+ {
+ feature.ViewComponents.Add(type);
+ }
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerFeatureProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerFeatureProviderTest.cs
index 69bd65a80e..d4579aad0b 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerFeatureProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerFeatureProviderTest.cs
@@ -280,7 +280,7 @@ public void AncestorTypeDoesNotHaveControllerAttribute_IsNotController()
}
[Fact]
- public void GetFeature_OnlyRunsOnParts_ThatImplementIExportTypes()
+ public void GetFeature_OnlyRunsOnParts_ThatImplementIApplicationPartTypeProvider()
{
// Arrange
var otherPart = new Mock();
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/MvcTestFixture.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/MvcTestFixture.cs
index 33a74a5712..2d48dda86c 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/MvcTestFixture.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/MvcTestFixture.cs
@@ -10,6 +10,7 @@
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 @@ protected virtual void InitializeServices(IServiceCollection services)
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new AssemblyPart(startupAssembly));
+
manager.FeatureProviders.Add(new ControllerFeatureProvider());
+ manager.FeatureProviders.Add(new ViewComponentFeatureProvider());
+
services.AddSingleton(manager);
}
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewComponentFromServicesTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewComponentFromServicesTests.cs
new file mode 100644
index 0000000000..446f159a02
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewComponentFromServicesTests.cs
@@ -0,0 +1,35 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.FunctionalTests
+{
+ public class ViewComponentFromServicesTest : IClassFixture>
+ {
+ public ViewComponentFromServicesTest(MvcTestFixture 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);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs
index ff4492cc08..32b3d70341 100644
--- a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs
@@ -10,6 +10,7 @@
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.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 @@ public void AddMvcServicesTwice_DoesNotAddDuplicates()
}
}
+ [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(descriptor.ImplementationInstance);
+
+ Assert.Equal(2, manager.FeatureProviders.Count);
+ Assert.IsType(manager.FeatureProviders[0]);
+ Assert.IsType(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 SingleRegistrationServiceTypes
{
get
diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/DependencyInjection/MvcViewFeaturesMvcBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/DependencyInjection/MvcViewFeaturesMvcBuilderExtensionsTest.cs
new file mode 100644
index 0000000000..37a887ce36
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/DependencyInjection/MvcViewFeaturesMvcBuilderExtensionsTest.cs
@@ -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 InvokeAsync() => Task.FromResult("Hello world");
+ }
+
+ private class TestProvider : IApplicationFeatureProvider
+ {
+ public void PopulateFeature(IEnumerable parts, ViewComponentFeature feature)
+ {
+ foreach (var type in parts.OfType().SelectMany(p => p.Types))
+ {
+ feature.ViewComponents.Add(type);
+ }
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/TestApplicationPart.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/TestApplicationPart.cs
new file mode 100644
index 0000000000..5f67622424
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/TestApplicationPart.cs
@@ -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();
+ }
+
+ public TestApplicationPart(params TypeInfo[] types)
+ {
+ Types = types;
+ }
+
+ public TestApplicationPart(IEnumerable types)
+ {
+ Types = types;
+ }
+
+ public TestApplicationPart(IEnumerable 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 Types { get; }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentDescriptorProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentDescriptorProviderTest.cs
index 273de6c2ce..370d027750 100644
--- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentDescriptorProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentDescriptorProviderTest.cs
@@ -6,47 +6,13 @@
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 @@ public void GetViewComponents_ThrowsIfInvokeIsVoidReturning()
Assert.Equal(expected, ex.Message);
}
- private class ConventionsViewComponent
- {
- public string Invoke() => "Hello world";
- }
-
- [ViewComponent(Name = "AttributesAreGreat")]
- private class AttributeViewComponent
- {
- public Task InvokeAsync() => Task.FromResult("Hello world");
- }
-
private class MultipleInvokeViewComponent
{
public IViewComponentResult Invoke() => null;
@@ -211,34 +166,27 @@ private DefaultViewComponentDescriptorProvider CreateProvider(Type componentType
private class FilteredViewComponentDescriptorProvider : DefaultViewComponentDescriptorProvider
{
public FilteredViewComponentDescriptorProvider(params Type[] allowedTypes)
- : base(GetAssemblyProvider())
- {
- AllowedTypes = allowedTypes;
- }
-
- public Type[] AllowedTypes { get; }
-
- protected override bool IsViewComponentType(TypeInfo typeInfo)
+ : 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 GetCandidateTypes()
+ private static ApplicationPartManager GetApplicationPartManager(IEnumerable 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
{
- var assemblyProvider = new StaticAssemblyProvider();
- assemblyProvider.CandidateAssemblies.Add(
- typeof(FilteredViewComponentDescriptorProvider).GetTypeInfo().Assembly);
-
- return assemblyProvider;
+ public void PopulateFeature(IEnumerable parts, ViewComponentFeature feature)
+ {
+ foreach (var type in parts.OfType().SelectMany(p => p.Types))
+ {
+ feature.ViewComponents.Add(type);
+ }
+ }
}
}
}
diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentSelectorTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentSelectorTest.cs
index ce84da9c9b..5a36085676 100644
--- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentSelectorTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentSelectorTest.cs
@@ -5,7 +5,7 @@
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 @@ public class FullNameInAttribute
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 GetCandidateTypes()
+ private static ApplicationPartManager GetApplicationPartManager(IEnumerable 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
{
- var assemblyProvider = new StaticAssemblyProvider();
- assemblyProvider.CandidateAssemblies.Add(
- typeof(ViewComponentContainer).GetTypeInfo().Assembly);
-
- return assemblyProvider;
+ public void PopulateFeature(IEnumerable parts, ViewComponentFeature feature)
+ {
+ foreach (var type in parts.OfType().SelectMany(p => p.Types))
+ {
+ feature.ViewComponents.Add(type);
+ }
+ }
}
}
}
diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentFeatureProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentFeatureProviderTest.cs
new file mode 100644
index 0000000000..6a075bf5d9
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentFeatureProviderTest.cs
@@ -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 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 InvokeAsync() => Task.FromResult("Hello world");
+ }
+}
diff --git a/test/WebSites/ControllersFromServicesWebSite/AnotherController.cs b/test/WebSites/ControllersFromServicesWebSite/AnotherController.cs
index 9277327a06..05d7f60732 100644
--- a/test/WebSites/ControllersFromServicesWebSite/AnotherController.cs
+++ b/test/WebSites/ControllersFromServicesWebSite/AnotherController.cs
@@ -13,5 +13,11 @@ public int Get()
{
return 1;
}
+
+ [HttpGet("InServicesViewComponent")]
+ public IActionResult ViewComponentAction()
+ {
+ return ViewComponent("ComponentFromServices");
+ }
}
}
diff --git a/test/WebSites/ControllersFromServicesWebSite/Components/ComponentFromServicesViewComponent.cs b/test/WebSites/ControllersFromServicesWebSite/Components/ComponentFromServicesViewComponent.cs
new file mode 100644
index 0000000000..54901d4fbb
--- /dev/null
+++ b/test/WebSites/ControllersFromServicesWebSite/Components/ComponentFromServicesViewComponent.cs
@@ -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}";
+ }
+ }
+}
diff --git a/test/WebSites/ControllersFromServicesWebSite/Startup.cs b/test/WebSites/ControllersFromServicesWebSite/Startup.cs
index 7339ad8400..74e5693442 100644
--- a/test/WebSites/ControllersFromServicesWebSite/Startup.cs
+++ b/test/WebSites/ControllersFromServicesWebSite/Startup.cs
@@ -6,6 +6,7 @@
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 @@ public void ConfigureServices(IServiceCollection services)
.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();
+ services.AddTransient();
services.AddSingleton();
}
diff --git a/test/WebSites/ControllersFromServicesWebSite/ValueService.cs b/test/WebSites/ControllersFromServicesWebSite/ValueService.cs
new file mode 100644
index 0000000000..f08089282b
--- /dev/null
+++ b/test/WebSites/ControllersFromServicesWebSite/ValueService.cs
@@ -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;
+ }
+}