diff --git a/src/Microsoft.AspNet.Mvc.Razor/DefaultTagHelperActivator.cs b/src/Microsoft.AspNet.Mvc.Razor/DefaultTagHelperActivator.cs new file mode 100644 index 0000000000..6667e86390 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Razor/DefaultTagHelperActivator.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Mvc.Rendering; +using Microsoft.AspNet.Razor.Runtime.TagHelpers; + +namespace Microsoft.AspNet.Mvc.Razor +{ + /// + public class DefaultTagHelperActivator : ITagHelperActivator + { + private readonly ConcurrentDictionary[]> _injectActions; + private readonly Func[]> _getPropertiesToActivate; + + /// + /// Instantiates a new instance. + /// + public DefaultTagHelperActivator() + { + _injectActions = new ConcurrentDictionary[]>(); + _getPropertiesToActivate = type => + PropertyActivator.GetPropertiesToActivate( + type, typeof(ActivateAttribute), CreateActivateInfo); + } + + /// + public void Activate([NotNull] ITagHelper tagHelper, [NotNull] ViewContext context) + { + var propertiesToActivate = _injectActions.GetOrAdd(tagHelper.GetType(), + _getPropertiesToActivate); + + for (var i = 0; i < propertiesToActivate.Length; i++) + { + var activateInfo = propertiesToActivate[i]; + activateInfo.Activate(tagHelper, context); + } + } + + private static PropertyActivator CreateActivateInfo(PropertyInfo property) + { + Func valueAccessor; + + if (property.PropertyType == typeof(ViewContext)) + { + valueAccessor = viewContext => viewContext; + } + else + { + valueAccessor = (viewContext) => + { + var serviceProvider = viewContext.HttpContext.RequestServices; + var service = serviceProvider.GetService(property.PropertyType); + + var contextable = service as ICanHasViewContext; + contextable?.Contextualize(viewContext); + + return service; + }; + } + + return new PropertyActivator(property, valueAccessor); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Razor/ITagHelperActivator.cs b/src/Microsoft.AspNet.Mvc.Razor/ITagHelperActivator.cs new file mode 100644 index 0000000000..892078f23e --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Razor/ITagHelperActivator.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Razor.Runtime.TagHelpers; + +namespace Microsoft.AspNet.Mvc.Razor +{ + /// + /// Provides methods to activate properties on a instance. + /// + public interface ITagHelperActivator + { + /// + /// When implemented in a type, activates an instantiated . + /// + /// The to activate. + /// The for the executing view. + void Activate(ITagHelper tagHelper, ViewContext context); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs index afab0e0ed5..cd15dc46af 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs @@ -26,6 +26,7 @@ namespace Microsoft.AspNet.Mvc.Razor private TextWriter _originalWriter; private IUrlHelper _urlHelper; private ITypeActivator _typeActivator; + private ITagHelperActivator _tagHelperActivator; private bool _renderedBody; public RazorPage() @@ -126,6 +127,19 @@ namespace Microsoft.AspNet.Mvc.Razor } } + private ITagHelperActivator TagHelperActivator + { + get + { + if (_tagHelperActivator == null) + { + _tagHelperActivator = ViewContext.HttpContext.RequestServices.GetService(); + } + + return _tagHelperActivator; + } + } + /// /// Creates and activates a . /// @@ -139,8 +153,7 @@ namespace Microsoft.AspNet.Mvc.Razor { var tagHelper = TypeActivator.CreateInstance(ViewContext.HttpContext.RequestServices); - var hasViewContext = tagHelper as ICanHasViewContext; - hasViewContext?.Contextualize(ViewContext); + TagHelperActivator.Activate(tagHelper, ViewContext); return tagHelper; } diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs index 9295ce4fc5..16f81c8cb7 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServices.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs @@ -33,6 +33,9 @@ namespace Microsoft.AspNet.Mvc yield return describe.Transient, MvcOptionsSetup>(); + // Only want one ITagHelperActivator so it can cache Type activation information. Types won't conflict. + yield return describe.Singleton(); + yield return describe.Transient(); yield return describe.Singleton(); diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageCreateTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageCreateTagHelperTest.cs index e067e62f97..d99b619f8b 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageCreateTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageCreateTagHelperTest.cs @@ -31,20 +31,47 @@ namespace Microsoft.AspNet.Mvc.Razor } [Fact] - public void CreateTagHelper_ActivatesProvidedTagHelperType() + public void CreateTagHelper_ActivatesProvidedTagHelperType_Constructor() { // Arrange var instance = CreateTestRazorPage(); // Act - var tagHelper = instance.CreateTagHelper(); + var tagHelper = instance.CreateTagHelper(); // Assert Assert.NotNull(tagHelper.PassedInService); } [Fact] - public void CreateTagHelper_ContextualizesProvidedTagHelperType() + public void CreateTagHelper_ActivatesProvidedTagHelperType_Property() + { + // Arrange + var instance = CreateTestRazorPage(); + + // Act + var tagHelper = instance.CreateTagHelper(); + + // Assert + Assert.NotNull(tagHelper.ActivatedService); + } + + [Fact] + public void CreateTagHelper_ActivatesProvidedTagHelperType_PropertyAndConstructor() + { + // Arrange + var instance = CreateTestRazorPage(); + + // Act + var tagHelper = instance.CreateTagHelper(); + + // Assert + Assert.NotNull(tagHelper.ActivatedService); + Assert.NotNull(tagHelper.PassedInService); + } + + [Fact] + public void CreateTagHelper_ProvidesTagHelperTypeWithViewContext() { // Arrange var instance = CreateTestRazorPage(); @@ -57,7 +84,7 @@ namespace Microsoft.AspNet.Mvc.Razor } [Fact] - public void CreateTagHelper_ContextualizesAndActivatesProvidedTagHelperType() + public void CreateTagHelper_ProvidesTagHelperTypeWithViewContextAndActivates() { // Arrange var instance = CreateTestRazorPage(); @@ -80,6 +107,8 @@ namespace Microsoft.AspNet.Mvc.Razor .Returns(myService); serviceProvider.Setup(mock => mock.GetService(typeof(ITypeActivator))) .Returns(typeActivator); + serviceProvider.Setup(mock => mock.GetService(typeof(ITagHelperActivator))) + .Returns(new DefaultTagHelperActivator()); var httpContext = new Mock(); httpContext.SetupGet(c => c.RequestServices) .Returns(serviceProvider.Object); @@ -109,26 +138,41 @@ namespace Microsoft.AspNet.Mvc.Razor { } - private class ServiceTagHelper : TagHelper + private class ConstructorServiceTagHelper : TagHelper { public MyService PassedInService { get; set; } - public ServiceTagHelper(MyService service) + public ConstructorServiceTagHelper(MyService service) { PassedInService = service; } } - private class ViewContextTagHelper : TagHelper, ICanHasViewContext + private class ActivateAttributeServiceTagHelper : TagHelper { - public ViewContext ViewContext { get; set; } + [Activate] + public MyService ActivatedService { get; set; } + } - public void Contextualize([NotNull]ViewContext viewContext) + private class AttributeConstructorServiceTagHelper : TagHelper + { + [Activate] + public MyService ActivatedService { get; set; } + + public MyService PassedInService { get; set; } + + public AttributeConstructorServiceTagHelper(MyService service) { - ViewContext = viewContext; + PassedInService = service; } } + private class ViewContextTagHelper : TagHelper + { + [Activate] + public ViewContext ViewContext { get; set; } + } + private class ViewContextServiceTagHelper : ViewContextTagHelper { public MyService PassedInService { get; set; }