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

Commit 12eeeb3

Browse files
committed
[Fixes #4087] Add support for AddTagHelpersAsServices()
1 parent ec83f14 commit 12eeeb3

File tree

26 files changed

+807
-16
lines changed

26 files changed

+807
-16
lines changed

src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
1010
/// <summary>
1111
/// An <see cref="ApplicationPart"/> backed by an <see cref="Assembly"/>.
1212
/// </summary>
13-
public class AssemblyPart : ApplicationPart
13+
public class AssemblyPart : ApplicationPart, IApplicationPartTypeProvider
1414
{
1515
/// <summary>
1616
/// Initalizes a new <see cref="AssemblyPart"/> instance.
@@ -35,5 +35,7 @@ public AssemblyPart(Assembly assembly)
3535
/// Gets the name of the <see cref="ApplicationPart"/>.
3636
/// </summary>
3737
public override string Name => Assembly.GetName().Name;
38+
39+
public IEnumerable<TypeInfo> Types => Assembly.DefinedTypes;
3840
}
3941
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using System.Reflection;
6+
7+
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
8+
{
9+
/// <summary>
10+
/// Exposes a set of types from an <see cref="ApplicationPart"/>.
11+
/// </summary>
12+
public interface IApplicationPartTypeProvider
13+
{
14+
/// <summary>
15+
/// Gets the list of available types in the <see cref="ApplicationPart"/>.
16+
/// </summary>
17+
IEnumerable<TypeInfo> Types { get; }
18+
}
19+
}

src/Microsoft.AspNetCore.Mvc.Razor.Host/MvcRazorHost.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,12 @@ public MvcRazorHost(string root)
143143
/// Initializes a new instance of <see cref="MvcRazorHost"/> using the specified <paramref name="chunkTreeCache"/>.
144144
/// </summary>
145145
/// <param name="chunkTreeCache">An <see cref="IChunkTreeCache"/> rooted at the application base path.</param>
146-
public MvcRazorHost(IChunkTreeCache chunkTreeCache)
146+
/// <param name="resolver">The <see cref="ITagHelperDescriptorResolver"/> used to resolve tag helpers on razor views.</param>
147+
public MvcRazorHost(IChunkTreeCache chunkTreeCache, ITagHelperDescriptorResolver resolver)
147148
: this(chunkTreeCache, new RazorPathNormalizer())
148149
{
150+
151+
TagHelperDescriptorResolver = resolver;
149152
}
150153

151154
/// <inheritdoc />

src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcBuilderExtensions.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
using System.Reflection;
77
using Microsoft.AspNetCore.Mvc.Razor;
88
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
9+
using Microsoft.AspNetCore.Mvc.Razor.Internal;
10+
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
911
using Microsoft.AspNetCore.Mvc.Rendering;
12+
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
1013
using Microsoft.AspNetCore.Razor.TagHelpers;
1114
using Microsoft.Extensions.DependencyInjection.Extensions;
1215

@@ -41,6 +44,28 @@ public static IMvcBuilder AddRazorOptions(
4144
return builder;
4245
}
4346

47+
/// <summary>
48+
/// Registers tag helpers as services and changes the existing <see cref="ITagHelperActivator"/>
49+
/// for an <see cref="ServiceBasedTagHelperActivator"/>.
50+
/// </summary>
51+
/// <param name="builder">The <see cref="IMvcBuilder"/> instance this method extends.</param>
52+
/// <returns>The <see cref="IMvcBuilder"/> instance this method extends.</returns>
53+
public static IMvcBuilder AddTagHelpersAsServices(this IMvcBuilder builder)
54+
{
55+
var feature = new TagHelperFeature();
56+
builder.PartManager.PopulateFeature(feature);
57+
58+
foreach (var type in feature.TagHelpers.Select(t => t.AsType()))
59+
{
60+
builder.Services.TryAddTransient(type, type);
61+
}
62+
63+
builder.Services.Replace(ServiceDescriptor.Transient<ITagHelperActivator, ServiceBasedTagHelperActivator>());
64+
builder.Services.Replace(ServiceDescriptor.Transient<ITagHelperTypeResolver, FeatureTagHelperTypeResolver>());
65+
66+
return builder;
67+
}
68+
4469
/// <summary>
4570
/// Adds an initialization callback for a given <typeparamref name="TTagHelper"/>.
4671
/// </summary>

src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Linq;
56
using Microsoft.AspNetCore.Mvc;
67
using Microsoft.AspNetCore.Mvc.Razor;
78
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
89
using Microsoft.AspNetCore.Mvc.Razor.Directives;
910
using Microsoft.AspNetCore.Mvc.Razor.Internal;
11+
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
1012
using Microsoft.AspNetCore.Mvc.Rendering;
13+
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
14+
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
1115
using Microsoft.AspNetCore.Razor.TagHelpers;
1216
using Microsoft.Extensions.Caching.Memory;
1317
using Microsoft.Extensions.DependencyInjection.Extensions;
1418
using Microsoft.Extensions.Options;
15-
using Microsoft.Extensions.PlatformAbstractions;
1619

1720
namespace Microsoft.Extensions.DependencyInjection
1821
{
@@ -26,10 +29,19 @@ public static IMvcCoreBuilder AddRazorViewEngine(this IMvcCoreBuilder builder)
2629
}
2730

2831
builder.AddViews();
32+
AddRazorViewEngineFeatureProviders(builder);
2933
AddRazorViewEngineServices(builder.Services);
3034
return builder;
3135
}
3236

37+
private static void AddRazorViewEngineFeatureProviders(IMvcCoreBuilder builder)
38+
{
39+
if (!builder.PartManager.FeatureProviders.OfType<TagHelperFeatureProvider>().Any())
40+
{
41+
builder.PartManager.FeatureProviders.Add(new TagHelperFeatureProvider());
42+
}
43+
}
44+
3345
public static IMvcCoreBuilder AddRazorViewEngine(
3446
this IMvcCoreBuilder builder,
3547
Action<RazorViewEngineOptions> setupAction)
@@ -45,6 +57,8 @@ public static IMvcCoreBuilder AddRazorViewEngine(
4557
}
4658

4759
builder.AddViews();
60+
61+
AddRazorViewEngineFeatureProviders(builder);
4862
AddRazorViewEngineServices(builder.Services);
4963

5064
if (setupAction != null)
@@ -55,6 +69,28 @@ public static IMvcCoreBuilder AddRazorViewEngine(
5569
return builder;
5670
}
5771

72+
/// <summary>
73+
/// Registers discovered tag helpers as services and changes the existing <see cref="ITagHelperActivator"/>
74+
/// for an <see cref="ServiceBasedTagHelperActivator"/>.
75+
/// </summary>
76+
/// <param name="builder">The <see cref="IMvcCoreBuilder"/> instance this method extends.</param>
77+
/// <returns>The <see cref="IMvcCoreBuilder"/> instance this method extends.</returns>
78+
public static IMvcCoreBuilder AddTagHelpersAsServices(this IMvcCoreBuilder builder)
79+
{
80+
var feature = new TagHelperFeature();
81+
builder.PartManager.PopulateFeature(feature);
82+
83+
foreach (var type in feature.TagHelpers.Select(t => t.AsType()))
84+
{
85+
builder.Services.TryAddTransient(type, type);
86+
}
87+
88+
builder.Services.Replace(ServiceDescriptor.Transient<ITagHelperActivator, ServiceBasedTagHelperActivator>());
89+
builder.Services.Replace(ServiceDescriptor.Transient<ITagHelperTypeResolver, FeatureTagHelperTypeResolver>());
90+
91+
return builder;
92+
}
93+
5894
/// <summary>
5995
/// Adds an initialization callback for a given <typeparamref name="TTagHelper"/>.
6096
/// </summary>
@@ -116,14 +152,20 @@ internal static void AddRazorViewEngineServices(IServiceCollection services)
116152
return new DefaultChunkTreeCache(accessor.FileProvider);
117153
}));
118154

155+
services.TryAddTransient<ITagHelperDescriptorResolver, TagHelperDescriptorResolver>();
156+
157+
services.TryAddTransient<ITagHelperTypeResolver, TagHelperTypeResolver>();
158+
services.TryAddTransient<ITagHelperDescriptorFactory>(s => new TagHelperDescriptorFactory(designTime: false));
159+
119160
// Caches compilation artifacts across the lifetime of the application.
120161
services.TryAddSingleton<ICompilerCacheProvider, DefaultCompilerCacheProvider>();
121162

122163
// In the default scenario the following services are singleton by virtue of being initialized as part of
123164
// creating the singleton RazorViewEngine instance.
124165
services.TryAddTransient<IRazorPageFactoryProvider, DefaultRazorPageFactoryProvider>();
125166
services.TryAddTransient<IRazorCompilationService, RazorCompilationService>();
126-
services.TryAddTransient<IMvcRazorHost, MvcRazorHost>();
167+
168+
services.TryAddTransient<IMvcRazorHost,MvcRazorHost>();
127169

128170
// This caches Razor page activation details that are valid for the lifetime of the application.
129171
services.TryAddSingleton<IRazorPageActivator, RazorPageActivator>();
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.AspNetCore.Mvc.Rendering;
6+
using Microsoft.AspNetCore.Razor.TagHelpers;
7+
using Microsoft.Extensions.DependencyInjection;
8+
9+
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
10+
{
11+
/// <summary>
12+
/// A <see cref="ITagHelperActivator"/> that retrieves tag helpers as services from the request's
13+
/// <see cref="IServiceProvider"/>.
14+
/// </summary>
15+
public class ServiceBasedTagHelperActivator : ITagHelperActivator
16+
{
17+
/// <inheritdoc />
18+
public TTagHelper Create<TTagHelper>(ViewContext context) where TTagHelper : ITagHelper
19+
{
20+
if (context == null)
21+
{
22+
throw new ArgumentNullException(nameof(context));
23+
}
24+
25+
return context.HttpContext.RequestServices.GetRequiredService<TTagHelper>();
26+
}
27+
}
28+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using Microsoft.AspNetCore.Razor;
7+
using Microsoft.AspNetCore.Mvc.ApplicationParts;
8+
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
9+
using System.Reflection;
10+
11+
namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
12+
{
13+
/// <summary>
14+
/// Resolves tag helper types from the <see cref="ApplicationPartManager.ApplicationParts"/>
15+
/// of the application.
16+
/// </summary>
17+
public class FeatureTagHelperTypeResolver : ITagHelperTypeResolver
18+
{
19+
private readonly ApplicationPartManager _manager;
20+
21+
/// <summary>
22+
/// Initializes a new <see cref="FeatureTagHelperTypeResolver"/> instance.
23+
/// </summary>
24+
/// <param name="manager">The <see cref="ApplicationPartManager"/> of the application.</param>
25+
public FeatureTagHelperTypeResolver(ApplicationPartManager manager)
26+
{
27+
if (manager == null)
28+
{
29+
throw new ArgumentNullException(nameof(manager));
30+
}
31+
32+
_manager = manager;
33+
34+
}
35+
36+
/// <inheritdoc />
37+
public IEnumerable<Type> Resolve(string assemblyName, SourceLocation documentLocation, ErrorSink errorSink)
38+
{
39+
if (assemblyName == null)
40+
{
41+
throw new ArgumentNullException(nameof(assemblyName));
42+
}
43+
44+
if (errorSink == null)
45+
{
46+
throw new ArgumentNullException(nameof(errorSink));
47+
}
48+
49+
var parsedName = new AssemblyName(assemblyName);
50+
Assembly assembly;
51+
try
52+
{
53+
assembly = Assembly.Load(parsedName);
54+
}
55+
catch (Exception ex)
56+
{
57+
errorSink.OnError(
58+
documentLocation,
59+
string.Format(
60+
"Cannot resolve TagHelper containing assembly '{0}'.Error: {1}",
61+
parsedName.Name,
62+
ex.Message),
63+
assemblyName.Length);
64+
65+
return Type.EmptyTypes;
66+
}
67+
68+
var feature = new TagHelperFeature();
69+
_manager.PopulateFeature(feature);
70+
71+
var results = new List<Type>();
72+
for (int i = 0; i < feature.TagHelpers.Count; i++)
73+
{
74+
var tagHelper = feature.TagHelpers[i];
75+
var tagHelperAssembly = tagHelper.Assembly;
76+
77+
if (tagHelperAssembly.Equals(assembly))
78+
{
79+
results.Add(tagHelper.AsType());
80+
}
81+
}
82+
83+
return results;
84+
}
85+
}
86+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using System.Reflection;
6+
using Microsoft.AspNetCore.Mvc.ApplicationParts;
7+
using Microsoft.Extensions.DependencyInjection;
8+
9+
namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
10+
{
11+
/// <summary>
12+
/// The list of tag helper types in an MVC application. The <see cref="TagHelperFeature"/> can be populated
13+
/// using the <see cref="ApplicationPartManager"/> that is available during startup at <see cref="IMvcBuilder.PartManager"/>
14+
/// and <see cref="IMvcCoreBuilder.PartManager"/> or at a later stage by requiring the <see cref="ApplicationPartManager"/>
15+
/// as a dependency in a component.
16+
/// </summary>
17+
public class TagHelperFeature
18+
{
19+
public IList<TypeInfo> TagHelpers { get; } = new List<TypeInfo>();
20+
}
21+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using Microsoft.AspNetCore.Mvc.ApplicationParts;
7+
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
8+
9+
namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
10+
{
11+
/// <summary>
12+
/// Discovers tag helpers from a list of <see cref="ApplicationPart"/> instances.
13+
/// </summary>
14+
public class TagHelperFeatureProvider : IApplicationFeatureProvider<TagHelperFeature>
15+
{
16+
/// <inheritdoc />
17+
public void PopulateFeature(IEnumerable<ApplicationPart> parts, TagHelperFeature feature)
18+
{
19+
foreach (var type in parts.OfType<IApplicationPartTypeProvider>().SelectMany(p => p.Types))
20+
{
21+
if (TagHelperConventions.IsTagHelper(type) && ! feature.TagHelpers.Contains(type))
22+
{
23+
feature.TagHelpers.Add(type);
24+
}
25+
}
26+
}
27+
}
28+
}

test/Microsoft.AspNetCore.Mvc.FunctionalTests/MvcTestFixture.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.IO;
7+
using System.Linq;
78
using System.Net.Http;
89
using System.Reflection;
910
using Microsoft.AspNetCore.Hosting;
@@ -12,6 +13,7 @@
1213
using Microsoft.AspNetCore.Mvc.Infrastructure;
1314
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
1415
using Microsoft.AspNetCore.Mvc.Razor.Internal;
16+
using Microsoft.AspNetCore.Mvc.TagHelpers;
1517
using Microsoft.AspNetCore.TestHost;
1618
using Microsoft.AspNetCore.Testing;
1719
using Microsoft.Extensions.DependencyInjection;
@@ -74,6 +76,7 @@ protected virtual void InitializeServices(IServiceCollection services)
7476

7577
var manager = new ApplicationPartManager();
7678
manager.ApplicationParts.Add(new AssemblyPart(startupAssembly));
79+
7780
services.AddSingleton(manager);
7881
}
7982

0 commit comments

Comments
 (0)