Initialize ViewContext for TagHelperComponentTagHelper (#7326)

Addresses #7017
This commit is contained in:
Jass Bagga 2018-02-06 16:07:38 -08:00 committed by GitHub
parent 8e303249c5
commit d27e66a8fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 229 additions and 9 deletions

View File

@ -175,8 +175,10 @@ namespace Microsoft.Extensions.DependencyInjection
// This caches Razor page activation details that are valid for the lifetime of the application. // This caches Razor page activation details that are valid for the lifetime of the application.
services.TryAddSingleton<IRazorPageActivator, RazorPageActivator>(); services.TryAddSingleton<IRazorPageActivator, RazorPageActivator>();
// Only want one ITagHelperActivator so it can cache Type activation information. Types won't conflict. // Only want one ITagHelperActivator and ITagHelperComponentPropertyActivator so it can cache Type activation information. Types won't conflict.
services.TryAddSingleton<ITagHelperActivator, DefaultTagHelperActivator>(); services.TryAddSingleton<ITagHelperActivator, DefaultTagHelperActivator>();
services.TryAddSingleton<ITagHelperComponentPropertyActivator, TagHelperComponentPropertyActivator>();
services.TryAddSingleton<ITagHelperFactory, DefaultTagHelperFactory>(); services.TryAddSingleton<ITagHelperFactory, DefaultTagHelperFactory>();
// TagHelperComponents manager // TagHelperComponents manager

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 Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
{
/// <summary>
/// Provides methods to activate properties of <see cref="ITagHelperComponent"/>s.
/// </summary>
public interface ITagHelperComponentPropertyActivator
{
/// <summary>
/// Activates properties of the <paramref name="tagHelperComponent"/>.
/// </summary>
/// <param name="context">The <see cref="ViewContext"/> for the executing view.</param>
/// <param name="tagHelperComponent">The <see cref="ITagHelperComponent"/> to activate properties of.</param>
void Activate(ViewContext context, ITagHelperComponent tagHelperComponent);
}
}

View File

@ -0,0 +1,60 @@
// 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.Concurrent;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
{
/// <summary>
/// Default implementation of <see cref="ITagHelperComponentPropertyActivator"/>.
/// </summary>
internal class TagHelperComponentPropertyActivator : ITagHelperComponentPropertyActivator
{
private readonly ConcurrentDictionary<Type, PropertyActivator<ViewContext>[]> _propertiesToActivate;
private readonly Func<Type, PropertyActivator<ViewContext>[]> _getPropertiesToActivate = GetPropertiesToActivate;
private static readonly Func<PropertyInfo, PropertyActivator<ViewContext>> _createActivateInfo = CreateActivateInfo;
public TagHelperComponentPropertyActivator()
{
_propertiesToActivate = new ConcurrentDictionary<Type, PropertyActivator<ViewContext>[]>();
}
/// <inheritdoc />
public void Activate(ViewContext context, ITagHelperComponent tagHelperComponent)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var propertiesToActivate = _propertiesToActivate.GetOrAdd(
tagHelperComponent.GetType(),
_getPropertiesToActivate);
for (var i = 0; i < propertiesToActivate.Length; i++)
{
var activateInfo = propertiesToActivate[i];
activateInfo.Activate(tagHelperComponent, context);
}
}
private static PropertyActivator<ViewContext> CreateActivateInfo(PropertyInfo property)
{
return new PropertyActivator<ViewContext>(property, viewContext => viewContext);
}
private static PropertyActivator<ViewContext>[] GetPropertiesToActivate(Type type)
{
return PropertyActivator<ViewContext>.GetPropertiesToActivate(
type,
typeof(ViewContextAttribute),
_createActivateInfo);
}
}
}

View File

@ -6,7 +6,10 @@ 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.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
@ -18,7 +21,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
public abstract class TagHelperComponentTagHelper : TagHelper public abstract class TagHelperComponentTagHelper : TagHelper
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IEnumerable<ITagHelperComponent> _components; private readonly IEnumerable<ITagHelperComponent> _components;
/// <summary> /// <summary>
@ -49,11 +51,28 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
_logger = loggerFactory.CreateLogger(GetType()); _logger = loggerFactory.CreateLogger(GetType());
} }
/// <summary>
/// Activates the <see cref="ViewContext"/> property of all the <see cref="ITagHelperComponentManager.Components"/>.
/// </summary>
[HtmlAttributeNotBound]
public ITagHelperComponentPropertyActivator PropertyActivator { get; set; }
[ViewContext]
[HtmlAttributeNotBound]
public ViewContext ViewContext { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public override void Init(TagHelperContext context) public override void Init(TagHelperContext context)
{ {
if (PropertyActivator == null)
{
var serviceProvider = ViewContext.HttpContext.RequestServices;
PropertyActivator = serviceProvider.GetRequiredService<ITagHelperComponentPropertyActivator>();
}
foreach (var component in _components) foreach (var component in _components)
{ {
PropertyActivator.Activate(ViewContext, component);
component.Init(context); component.Init(context);
if (_logger.IsEnabled(LogLevel.Debug)) if (_logger.IsEnabled(LogLevel.Debug))
{ {

View File

@ -1,3 +1,4 @@
<body inject="true"> <body inject="true">
Hello from Body Tag Helper Component Hello from Body Tag Helper Component
<p>NewValue</p>
<script>'This was injected!!'</script></body> <script>'This was injected!!'</script></body>

View File

@ -0,0 +1,63 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
{
public class TagHelperComponentPropertyActivatorTest
{
[Fact]
public void Activate_InitializesViewContext()
{
// Arrange
var tagHelperComponent = new TestTagHelperComponent();
var viewContext = CreateViewContext();
var propertyActivator = new TagHelperComponentPropertyActivator();
// Act
propertyActivator.Activate(viewContext, tagHelperComponent);
// Assert
Assert.Same(viewContext, tagHelperComponent.ViewContext);
}
private class TestTagHelperComponent : ITagHelperComponent
{
public int Order => 1;
[ViewContext]
public ViewContext ViewContext { get; set; }
public void Init(TagHelperContext context)
{
}
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
return Task.CompletedTask;
}
}
private static ViewContext CreateViewContext()
{
var httpContext = new DefaultHttpContext()
{
RequestServices = new ServiceCollection()
.AddSingleton<ITagHelperComponentPropertyActivator>(new TagHelperComponentPropertyActivator())
.BuildServiceProvider()
};
var viewContext = Mock.Of<ViewContext>(vc => vc.HttpContext == httpContext);
return viewContext;
}
}
}

View File

@ -5,11 +5,15 @@ 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.Http;
using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Moq;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
@ -28,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
uniqueId: "test"); uniqueId: "test");
var incrementer = 0; var incrementer = 0;
var testTagHelperComponentManager = new TagHelperComponentManager(new [] var testTagHelperComponentManager = new TagHelperComponentManager(new[]
{ {
new CallbackTagHelperComponent( new CallbackTagHelperComponent(
order: 2, order: 2,
@ -83,7 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
new DefaultTagHelperContent())); new DefaultTagHelperContent()));
var incrementer = 0; var incrementer = 0;
var testTagHelperComponentManager = new TagHelperComponentManager(new [] var testTagHelperComponentManager = new TagHelperComponentManager(new[]
{ {
new CallbackTagHelperComponent( new CallbackTagHelperComponent(
order: 2, order: 2,
@ -169,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>( getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent())); new DefaultTagHelperContent()));
var testTagHelperComponentManager = new TagHelperComponentManager(new [] var testTagHelperComponentManager = new TagHelperComponentManager(new[]
{ {
new TestTagHelperComponent() new TestTagHelperComponent()
}); });
@ -200,7 +204,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>( getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent())); new DefaultTagHelperContent()));
var testTagHelperComponentManager = new TagHelperComponentManager(new [] var testTagHelperComponentManager = new TagHelperComponentManager(new[]
{ {
new TestTagHelperComponent() new TestTagHelperComponent()
}); });
@ -235,7 +239,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>( getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent())); new DefaultTagHelperContent()));
var testTagHelperComponentManager = new TagHelperComponentManager(new [] var testTagHelperComponentManager = new TagHelperComponentManager(new[]
{ {
new TestTagHelperComponent() new TestTagHelperComponent()
}); });
@ -268,7 +272,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>( getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent())); new DefaultTagHelperContent()));
var testTagHelperComponentManager = new TagHelperComponentManager(new [] var testTagHelperComponentManager = new TagHelperComponentManager(new[]
{ {
new TestTagHelperComponent() new TestTagHelperComponent()
}); });
@ -282,6 +286,33 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
Assert.Equal($"Tag helper component '{typeof(TestTagHelperComponent)}' processed.", sink.Writes[0].State.ToString(), StringComparer.Ordinal); Assert.Equal($"Tag helper component '{typeof(TestTagHelperComponent)}' processed.", sink.Writes[0].State.ToString(), StringComparer.Ordinal);
} }
[Fact]
public void Init_GetsTagHelperComponentPropertyActivator_FromRequestServices()
{
// Arrange
var tagHelperContext = new TagHelperContext(
"head",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var testTagHelperComponentManager = new TagHelperComponentManager(new[]
{
new TestTagHelperComponent()
});
var testTagHelperComponentTagHelper = new TestTagHelperComponentTagHelper(
testTagHelperComponentManager,
NullLoggerFactory.Instance);
// Act
testTagHelperComponentTagHelper.Init(tagHelperContext);
// Assert
Assert.NotNull(testTagHelperComponentTagHelper.PropertyActivator);
}
private class TestTagHelperComponentTagHelper : TagHelperComponentTagHelper private class TestTagHelperComponentTagHelper : TagHelperComponentTagHelper
{ {
public TestTagHelperComponentTagHelper( public TestTagHelperComponentTagHelper(
@ -289,6 +320,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
ILoggerFactory loggerFactory) ILoggerFactory loggerFactory)
: base(manager, loggerFactory) : base(manager, loggerFactory)
{ {
ViewContext = CreateViewContext();
} }
} }
@ -356,5 +388,18 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers
return Task.CompletedTask; return Task.CompletedTask;
} }
} }
private static ViewContext CreateViewContext()
{
var httpContext = new DefaultHttpContext()
{
RequestServices = new ServiceCollection()
.AddSingleton<ITagHelperComponentPropertyActivator>(new TagHelperComponentPropertyActivator())
.BuildServiceProvider()
};
var viewContext = Mock.Of<ViewContext>(vc => vc.HttpContext == httpContext);
return viewContext;
}
} }
} }

View File

@ -18,6 +18,7 @@ namespace RazorWebSite.Controllers
public IActionResult AddComponent() public IActionResult AddComponent()
{ {
_tagHelperComponentManager.Components.Add(new TestBodyTagHelperComponent(0, "Processed TagHelperComponent added from controller.")); _tagHelperComponentManager.Components.Add(new TestBodyTagHelperComponent(0, "Processed TagHelperComponent added from controller."));
ViewData["TestData"] = "Value";
return View("AddComponent"); return View("AddComponent");
} }
} }

View File

@ -3,6 +3,8 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
namespace RazorWebSite namespace RazorWebSite
@ -23,6 +25,9 @@ namespace RazorWebSite
_html = html; _html = html;
} }
[ViewContext]
public ViewContext ViewContext { get; set; }
public int Order => _order; public int Order => _order;
public void Init(TagHelperContext context) public void Init(TagHelperContext context)
@ -31,9 +36,11 @@ namespace RazorWebSite
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output) 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(_html); output.PostContent.AppendHtml(_html);
ViewContext.ViewData["TestData"] = "NewValue";
} }
return Task.FromResult(0); return Task.FromResult(0);

View File

@ -1,3 +1,4 @@
<body inject="true"> <body inject="true">
Hello from Body Tag Helper Component Hello from Body Tag Helper Component
<p>@ViewData["TestData"]</p>
</body> </body>