From d27e66a8fc2ec610bd9b6cd5afc452e4fdfc0e77 Mon Sep 17 00:00:00 2001 From: Jass Bagga Date: Tue, 6 Feb 2018 16:07:38 -0800 Subject: [PATCH] Initialize ViewContext for TagHelperComponentTagHelper (#7326) Addresses #7017 --- .../MvcRazorMvcCoreBuilderExtensions.cs | 4 +- .../ITagHelperComponentPropertyActivator.cs | 21 +++++++ .../TagHelperComponentPropertyActivator.cs | 60 ++++++++++++++++++ .../TagHelpers/TagHelperComponentTagHelper.cs | 21 ++++++- .../RazorWebSite.TagHelperComponent.Body.html | 1 + ...TagHelperComponentPropertyActivatorTest.cs | 63 +++++++++++++++++++ .../TagHelperComponentTagHelperTest.cs | 57 +++++++++++++++-- .../AddTagHelperComponentController.cs | 1 + .../Services/TestBodyTagHelperComponent.cs | 9 ++- .../Views/TagHelperComponent/Body.cshtml | 1 + 10 files changed, 229 insertions(+), 9 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentPropertyActivator.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentPropertyActivator.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/TagHelperComponentPropertyActivatorTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs index 0ea907a2ce..69bba1113e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs @@ -175,8 +175,10 @@ namespace Microsoft.Extensions.DependencyInjection // This caches Razor page activation details that are valid for the lifetime of the application. services.TryAddSingleton(); - // 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(); + services.TryAddSingleton(); + services.TryAddSingleton(); // TagHelperComponents manager diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentPropertyActivator.cs b/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentPropertyActivator.cs new file mode 100644 index 0000000000..f502fd90c3 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentPropertyActivator.cs @@ -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 +{ + /// + /// Provides methods to activate properties of s. + /// + public interface ITagHelperComponentPropertyActivator + { + /// + /// Activates properties of the . + /// + /// The for the executing view. + /// The to activate properties of. + void Activate(ViewContext context, ITagHelperComponent tagHelperComponent); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentPropertyActivator.cs b/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentPropertyActivator.cs new file mode 100644 index 0000000000..7da3e3ee4d --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentPropertyActivator.cs @@ -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 +{ + /// + /// Default implementation of . + /// + internal class TagHelperComponentPropertyActivator : ITagHelperComponentPropertyActivator + { + private readonly ConcurrentDictionary[]> _propertiesToActivate; + private readonly Func[]> _getPropertiesToActivate = GetPropertiesToActivate; + private static readonly Func> _createActivateInfo = CreateActivateInfo; + + public TagHelperComponentPropertyActivator() + { + _propertiesToActivate = new ConcurrentDictionary[]>(); + } + + /// + 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 CreateActivateInfo(PropertyInfo property) + { + return new PropertyActivator(property, viewContext => viewContext); + } + + private static PropertyActivator[] GetPropertiesToActivate(Type type) + { + return PropertyActivator.GetPropertiesToActivate( + type, + typeof(ViewContextAttribute), + _createActivateInfo); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentTagHelper.cs index 9946993dc3..ddf68268bd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentTagHelper.cs @@ -6,7 +6,10 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers @@ -18,7 +21,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers public abstract class TagHelperComponentTagHelper : TagHelper { private readonly ILogger _logger; - private readonly IEnumerable _components; /// @@ -49,11 +51,28 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers _logger = loggerFactory.CreateLogger(GetType()); } + /// + /// Activates the property of all the . + /// + [HtmlAttributeNotBound] + public ITagHelperComponentPropertyActivator PropertyActivator { get; set; } + + [ViewContext] + [HtmlAttributeNotBound] + public ViewContext ViewContext { get; set; } + /// public override void Init(TagHelperContext context) { + if (PropertyActivator == null) + { + var serviceProvider = ViewContext.HttpContext.RequestServices; + PropertyActivator = serviceProvider.GetRequiredService(); + } + foreach (var component in _components) { + PropertyActivator.Activate(ViewContext, component); component.Init(context); if (_logger.IsEnabled(LogLevel.Debug)) { diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.TagHelperComponent.Body.html b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.TagHelperComponent.Body.html index af76bea0cd..9d89dab30b 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.TagHelperComponent.Body.html +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.TagHelperComponent.Body.html @@ -1,3 +1,4 @@ Hello from Body Tag Helper Component +

NewValue

\ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/TagHelperComponentPropertyActivatorTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/TagHelperComponentPropertyActivatorTest.cs new file mode 100644 index 0000000000..37fe160c56 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/TagHelperComponentPropertyActivatorTest.cs @@ -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(new TagHelperComponentPropertyActivator()) + .BuildServiceProvider() + }; + + var viewContext = Mock.Of(vc => vc.HttpContext == httpContext); + return viewContext; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/TagHelperComponentTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/TagHelperComponentTagHelperTest.cs index 95a580e278..006c78ab7c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/TagHelperComponentTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/TagHelperComponentTagHelperTest.cs @@ -5,11 +5,15 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Testing; +using Moq; using Xunit; namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers @@ -28,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers uniqueId: "test"); var incrementer = 0; - var testTagHelperComponentManager = new TagHelperComponentManager(new [] + var testTagHelperComponentManager = new TagHelperComponentManager(new[] { new CallbackTagHelperComponent( order: 2, @@ -83,7 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers new DefaultTagHelperContent())); var incrementer = 0; - var testTagHelperComponentManager = new TagHelperComponentManager(new [] + var testTagHelperComponentManager = new TagHelperComponentManager(new[] { new CallbackTagHelperComponent( order: 2, @@ -169,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers getChildContentAsync: (useCachedResult, encoder) => Task.FromResult( new DefaultTagHelperContent())); - var testTagHelperComponentManager = new TagHelperComponentManager(new [] + var testTagHelperComponentManager = new TagHelperComponentManager(new[] { new TestTagHelperComponent() }); @@ -200,7 +204,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers getChildContentAsync: (useCachedResult, encoder) => Task.FromResult( new DefaultTagHelperContent())); - var testTagHelperComponentManager = new TagHelperComponentManager(new [] + var testTagHelperComponentManager = new TagHelperComponentManager(new[] { new TestTagHelperComponent() }); @@ -235,7 +239,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers getChildContentAsync: (useCachedResult, encoder) => Task.FromResult( new DefaultTagHelperContent())); - var testTagHelperComponentManager = new TagHelperComponentManager(new [] + var testTagHelperComponentManager = new TagHelperComponentManager(new[] { new TestTagHelperComponent() }); @@ -268,7 +272,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers getChildContentAsync: (useCachedResult, encoder) => Task.FromResult( new DefaultTagHelperContent())); - var testTagHelperComponentManager = new TagHelperComponentManager(new [] + var testTagHelperComponentManager = new TagHelperComponentManager(new[] { 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); } + [Fact] + public void Init_GetsTagHelperComponentPropertyActivator_FromRequestServices() + { + // Arrange + var tagHelperContext = new TagHelperContext( + "head", + allAttributes: new TagHelperAttributeList( + Enumerable.Empty()), + items: new Dictionary(), + 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 { public TestTagHelperComponentTagHelper( @@ -289,6 +320,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers ILoggerFactory loggerFactory) : base(manager, loggerFactory) { + ViewContext = CreateViewContext(); } } @@ -356,5 +388,18 @@ namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers return Task.CompletedTask; } } + + private static ViewContext CreateViewContext() + { + var httpContext = new DefaultHttpContext() + { + RequestServices = new ServiceCollection() + .AddSingleton(new TagHelperComponentPropertyActivator()) + .BuildServiceProvider() + }; + + var viewContext = Mock.Of(vc => vc.HttpContext == httpContext); + return viewContext; + } } } diff --git a/test/WebSites/RazorWebSite/Controllers/AddTagHelperComponentController.cs b/test/WebSites/RazorWebSite/Controllers/AddTagHelperComponentController.cs index cd318ac287..403520dcff 100644 --- a/test/WebSites/RazorWebSite/Controllers/AddTagHelperComponentController.cs +++ b/test/WebSites/RazorWebSite/Controllers/AddTagHelperComponentController.cs @@ -18,6 +18,7 @@ namespace RazorWebSite.Controllers public IActionResult AddComponent() { _tagHelperComponentManager.Components.Add(new TestBodyTagHelperComponent(0, "Processed TagHelperComponent added from controller.")); + ViewData["TestData"] = "Value"; return View("AddComponent"); } } diff --git a/test/WebSites/RazorWebSite/Services/TestBodyTagHelperComponent.cs b/test/WebSites/RazorWebSite/Services/TestBodyTagHelperComponent.cs index bed0531dc7..750dd61d20 100644 --- a/test/WebSites/RazorWebSite/Services/TestBodyTagHelperComponent.cs +++ b/test/WebSites/RazorWebSite/Services/TestBodyTagHelperComponent.cs @@ -3,6 +3,8 @@ using System; using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; namespace RazorWebSite @@ -23,6 +25,9 @@ namespace RazorWebSite _html = html; } + [ViewContext] + public ViewContext ViewContext { get; set; } + public int Order => _order; public void Init(TagHelperContext context) @@ -31,9 +36,11 @@ namespace RazorWebSite 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); + ViewContext.ViewData["TestData"] = "NewValue"; } return Task.FromResult(0); diff --git a/test/WebSites/RazorWebSite/Views/TagHelperComponent/Body.cshtml b/test/WebSites/RazorWebSite/Views/TagHelperComponent/Body.cshtml index fa0abd5f31..388b94a71d 100644 --- a/test/WebSites/RazorWebSite/Views/TagHelperComponent/Body.cshtml +++ b/test/WebSites/RazorWebSite/Views/TagHelperComponent/Body.cshtml @@ -1,3 +1,4 @@  Hello from Body Tag Helper Component +

@ViewData["TestData"]

\ No newline at end of file