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.Extensions;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.TagHelpers;
@ -187,6 +188,9 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddSingleton<ITagHelperActivator, DefaultTagHelperActivator>();
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.
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.
// 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 Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Logging;
@ -18,10 +17,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
/// <summary>
/// Creates a new <see cref="BodyTagHelper"/>.
/// </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>
public BodyTagHelper(IEnumerable<ITagHelperComponent> components, ILoggerFactory loggerFactory)
: base(components, loggerFactory)
public BodyTagHelper(ITagHelperComponentManager manager, ILoggerFactory loggerFactory)
: base(manager, loggerFactory)
{
}
}

View File

@ -1,7 +1,6 @@
// 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.ComponentModel;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Logging;
@ -18,10 +17,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
/// <summary>
/// Creates a new <see cref="HeadTagHelper"/>.
/// </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>
public HeadTagHelper(IEnumerable<ITagHelperComponent> components, ILoggerFactory loggerFactory)
: base(components, loggerFactory)
public HeadTagHelper(ITagHelperComponentManager manager, ILoggerFactory 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
{
/// <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
{
private readonly ILogger _logger;
@ -18,16 +22,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
private IEnumerable<ITagHelperComponent> _components;
/// <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>
/// <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>
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)
{
if (components == null)
if (manager == null)
{
throw new ArgumentNullException(nameof(components));
throw new ArgumentNullException(nameof(manager));
}
if (loggerFactory == null)
@ -35,14 +44,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
throw new ArgumentNullException(nameof(loggerFactory));
}
_components = components;
_components = manager.Components.OrderBy(p => p.Order).ToArray();
_logger = loggerFactory.CreateLogger(GetType());
}
/// <inheritdoc />
public override void Init(TagHelperContext context)
{
_components = _components.OrderBy(p => p.Order);
foreach (var component in _components)
{
component.Init(context);

View File

@ -61,6 +61,30 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
// Assert
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
ResourceFile.UpdateFile(_resourcesAssembly, outputFile, expectedContent, responseContent);
#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.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
@ -27,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
uniqueId: "test");
var incrementer = 0;
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(new []
var testTagHelperComponentManager = new TagHelperComponentManager(new []
{
new CallbackTagHelperComponent(
order: 2,
@ -53,7 +54,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
incrementer++;
},
processAsyncCallback: null),
}, NullLoggerFactory.Instance);
});
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(testTagHelperComponentManager, NullLoggerFactory.Instance);
// Act
testTagHelperComponentTagHelper.Init(tagHelperContext);
@ -80,7 +83,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
new DefaultTagHelperContent()));
var incrementer = 0;
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(new []
var testTagHelperComponentManager = new TagHelperComponentManager(new []
{
new CallbackTagHelperComponent(
order: 2,
@ -106,7 +109,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
Assert.Equal(0, incrementer);
incrementer++;
}),
}, NullLoggerFactory.Instance);
});
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(testTagHelperComponentManager, NullLoggerFactory.Instance);
// Act
testTagHelperComponentTagHelper.Init(tagHelperContext);
@ -133,10 +138,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent()));
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(new []
var testTagHelperComponentManager = new TagHelperComponentManager(new[]
{
new TestTagHelperComponent()
}, NullLoggerFactory.Instance);
});
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(testTagHelperComponentManager, NullLoggerFactory.Instance);
// Act
testTagHelperComponentTagHelper.Init(tagHelperContext);
@ -162,16 +169,51 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent()));
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(new []
var testTagHelperComponentManager = new TagHelperComponentManager(new []
{
new TestTagHelperComponent()
}, NullLoggerFactory.Instance);
});
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(testTagHelperComponentManager, NullLoggerFactory.Instance);
// Act
await testTagHelperComponentTagHelper.ProcessAsync(tagHelperContext, output);
// 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]
@ -193,10 +235,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent()));
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(new []
var testTagHelperComponentManager = new TagHelperComponentManager(new []
{
new TestTagHelperComponent()
}, loggerFactory);
});
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(testTagHelperComponentManager, loggerFactory);
// Act
testTagHelperComponentTagHelper.Init(tagHelperContext);
@ -224,10 +268,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent()));
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(new []
var testTagHelperComponentManager = new TagHelperComponentManager(new []
{
new TestTagHelperComponent()
}, loggerFactory);
});
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(testTagHelperComponentManager, loggerFactory);
// Act
await testTagHelperComponentTagHelper.ProcessAsync(tagHelperContext, output);
@ -239,9 +285,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
private class TestTagHelperComponentTagHelper : TagHelperComponentTagHelper
{
public TestTagHelperComponentTagHelper(
IEnumerable<ITagHelperComponent> components,
ITagHelperComponentManager manager,
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)
{
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;
}
}

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
{
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"))
{
output.PostContent.AppendHtml("<script>'This was injected!!'</script>");
output.PostContent.AppendHtml(_html);
}
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>