Add ITagHelperComponentManager (#6302)

Addresses #6282
This commit is contained in:
Jass Bagga 2017-05-23 13:22:15 -07:00 committed by GitHub
parent 04f74edd85
commit e681c23d5c
12 changed files with 251 additions and 35 deletions

View File

@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.Extensions; using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
@ -187,6 +188,9 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddSingleton<ITagHelperActivator, DefaultTagHelperActivator>(); services.TryAddSingleton<ITagHelperActivator, DefaultTagHelperActivator>();
services.TryAddSingleton<ITagHelperFactory, DefaultTagHelperFactory>(); services.TryAddSingleton<ITagHelperFactory, DefaultTagHelperFactory>();
// TagHelperComponents manager
services.TryAddScoped<ITagHelperComponentManager, TagHelperComponentManager>();
// Consumed by the Cache tag helper to cache results across the lifetime of the application. // Consumed by the Cache tag helper to cache results across the lifetime of the application.
services.TryAddSingleton<IMemoryCache, MemoryCache>(); services.TryAddSingleton<IMemoryCache, MemoryCache>();
} }

View File

@ -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 System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
{
/// <summary>
/// The default implementation of the <see cref="ITagHelperComponentManager"/>.
/// </summary>
public class TagHelperComponentManager : ITagHelperComponentManager
{
/// <summary>
/// Creates a new <see cref="TagHelperComponentManager"/>.
/// </summary>
/// <param name="tagHelperComponents">The collection of <see cref="ITagHelperComponent"/>s.</param>
public TagHelperComponentManager(IEnumerable<ITagHelperComponent> tagHelperComponents)
{
if (tagHelperComponents == null)
{
throw new ArgumentNullException(nameof(tagHelperComponents));
}
Components = new List<ITagHelperComponent>(tagHelperComponents);
}
/// <inheritdoc />
public ICollection<ITagHelperComponent> Components { get; }
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -18,10 +17,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
/// <summary> /// <summary>
/// Creates a new <see cref="BodyTagHelper"/>. /// Creates a new <see cref="BodyTagHelper"/>.
/// </summary> /// </summary>
/// <param name="components">The list of <see cref="ITagHelperComponent"/>.</param> /// <param name="manager">The <see cref="ITagHelperComponentManager"/> which contains the collection
/// of <see cref="ITagHelperComponent"/>s.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param> /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public BodyTagHelper(IEnumerable<ITagHelperComponent> components, ILoggerFactory loggerFactory) public BodyTagHelper(ITagHelperComponentManager manager, ILoggerFactory loggerFactory)
: base(components, loggerFactory) : base(manager, loggerFactory)
{ {
} }
} }

View File

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -18,10 +17,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
/// <summary> /// <summary>
/// Creates a new <see cref="HeadTagHelper"/>. /// Creates a new <see cref="HeadTagHelper"/>.
/// </summary> /// </summary>
/// <param name="components">The list of <see cref="ITagHelperComponent"/>.</param> /// <param name="manager">The <see cref="ITagHelperComponentManager"/> which contains the collection
/// of <see cref="ITagHelperComponent"/>s.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param> /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public HeadTagHelper(IEnumerable<ITagHelperComponent> components, ILoggerFactory loggerFactory) public HeadTagHelper(ITagHelperComponentManager manager, ILoggerFactory loggerFactory)
: base(components, loggerFactory) : base(manager, loggerFactory)
{ {
} }
} }

View File

@ -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 Microsoft.AspNetCore.Razor.TagHelpers;
namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
{
/// <summary>
/// An implementation of this interface provides the collection of <see cref="ITagHelperComponent"/>s
/// that will be used by <see cref="TagHelperComponentTagHelper"/>s.
/// </summary>
public interface ITagHelperComponentManager
{
/// <summary>
/// Gets the collection of <see cref="ITagHelperComponent"/>s that will be used by
/// <see cref="TagHelperComponentTagHelper"/>s.
/// </summary>
ICollection<ITagHelperComponent> Components { get; }
}
}

View File

@ -11,6 +11,10 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
{ {
/// <summary>
/// Initializes and processes the <see cref="ITagHelperComponent"/>s added to the
/// <see cref="ITagHelperComponentManager.Components"/> in the specified order.
/// </summary>
public abstract class TagHelperComponentTagHelper : TagHelper public abstract class TagHelperComponentTagHelper : TagHelper
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
@ -18,16 +22,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
private IEnumerable<ITagHelperComponent> _components; private IEnumerable<ITagHelperComponent> _components;
/// <summary> /// <summary>
/// Creates a new <see cref="TagHelperComponentTagHelper"/>. /// Creates a new <see cref="TagHelperComponentTagHelper"/> and orders the
/// the collection of <see cref="ITagHelperComponent"/>s in <see cref="ITagHelperComponentManager.Components"/>.
/// </summary> /// </summary>
/// <param name="components">The list of <see cref="ITagHelperComponent"/>.</param> /// <param name="manager">The <see cref="ITagHelperComponentManager"/> which contains the collection
/// of <see cref="ITagHelperComponent"/>s.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param> /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public TagHelperComponentTagHelper(IEnumerable<ITagHelperComponent> components, /// <remarks>The <see cref="ITagHelperComponentManager.Components"/> are ordered after the
/// creation of the <see cref="ITagHelperComponentManager"/> to position the <see cref="ITagHelperComponent"/>s
/// added from controllers and views correctly.</remarks>
public TagHelperComponentTagHelper(ITagHelperComponentManager manager,
ILoggerFactory loggerFactory) ILoggerFactory loggerFactory)
{ {
if (components == null) if (manager == null)
{ {
throw new ArgumentNullException(nameof(components)); throw new ArgumentNullException(nameof(manager));
} }
if (loggerFactory == null) if (loggerFactory == null)
@ -35,14 +44,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
throw new ArgumentNullException(nameof(loggerFactory)); throw new ArgumentNullException(nameof(loggerFactory));
} }
_components = components; _components = manager.Components.OrderBy(p => p.Order).ToArray();
_logger = loggerFactory.CreateLogger(GetType()); _logger = loggerFactory.CreateLogger(GetType());
} }
/// <inheritdoc /> /// <inheritdoc />
public override void Init(TagHelperContext context) public override void Init(TagHelperContext context)
{ {
_components = _components.OrderBy(p => p.Order);
foreach (var component in _components) foreach (var component in _components)
{ {
component.Init(context); component.Init(context);

View File

@ -61,6 +61,30 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
// Assert // Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
#if GENERATE_BASELINES
ResourceFile.UpdateFile(_resourcesAssembly, outputFile, expectedContent, responseContent);
#else
Assert.Equal(expectedContent, responseContent, ignoreLineEndingDifferences: true);
#endif
}
[Fact]
public async Task AddTestTagHelperComponent_FromController()
{
// Arrange
var url = "http://localhost/AddTagHelperComponent/AddComponent";
var request = new HttpRequestMessage(HttpMethod.Get, url);
var outputFile = "compiler/resources/RazorWebSite.AddTagHelperComponent.AddComponent.html";
var expectedContent =
await ResourceFile.ReadResourceAsync(_resourcesAssembly, outputFile, sourceFile: false);
// Act
var response = await Client.SendAsync(request);
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
#if GENERATE_BASELINES #if GENERATE_BASELINES
ResourceFile.UpdateFile(_resourcesAssembly, outputFile, expectedContent, responseContent); ResourceFile.UpdateFile(_resourcesAssembly, outputFile, expectedContent, responseContent);
#else #else

View File

@ -0,0 +1,6 @@
<head inject>
Hello from Head Tag Helper Component
<script>'This was injected!!'</script></head>
<body inject>
Hello from Body Tag Helper Component
Processed TagHelperComponent added from controller.<script>'This was injected!!'</script>Processed TagHelperComponent added from view.</body>

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
@ -27,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
uniqueId: "test"); uniqueId: "test");
var incrementer = 0; var incrementer = 0;
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(new [] var testTagHelperComponentManager = new TagHelperComponentManager(new []
{ {
new CallbackTagHelperComponent( new CallbackTagHelperComponent(
order: 2, order: 2,
@ -53,7 +54,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
incrementer++; incrementer++;
}, },
processAsyncCallback: null), processAsyncCallback: null),
}, NullLoggerFactory.Instance); });
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(testTagHelperComponentManager, NullLoggerFactory.Instance);
// Act // Act
testTagHelperComponentTagHelper.Init(tagHelperContext); testTagHelperComponentTagHelper.Init(tagHelperContext);
@ -80,7 +83,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
new DefaultTagHelperContent())); new DefaultTagHelperContent()));
var incrementer = 0; var incrementer = 0;
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(new [] var testTagHelperComponentManager = new TagHelperComponentManager(new []
{ {
new CallbackTagHelperComponent( new CallbackTagHelperComponent(
order: 2, order: 2,
@ -106,7 +109,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
Assert.Equal(0, incrementer); Assert.Equal(0, incrementer);
incrementer++; incrementer++;
}), }),
}, NullLoggerFactory.Instance); });
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(testTagHelperComponentManager, NullLoggerFactory.Instance);
// Act // Act
testTagHelperComponentTagHelper.Init(tagHelperContext); testTagHelperComponentTagHelper.Init(tagHelperContext);
@ -133,10 +138,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>( getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent())); new DefaultTagHelperContent()));
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(new [] var testTagHelperComponentManager = new TagHelperComponentManager(new[]
{ {
new TestTagHelperComponent() new TestTagHelperComponent()
}, NullLoggerFactory.Instance); });
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(testTagHelperComponentManager, NullLoggerFactory.Instance);
// Act // Act
testTagHelperComponentTagHelper.Init(tagHelperContext); testTagHelperComponentTagHelper.Init(tagHelperContext);
@ -162,16 +169,51 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>( getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent())); new DefaultTagHelperContent()));
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(new [] var testTagHelperComponentManager = new TagHelperComponentManager(new []
{ {
new TestTagHelperComponent() new TestTagHelperComponent()
}, NullLoggerFactory.Instance); });
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(testTagHelperComponentManager, NullLoggerFactory.Instance);
// Act // Act
await testTagHelperComponentTagHelper.ProcessAsync(tagHelperContext, output); await testTagHelperComponentTagHelper.ProcessAsync(tagHelperContext, output);
// Assert // Assert
Assert.Equal("Processed", output.PostContent.GetContent()); Assert.Equal("Processed1", output.PostContent.GetContent());
}
[Fact]
public async Task ProcessAsync_InvokesTagHelperComponentProcessAsync_WithAddedTagHelperComponents()
{
// Arrange
var tagHelperContext = new TagHelperContext(
"head",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
"head",
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent()));
var testTagHelperComponentManager = new TagHelperComponentManager(new []
{
new TestTagHelperComponent()
});
testTagHelperComponentManager.Components.Add(new TestAddTagHelperComponent(0));
testTagHelperComponentManager.Components.Add(new TestAddTagHelperComponent(2));
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(testTagHelperComponentManager, NullLoggerFactory.Instance);
// Act
await testTagHelperComponentTagHelper.ProcessAsync(tagHelperContext, output);
// Assert
Assert.Equal("Processed0Processed1Processed2", output.PostContent.GetContent());
} }
[Fact] [Fact]
@ -193,10 +235,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>( getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent())); new DefaultTagHelperContent()));
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(new [] var testTagHelperComponentManager = new TagHelperComponentManager(new []
{ {
new TestTagHelperComponent() new TestTagHelperComponent()
}, loggerFactory); });
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(testTagHelperComponentManager, loggerFactory);
// Act // Act
testTagHelperComponentTagHelper.Init(tagHelperContext); testTagHelperComponentTagHelper.Init(tagHelperContext);
@ -224,10 +268,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>( getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent())); new DefaultTagHelperContent()));
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(new [] var testTagHelperComponentManager = new TagHelperComponentManager(new []
{ {
new TestTagHelperComponent() new TestTagHelperComponent()
}, loggerFactory); });
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(testTagHelperComponentManager, loggerFactory);
// Act // Act
await testTagHelperComponentTagHelper.ProcessAsync(tagHelperContext, output); await testTagHelperComponentTagHelper.ProcessAsync(tagHelperContext, output);
@ -239,9 +285,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
private class TestTagHelperComponentTagHelper : TagHelperComponentTagHelper private class TestTagHelperComponentTagHelper : TagHelperComponentTagHelper
{ {
public TestTagHelperComponentTagHelper( public TestTagHelperComponentTagHelper(
IEnumerable<ITagHelperComponent> components, ITagHelperComponentManager manager,
ILoggerFactory loggerFactory) ILoggerFactory loggerFactory)
: base(components, loggerFactory) : base(manager, loggerFactory)
{ {
} }
} }
@ -284,7 +330,29 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output) public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{ {
output.PostContent.AppendHtml("Processed"); output.PostContent.AppendHtml("Processed1");
return Task.CompletedTask;
}
}
private class TestAddTagHelperComponent : ITagHelperComponent
{
private int _order;
public TestAddTagHelperComponent(int order)
{
_order = order;
}
public int Order => _order;
public void Init(TagHelperContext context)
{
}
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
output.PostContent.AppendHtml("Processed" + Order);
return Task.CompletedTask; return Task.CompletedTask;
} }
} }

View File

@ -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 Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
namespace RazorWebSite.Controllers
{
public class AddTagHelperComponentController : Controller
{
private readonly ITagHelperComponentManager _tagHelperComponentManager;
public AddTagHelperComponentController(ITagHelperComponentManager tagHelperComponentManager)
{
_tagHelperComponentManager = tagHelperComponentManager;
}
public IActionResult AddComponent()
{
_tagHelperComponentManager.Components.Add(new TestBodyTagHelperComponent(0, "Processed TagHelperComponent added from controller."));
return View("AddComponent");
}
}
}

View File

@ -7,15 +7,33 @@ using Microsoft.AspNetCore.Razor.TagHelpers;
namespace RazorWebSite namespace RazorWebSite
{ {
public class TestBodyTagHelperComponent : TagHelperComponent public class TestBodyTagHelperComponent : ITagHelperComponent
{ {
public override int Order => 1; private int _order;
private string _html;
public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) public TestBodyTagHelperComponent() : this(1, "<script>'This was injected!!'</script>")
{
}
public TestBodyTagHelperComponent(int order, string html)
{
_order = order;
_html = html;
}
public int Order => _order;
public void Init(TagHelperContext context)
{
}
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{ {
if (string.Equals(context.TagName, "body", StringComparison.Ordinal) && output.Attributes.ContainsName("inject")) if (string.Equals(context.TagName, "body", StringComparison.Ordinal) && output.Attributes.ContainsName("inject"))
{ {
output.PostContent.AppendHtml("<script>'This was injected!!'</script>"); output.PostContent.AppendHtml(_html);
} }
return Task.FromResult(0); return Task.FromResult(0);

View File

@ -0,0 +1,10 @@
@inject Microsoft.AspNetCore.Mvc.Razor.TagHelpers.ITagHelperComponentManager manager
<head inject>
Hello from Head Tag Helper Component
</head>
@{
manager.Components.Add(new TestBodyTagHelperComponent(2, "Processed TagHelperComponent added from view."));
}
<body inject>
Hello from Body Tag Helper Component
</body>