From 680e9bb2d12c3316bbbfa3f87b5288da6a1856f6 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Thu, 24 Mar 2016 22:22:15 -0700 Subject: [PATCH] Refactor and cleanup tag helper creation. * Moved instantiation of tag helpers into DefaultTagHelperActivator. * Introduced ITagHelperFactory for handling the setup of new tag helper instances. --- .../MvcRazorMvcCoreBuilderExtensions.cs | 1 + .../ITagHelperActivator.cs | 8 +- .../ITagHelperFactory.cs | 21 ++ .../Internal/DefaultTagHelperActivator.cs | 68 ++---- .../Internal/DefaultTagHelperFactory.cs | 92 ++++++++ .../RazorPage.cs | 33 +-- .../Internal/DefaultTagHelperActivatorTest.cs | 118 +---------- .../Internal/DefaultTagHelperFactoryTest.cs | 197 ++++++++++++++++++ .../RazorPageCreateTagHelperTest.cs | 5 +- 9 files changed, 350 insertions(+), 193 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperFactory.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperFactory.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperFactoryTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs index da4abb7dd4..abcc7c05da 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs @@ -130,6 +130,7 @@ namespace Microsoft.Extensions.DependencyInjection // Only want one ITagHelperActivator so it can cache Type activation information. Types won't conflict. services.TryAddSingleton(); + services.TryAddSingleton(); // Consumed by the Cache tag helper to cache results across the lifetime of the application. services.TryAddSingleton(); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperActivator.cs b/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperActivator.cs index d4007b1e8a..7757980390 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperActivator.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperActivator.cs @@ -7,16 +7,16 @@ using Microsoft.AspNetCore.Razor.TagHelpers; namespace Microsoft.AspNetCore.Mvc.Razor { /// - /// Provides methods to activate properties on a instance. + /// Provides methods to create a tag helper. /// public interface ITagHelperActivator { /// - /// When implemented in a type, activates an instantiated . + /// Creates an . /// /// The type. - /// The to activate. /// The for the executing view. - void Activate(TTagHelper tagHelper, ViewContext context) where TTagHelper : ITagHelper; + /// The tag helper. + TTagHelper Create(ViewContext context) where TTagHelper : ITagHelper; } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperFactory.cs b/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperFactory.cs new file mode 100644 index 0000000000..181b805655 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperFactory.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 +{ + /// + /// Provides methods to create and initialize tag helpers. + /// + public interface ITagHelperFactory + { + /// + /// Creates a new tag helper for the specified . + /// + /// for the executing view. + /// The tag helper. + TTagHelper CreateTagHelper(ViewContext context) where TTagHelper : ITagHelper; + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperActivator.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperActivator.cs index 8824fdae4e..c52a77a2eb 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperActivator.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperActivator.cs @@ -2,79 +2,45 @@ // 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.Collections.Generic; -using System.Reflection; +using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Mvc.Razor.Internal { - /// + /// + /// Default implementation of . + /// public class DefaultTagHelperActivator : ITagHelperActivator { - private readonly ConcurrentDictionary[]> _injectActions; - private readonly Func[]> _getPropertiesToActivate; + private readonly ITypeActivatorCache _typeActivatorCache; /// /// Instantiates a new instance. /// - public DefaultTagHelperActivator() + /// The . + public DefaultTagHelperActivator(ITypeActivatorCache typeActivatorCache) { - _injectActions = new ConcurrentDictionary[]>(); - _getPropertiesToActivate = type => - PropertyActivator.GetPropertiesToActivate( - type, - typeof(ViewContextAttribute), - CreateActivateInfo); + if (typeActivatorCache == null) + { + throw new ArgumentNullException(nameof(typeActivatorCache)); + } + + _typeActivatorCache = typeActivatorCache; } /// - public void Activate(TTagHelper tagHelper, ViewContext context) + public TTagHelper Create(ViewContext context) where TTagHelper : ITagHelper { - if (tagHelper == null) - { - throw new ArgumentNullException(nameof(tagHelper)); - } - if (context == null) { throw new ArgumentNullException(nameof(context)); } - var propertiesToActivate = _injectActions.GetOrAdd( - tagHelper.GetType(), - _getPropertiesToActivate); - - for (var i = 0; i < propertiesToActivate.Length; i++) - { - var activateInfo = propertiesToActivate[i]; - activateInfo.Activate(tagHelper, context); - } - - InitializeTagHelper(tagHelper, context); - } - - private static void InitializeTagHelper(TTagHelper tagHelper, ViewContext context) - where TTagHelper : ITagHelper - { - // Run any tag helper initializers in the container - var serviceProvider = context.HttpContext.RequestServices; - var initializers = serviceProvider.GetService>>(); - - foreach (var initializer in initializers) - { - initializer.Initialize(tagHelper, context); - } - } - - private static PropertyActivator CreateActivateInfo(PropertyInfo property) - { - return new PropertyActivator(property, viewContext => viewContext); + return _typeActivatorCache.CreateInstance( + context.HttpContext.RequestServices, + typeof(TTagHelper)); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperFactory.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperFactory.cs new file mode 100644 index 0000000000..6dd51965c3 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperFactory.cs @@ -0,0 +1,92 @@ +// 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.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + /// + /// Default implementation for . + /// + public class DefaultTagHelperFactory : ITagHelperFactory + { + private readonly ITagHelperActivator _activator; + private readonly ConcurrentDictionary[]> _injectActions; + private readonly Func[]> _getPropertiesToActivate; + private static readonly Func> _createActivateInfo = CreateActivateInfo; + + /// + /// Initializes a new instance. + /// + /// + /// The used to create tag helper instances. + /// + public DefaultTagHelperFactory(ITagHelperActivator activator) + { + if (activator == null) + { + throw new ArgumentNullException(nameof(activator)); + } + + _activator = activator; + _injectActions = new ConcurrentDictionary[]>(); + _getPropertiesToActivate = type => + PropertyActivator.GetPropertiesToActivate( + type, + typeof(ViewContextAttribute), + _createActivateInfo); + } + + /// + public TTagHelper CreateTagHelper(ViewContext context) + where TTagHelper : ITagHelper + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var tagHelper = _activator.Create(context); + + var propertiesToActivate = _injectActions.GetOrAdd( + tagHelper.GetType(), + _getPropertiesToActivate); + + for (var i = 0; i < propertiesToActivate.Length; i++) + { + var activateInfo = propertiesToActivate[i]; + activateInfo.Activate(tagHelper, context); + } + + InitializeTagHelper(tagHelper, context); + + return tagHelper; + } + + private static void InitializeTagHelper(TTagHelper tagHelper, ViewContext context) + where TTagHelper : ITagHelper + { + // Run any tag helper initializers in the container + var serviceProvider = context.HttpContext.RequestServices; + var initializers = serviceProvider.GetService>>(); + + foreach (var initializer in initializers) + { + initializer.Initialize(tagHelper, context); + } + } + + private static PropertyActivator CreateActivateInfo(PropertyInfo property) + { + return new PropertyActivator(property, viewContext => viewContext); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs index 79b86d291a..2795e37a44 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs @@ -32,8 +32,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor private readonly HashSet _renderedSections = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly Stack _tagHelperScopes = new Stack(); private IUrlHelper _urlHelper; - private ITagHelperActivator _tagHelperActivator; - private ITypeActivatorCache _typeActivatorCache; + private ITagHelperFactory _tagHelperFactory; private bool _renderedBody; private AttributeInfo _attributeInfo; private TagHelperAttributeInfo _tagHelperAttributeInfo; @@ -122,31 +121,17 @@ namespace Microsoft.AspNetCore.Mvc.Razor /// public abstract Task ExecuteAsync(); - private ITagHelperActivator TagHelperActivator + private ITagHelperFactory TagHelperFactory { get { - if (_tagHelperActivator == null) + if (_tagHelperFactory == null) { var services = ViewContext.HttpContext.RequestServices; - _tagHelperActivator = services.GetRequiredService(); + _tagHelperFactory = services.GetRequiredService(); } - return _tagHelperActivator; - } - } - - private ITypeActivatorCache TypeActivatorCache - { - get - { - if (_typeActivatorCache == null) - { - var services = ViewContext.HttpContext.RequestServices; - _typeActivatorCache = services.GetRequiredService(); - } - - return _typeActivatorCache; + return _tagHelperFactory; } } @@ -192,13 +177,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor /// public TTagHelper CreateTagHelper() where TTagHelper : ITagHelper { - var tagHelper = TypeActivatorCache.CreateInstance( - ViewContext.HttpContext.RequestServices, - typeof(TTagHelper)); - - TagHelperActivator.Activate(tagHelper, ViewContext); - - return tagHelper; + return TagHelperFactory.CreateTagHelper(ViewContext); } /// diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperActivatorTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperActivatorTest.cs index 36d8b1fe1f..5ccc549f04 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperActivatorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperActivatorTest.cs @@ -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; using System.IO; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -20,125 +19,24 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { public class DefaultTagHelperActivatorTest { - [Theory] - [InlineData("test", 100)] - [InlineData(null, -1)] - public void Activate_InitializesTagHelpers(string name, int number) + [Fact] + public void CreateTagHelper_InitializesTagHelpers() { // Arrange - var services = new ServiceCollection(); - var builder = new MvcCoreBuilder(services); - builder.InitializeTagHelper((h, vc) => + var httpContext = new DefaultHttpContext() { - h.Name = name; - h.Number = number; - h.ViewDataValue = vc.ViewData["TestData"]; - }); - var httpContext = MakeHttpContext(services.BuildServiceProvider()); + RequestServices = new ServiceCollection().BuildServiceProvider() + }; var viewContext = MakeViewContext(httpContext); var viewDataValue = new object(); viewContext.ViewData.Add("TestData", viewDataValue); - var activator = new DefaultTagHelperActivator(); - var helper = new TestTagHelper(); + var activator = new DefaultTagHelperActivator(new TypeActivatorCache()); // Act - activator.Activate(helper, viewContext); + var helper = activator.Create(viewContext); // Assert - Assert.Equal(name, helper.Name); - Assert.Equal(number, helper.Number); - Assert.Same(viewDataValue, helper.ViewDataValue); - } - - [Fact] - public void Activate_InitializesTagHelpersAfterActivatingProperties() - { - // Arrange - var services = new ServiceCollection(); - var builder = new MvcCoreBuilder(services); - builder.InitializeTagHelper((h, _) => h.ViewContext = MakeViewContext(MakeHttpContext())); - var httpContext = MakeHttpContext(services.BuildServiceProvider()); - var viewContext = MakeViewContext(httpContext); - var activator = new DefaultTagHelperActivator(); - var helper = new TestTagHelper(); - - // Act - activator.Activate(helper, viewContext); - - // Assert - Assert.NotSame(viewContext, helper.ViewContext); - } - - [Fact] - public void Activate_InitializesTagHelpersWithMultipleInitializers() - { - // Arrange - var services = new ServiceCollection(); - var builder = new MvcCoreBuilder(services); - builder.InitializeTagHelper((h, vc) => - { - h.Name = "Test 1"; - h.Number = 100; - }); - builder.InitializeTagHelper((h, vc) => - { - h.Name += ", Test 2"; - h.Number += 100; - }); - var httpContext = MakeHttpContext(services.BuildServiceProvider()); - var viewContext = MakeViewContext(httpContext); - var activator = new DefaultTagHelperActivator(); - var helper = new TestTagHelper(); - - // Act - activator.Activate(helper, viewContext); - - // Assert - Assert.Equal("Test 1, Test 2", helper.Name); - Assert.Equal(200, helper.Number); - } - - [Fact] - public void Activate_InitializesTagHelpersWithCorrectInitializers() - { - // Arrange - var services = new ServiceCollection(); - var builder = new MvcCoreBuilder(services); - builder.InitializeTagHelper((h, vc) => - { - h.Name = "Test 1"; - h.Number = 100; - }); - builder.InitializeTagHelper((h, vc) => - { - h.Name = "Test 2"; - h.Number = 102; - }); - var httpContext = MakeHttpContext(services.BuildServiceProvider()); - var viewContext = MakeViewContext(httpContext); - var activator = new DefaultTagHelperActivator(); - var testTagHelper = new TestTagHelper(); - var anotherTestTagHelper = new AnotherTestTagHelper(); - - // Act - activator.Activate(testTagHelper, viewContext); - activator.Activate(anotherTestTagHelper, viewContext); - - // Assert - Assert.Equal("Test 1", testTagHelper.Name); - Assert.Equal(100, testTagHelper.Number); - Assert.Equal("Test 2", anotherTestTagHelper.Name); - Assert.Equal(102, anotherTestTagHelper.Number); - } - - private static HttpContext MakeHttpContext(IServiceProvider services = null) - { - var httpContext = new DefaultHttpContext(); - if (services != null) - { - httpContext.RequestServices = services; - } - return httpContext; + Assert.NotNull(helper); } private static ViewContext MakeViewContext(HttpContext httpContext) diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperFactoryTest.cs new file mode 100644 index 0000000000..2d40c3b4a1 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperFactoryTest.cs @@ -0,0 +1,197 @@ +// 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.IO; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Internal; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public class DefaultTagHelperFactoryTest + { + [Theory] + [InlineData("test", 100)] + [InlineData(null, -1)] + public void CreateTagHelper_InitializesTagHelpers(string name, int number) + { + // Arrange + var services = new ServiceCollection(); + var builder = new MvcCoreBuilder(services); + builder.InitializeTagHelper((h, vc) => + { + h.Name = name; + h.Number = number; + h.ViewDataValue = vc.ViewData["TestData"]; + }); + var httpContext = MakeHttpContext(services.BuildServiceProvider()); + var viewContext = MakeViewContext(httpContext); + var viewDataValue = new object(); + viewContext.ViewData.Add("TestData", viewDataValue); + var factory = CreateFactory(); + + // Act + var helper = factory.CreateTagHelper(viewContext); + + // Assert + Assert.Equal(name, helper.Name); + Assert.Equal(number, helper.Number); + Assert.Same(viewDataValue, helper.ViewDataValue); + } + + [Fact] + public void CreateTagHelper_InitializesTagHelpersAfterActivatingProperties() + { + // Arrange + var services = new ServiceCollection(); + var builder = new MvcCoreBuilder(services); + builder.InitializeTagHelper((h, _) => h.ViewContext = MakeViewContext(MakeHttpContext())); + var httpContext = MakeHttpContext(services.BuildServiceProvider()); + var viewContext = MakeViewContext(httpContext); + var factory = CreateFactory(); + + // Act + var helper = factory.CreateTagHelper(viewContext); + + // Assert + Assert.NotSame(viewContext, helper.ViewContext); + } + + [Fact] + public void CreateTagHelper_InitializesTagHelpersWithMultipleInitializers() + { + // Arrange + var services = new ServiceCollection(); + var builder = new MvcCoreBuilder(services); + builder.InitializeTagHelper((h, vc) => + { + h.Name = "Test 1"; + h.Number = 100; + }); + builder.InitializeTagHelper((h, vc) => + { + h.Name += ", Test 2"; + h.Number += 100; + }); + var httpContext = MakeHttpContext(services.BuildServiceProvider()); + var viewContext = MakeViewContext(httpContext); + var factory = CreateFactory(); + + // Act + var helper = factory.CreateTagHelper(viewContext); + + // Assert + Assert.Equal("Test 1, Test 2", helper.Name); + Assert.Equal(200, helper.Number); + } + + [Fact] + public void CreateTagHelper_InitializesTagHelpersWithCorrectInitializers() + { + // Arrange + var services = new ServiceCollection(); + var builder = new MvcCoreBuilder(services); + builder.InitializeTagHelper((h, vc) => + { + h.Name = "Test 1"; + h.Number = 100; + }); + builder.InitializeTagHelper((h, vc) => + { + h.Name = "Test 2"; + h.Number = 102; + }); + var httpContext = MakeHttpContext(services.BuildServiceProvider()); + var viewContext = MakeViewContext(httpContext); + + var activator = new Mock(); + activator + .Setup(a => a.Create(It.IsAny())) + .Returns(new TestTagHelper()); + + activator + .Setup(a => a.Create(It.IsAny())) + .Returns(new AnotherTestTagHelper()); + + var factory = new DefaultTagHelperFactory(activator.Object); + + // Act + var testTagHelper = factory.CreateTagHelper(viewContext); + var anotherTestTagHelper = factory.CreateTagHelper(viewContext); + + // Assert + Assert.Equal("Test 1", testTagHelper.Name); + Assert.Equal(100, testTagHelper.Number); + Assert.Equal("Test 2", anotherTestTagHelper.Name); + Assert.Equal(102, anotherTestTagHelper.Number); + } + + private static HttpContext MakeHttpContext(IServiceProvider services = null) + { + var httpContext = new DefaultHttpContext(); + if (services != null) + { + httpContext.RequestServices = services; + } + return httpContext; + } + + private static DefaultTagHelperFactory CreateFactory() + { + var activator = new Mock(); + activator.Setup(a => a.Create(It.IsAny())).Returns(new TestTagHelper()); + return new DefaultTagHelperFactory(activator.Object); + } + + private static ViewContext MakeViewContext(HttpContext httpContext) + { + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); + var metadataProvider = new EmptyModelMetadataProvider(); + var viewData = new ViewDataDictionary(metadataProvider); + var viewContext = new ViewContext( + actionContext, + Mock.Of(), + viewData, + Mock.Of(), + TextWriter.Null, + new HtmlHelperOptions()); + + return viewContext; + } + + private class TestTagHelper : TagHelper + { + public string Name { get; set; } = "Initial Name"; + + public int Number { get; set; } = 1000; + + public object ViewDataValue { get; set; } = new object(); + + [ViewContext] + public ViewContext ViewContext { get; set; } + } + + private class AnotherTestTagHelper : TagHelper + { + public string Name { get; set; } = "Initial Name"; + + public int Number { get; set; } = 1000; + + public object ViewDataValue { get; set; } = new object(); + + [ViewContext] + public ViewContext ViewContext { get; set; } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageCreateTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageCreateTagHelperTest.cs index 170505a25f..12438a59a0 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageCreateTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageCreateTagHelperTest.cs @@ -69,11 +69,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor var activator = new RazorPageActivator(new EmptyModelMetadataProvider()); var serviceProvider = new Mock(); var typeActivator = new TypeActivatorCache(); + var tagHelperActivator = new DefaultTagHelperActivator(typeActivator); var myService = new MyService(); serviceProvider.Setup(mock => mock.GetService(typeof(MyService))) .Returns(myService); + serviceProvider.Setup(mock => mock.GetService(typeof(ITagHelperFactory))) + .Returns(new DefaultTagHelperFactory(tagHelperActivator)); serviceProvider.Setup(mock => mock.GetService(typeof(ITagHelperActivator))) - .Returns(new DefaultTagHelperActivator()); + .Returns(tagHelperActivator); serviceProvider.Setup(mock => mock.GetService(typeof(ITypeActivatorCache))) .Returns(typeActivator); serviceProvider.Setup(mock => mock.GetService(It.Is(serviceType =>