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 support for AddTagHelpersAsServices()
Browse files Browse the repository at this point in the history
  • Loading branch information
javiercn committed Apr 1, 2016
1 parent 5246125 commit 830a873
Show file tree
Hide file tree
Showing 22 changed files with 763 additions and 11 deletions.
5 changes: 4 additions & 1 deletion src/Microsoft.AspNetCore.Mvc.Razor.Host/MvcRazorHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,12 @@ public MvcRazorHost(string root)
/// Initializes a new instance of <see cref="MvcRazorHost"/> using the specified <paramref name="chunkTreeCache"/>.
/// </summary>
/// <param name="chunkTreeCache">An <see cref="IChunkTreeCache"/> rooted at the application base path.</param>
public MvcRazorHost(IChunkTreeCache chunkTreeCache)
/// <param name="resolver">The <see cref="ITagHelperDescriptorResolver"/> used to resolve tag helpers on razor views.</param>
public MvcRazorHost(IChunkTreeCache chunkTreeCache, ITagHelperDescriptorResolver resolver)
: this(chunkTreeCache, new RazorPathNormalizer())
{

TagHelperDescriptorResolver = resolver;
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.DependencyInjection.Extensions;

Expand Down Expand Up @@ -41,6 +44,28 @@ public static IMvcBuilder AddRazorOptions(
return builder;
}

/// <summary>
/// Registers tag helpers as services and changes the existing <see cref="ITagHelperActivator"/>
/// for an <see cref="ServiceBasedTagHelperActivator"/>.
/// </summary>
/// <param name="builder">The <see cref="IMvcBuilder"/> instance this method extends.</param>
/// <returns>The <see cref="IMvcBuilder"/> instance this method extends.</returns>
public static IMvcBuilder AddTagHelpersAsServices(this IMvcBuilder builder)
{
var feature = new TagHelperFeature();
builder.PartManager.PopulateFeature(feature);

foreach (var type in feature.TagHelpers.Select(t => t.AsType()))
{
builder.Services.TryAddTransient(type, type);
}

builder.Services.Replace(ServiceDescriptor.Transient<ITagHelperActivator, ServiceBasedTagHelperActivator>());
builder.Services.Replace(ServiceDescriptor.Transient<ITagHelperTypeResolver, FeatureTagHelperTypeResolver>());

return builder;
}

/// <summary>
/// Adds an initialization callback for a given <typeparamref name="TTagHelper"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@
// 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.Razor;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.Directives;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.PlatformAbstractions;

namespace Microsoft.Extensions.DependencyInjection
{
Expand All @@ -26,10 +29,19 @@ public static IMvcCoreBuilder AddRazorViewEngine(this IMvcCoreBuilder builder)
}

builder.AddViews();
AddRazorViewEngineFeatureProviders(builder);
AddRazorViewEngineServices(builder.Services);
return builder;
}

private static void AddRazorViewEngineFeatureProviders(IMvcCoreBuilder builder)
{
if (!builder.PartManager.FeatureProviders.OfType<TagHelperFeatureProvider>().Any())
{
builder.PartManager.FeatureProviders.Add(new TagHelperFeatureProvider());
}
}

public static IMvcCoreBuilder AddRazorViewEngine(
this IMvcCoreBuilder builder,
Action<RazorViewEngineOptions> setupAction)
Expand All @@ -45,6 +57,8 @@ public static IMvcCoreBuilder AddRazorViewEngine(
}

builder.AddViews();

AddRazorViewEngineFeatureProviders(builder);
AddRazorViewEngineServices(builder.Services);

if (setupAction != null)
Expand All @@ -55,6 +69,28 @@ public static IMvcCoreBuilder AddRazorViewEngine(
return builder;
}

/// <summary>
/// Registers discovered tag helpers as services and changes the existing <see cref="ITagHelperActivator"/>
/// for an <see cref="ServiceBasedTagHelperActivator"/>.
/// </summary>
/// <param name="builder">The <see cref="IMvcCoreBuilder"/> instance this method extends.</param>
/// <returns>The <see cref="IMvcCoreBuilder"/> instance this method extends.</returns>
public static IMvcCoreBuilder AddTagHelpersAsServices(this IMvcCoreBuilder builder)
{
var feature = new TagHelperFeature();
builder.PartManager.PopulateFeature(feature);

foreach (var type in feature.TagHelpers.Select(t => t.AsType()))
{
builder.Services.TryAddTransient(type, type);
}

builder.Services.Replace(ServiceDescriptor.Transient<ITagHelperActivator, ServiceBasedTagHelperActivator>());
builder.Services.Replace(ServiceDescriptor.Transient<ITagHelperTypeResolver, FeatureTagHelperTypeResolver>());

return builder;
}

/// <summary>
/// Adds an initialization callback for a given <typeparamref name="TTagHelper"/>.
/// </summary>
Expand Down Expand Up @@ -116,14 +152,20 @@ internal static void AddRazorViewEngineServices(IServiceCollection services)
return new DefaultChunkTreeCache(accessor.FileProvider);
}));

services.TryAddTransient<ITagHelperDescriptorResolver, TagHelperDescriptorResolver>();

services.TryAddTransient<ITagHelperTypeResolver, TagHelperTypeResolver>();
services.TryAddTransient<ITagHelperDescriptorFactory>(s => new TagHelperDescriptorFactory(designTime: false));

// Caches compilation artifacts across the lifetime of the application.
services.TryAddSingleton<ICompilerCacheProvider, DefaultCompilerCacheProvider>();

// In the default scenario the following services are singleton by virtue of being initialized as part of
// creating the singleton RazorViewEngine instance.
services.TryAddTransient<IRazorPageFactoryProvider, DefaultRazorPageFactoryProvider>();
services.TryAddTransient<IRazorCompilationService, RazorCompilationService>();
services.TryAddTransient<IMvcRazorHost, MvcRazorHost>();

services.TryAddTransient<IMvcRazorHost,MvcRazorHost>();

// This caches Razor page activation details that are valid for the lifetime of the application.
services.TryAddSingleton<IRazorPageActivator, RazorPageActivator>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.AspNetCore.Mvc.Razor.Internal
{
/// <summary>
/// A <see cref="ITagHelperActivator"/> that retrieves tag helpers as services from the request's
/// <see cref="IServiceProvider"/>.
/// </summary>
public class ServiceBasedTagHelperActivator : ITagHelperActivator
{
/// <inheritdoc />
public TTagHelper Create<TTagHelper>(ViewContext context) where TTagHelper : ITagHelper
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

return context.HttpContext.RequestServices.GetRequiredService<TTagHelper>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// 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 Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
using System.Reflection;

namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
{
/// <summary>
/// Resolves tag helper types from the <see cref="ApplicationPartManager.ApplicationParts"/>
/// of the application.
/// </summary>
public class FeatureTagHelperTypeResolver : ITagHelperTypeResolver
{
private readonly ApplicationPartManager _manager;

/// <summary>
/// Initializes a new <see cref="FeatureTagHelperTypeResolver"/> instance.
/// </summary>
/// <param name="manager">The <see cref="ApplicationPartManager"/> of the application.</param>
public FeatureTagHelperTypeResolver(ApplicationPartManager manager)
{
if (manager == null)
{
throw new ArgumentNullException(nameof(manager));
}

_manager = manager;

}

/// <inheritdoc />
public IEnumerable<Type> Resolve(string assemblyName, SourceLocation documentLocation, ErrorSink errorSink)
{
if (assemblyName == null)
{
throw new ArgumentNullException(nameof(assemblyName));
}

if (errorSink == null)
{
throw new ArgumentNullException(nameof(errorSink));
}

var parsedName = new AssemblyName(assemblyName);
Assembly assembly;
try
{
assembly = Assembly.Load(parsedName);
}
catch (Exception ex)
{
errorSink.OnError(
documentLocation,
string.Format(
"Cannot resolve TagHelper containing assembly '{0}'.Error: {1}",
parsedName.Name,
ex.Message),
assemblyName.Length);

return Type.EmptyTypes;
}

var feature = new TagHelperFeature();
_manager.PopulateFeature(feature);

var results = new List<Type>();
for (int i = 0; i < feature.TagHelpers.Count; i++)
{
var tagHelper = feature.TagHelpers[i];
var tagHelperAssembly = tagHelper.Assembly;

if (tagHelperAssembly.Equals(assembly))
{
results.Add(tagHelper.AsType());
}
}

return results;
}
}
}
21 changes: 21 additions & 0 deletions src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperFeature.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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.Razor.TagHelpers
{
/// <summary>
/// The list of tag helper types in an MVC application. The <see cref="TagHelperFeature"/> 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 TagHelperFeature
{
public IList<TypeInfo> TagHelpers { get; } = new List<TypeInfo>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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 Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;

namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
{
/// <summary>
/// Discovers tag helpers from a list of <see cref="ApplicationPart"/> instances.
/// </summary>
public class TagHelperFeatureProvider : IApplicationFeatureProvider<TagHelperFeature>
{
/// <inheritdoc />
public void PopulateFeature(IEnumerable<ApplicationPart> parts, TagHelperFeature feature)
{
foreach (var type in parts.OfType<IApplicationPartTypeProvider>().SelectMany(p => p.Types))
{
if (TagHelperConventions.IsTagHelper(type) && ! feature.TagHelpers.Contains(type))
{
feature.TagHelpers.Add(type);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.TestHost;
using Microsoft.AspNetCore.Testing;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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.Net.Http;
using System.Threading.Tasks;
using Xunit;

namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class TagHelpersFromServicesTest : IClassFixture<MvcTestFixture<ControllersFromServicesWebSite.Startup>>
{
public TagHelpersFromServicesTest(MvcTestFixture<ControllersFromServicesWebSite.Startup> fixture)
{
Client = fixture.Client;
}

public HttpClient Client { get; }

[Fact]
public async Task TagHelpersWithConstructorInjectionAreCreatedAndActivated()
{
// Arrange
var expected = "3";
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/another/inservicestaghelper");

// Act
var response = await Client.SendAsync(request);
var responseText = await response.Content.ReadAsStringAsync();

// Assert
Assert.Equal(expected, responseText.Trim());
}
}
}
Loading

0 comments on commit 830a873

Please sign in to comment.