Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
[Fixes #4087] Add AddViewComponentsAsServices() and ServiceBasedViewC…
Browse files Browse the repository at this point in the history
…omponentActivator

* 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.
  • Loading branch information
javiercn committed Mar 31, 2016
1 parent 2289bbe commit 508bfe5
Show file tree
Hide file tree
Showing 19 changed files with 530 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -16,6 +19,7 @@ public static class MvcViewFeaturesMvcBuilderExtensions
/// </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)
Expand All @@ -33,5 +37,30 @@ public static IMvcBuilder AddViewOptions(
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<ViewComponentFeatureProvider>().Any())
{
manager.FeatureProviders.Add(new ViewComponentFeatureProvider());
}
}

public static IMvcCoreBuilder AddViews(
this IMvcCoreBuilder builder,
Action<MvcViewOptions> setupAction)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;

/// <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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -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>();
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ public void AncestorTypeDoesNotHaveControllerAttribute_IsNotController()
}

[Fact]
public void GetFeature_OnlyRunsOnParts_ThatImplementIExportTypes()
public void GetFeature_OnlyRunsOnParts_ThatImplementIApplicationPartTypeProvider()
{
// Arrange
var otherPart = new Mock<ApplicationPart>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<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
Expand Down
Loading

0 comments on commit 508bfe5

Please sign in to comment.