Add ITagHelperActivator.

- The TagHelperActivator enables dependency injection via properties and allows access to the ViewContext.
- This replaces the ICanHasViewContext mechanism that we had in place before.
- Added tests and fixed up existing to work with new format for providing ViewContext.

#1258
This commit is contained in:
NTaylorMullen 2014-10-07 00:30:27 -07:00 committed by N. Taylor Mullen
parent 05c35dd3ba
commit 3dff1ca410
5 changed files with 159 additions and 12 deletions

View File

@ -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
{
/// <inheritdoc />
public class DefaultTagHelperActivator : ITagHelperActivator
{
private readonly ConcurrentDictionary<Type, PropertyActivator<ViewContext>[]> _injectActions;
private readonly Func<Type, PropertyActivator<ViewContext>[]> _getPropertiesToActivate;
/// <summary>
/// Instantiates a new <see cref="DefaultTagHelperActivator"/> instance.
/// </summary>
public DefaultTagHelperActivator()
{
_injectActions = new ConcurrentDictionary<Type, PropertyActivator<ViewContext>[]>();
_getPropertiesToActivate = type =>
PropertyActivator<ViewContext>.GetPropertiesToActivate(
type, typeof(ActivateAttribute), CreateActivateInfo);
}
/// <inheritdoc />
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<ViewContext> CreateActivateInfo(PropertyInfo property)
{
Func<ViewContext, object> 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<ViewContext>(property, valueAccessor);
}
}
}

View File

@ -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
{
/// <summary>
/// Provides methods to activate properties on a <see cref="ITagHelper"/> instance.
/// </summary>
public interface ITagHelperActivator
{
/// <summary>
/// When implemented in a type, activates an instantiated <see cref="ITagHelper"/>.
/// </summary>
/// <param name="tagHelper">The <see cref="ITagHelper"/> to activate.</param>
/// <param name="context">The <see cref="ViewContext"/> for the executing view.</param>
void Activate(ITagHelper tagHelper, ViewContext context);
}
}

View File

@ -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<ITagHelperActivator>();
}
return _tagHelperActivator;
}
}
/// <summary>
/// Creates and activates a <see cref="ITagHelper"/>.
/// </summary>
@ -139,8 +153,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{
var tagHelper = TypeActivator.CreateInstance<TTagHelper>(ViewContext.HttpContext.RequestServices);
var hasViewContext = tagHelper as ICanHasViewContext;
hasViewContext?.Contextualize(ViewContext);
TagHelperActivator.Activate(tagHelper, ViewContext);
return tagHelper;
}

View File

@ -33,6 +33,9 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Transient<IOptionsAction<MvcOptions>, MvcOptionsSetup>();
// Only want one ITagHelperActivator so it can cache Type activation information. Types won't conflict.
yield return describe.Singleton<ITagHelperActivator, DefaultTagHelperActivator>();
yield return describe.Transient<IControllerFactory, DefaultControllerFactory>();
yield return describe.Singleton<IControllerActivator, DefaultControllerActivator>();

View File

@ -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<ServiceTagHelper>();
var tagHelper = instance.CreateTagHelper<ConstructorServiceTagHelper>();
// Assert
Assert.NotNull(tagHelper.PassedInService);
}
[Fact]
public void CreateTagHelper_ContextualizesProvidedTagHelperType()
public void CreateTagHelper_ActivatesProvidedTagHelperType_Property()
{
// Arrange
var instance = CreateTestRazorPage();
// Act
var tagHelper = instance.CreateTagHelper<ActivateAttributeServiceTagHelper>();
// Assert
Assert.NotNull(tagHelper.ActivatedService);
}
[Fact]
public void CreateTagHelper_ActivatesProvidedTagHelperType_PropertyAndConstructor()
{
// Arrange
var instance = CreateTestRazorPage();
// Act
var tagHelper = instance.CreateTagHelper<AttributeConstructorServiceTagHelper>();
// 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>();
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; }