Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit 3dff1ca

Browse files
author
NTaylorMullen
committed
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
1 parent 05c35dd commit 3dff1ca

File tree

5 files changed

+159
-12
lines changed

5 files changed

+159
-12
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Concurrent;
6+
using System.Reflection;
7+
using Microsoft.AspNet.Mvc.Rendering;
8+
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
9+
10+
namespace Microsoft.AspNet.Mvc.Razor
11+
{
12+
/// <inheritdoc />
13+
public class DefaultTagHelperActivator : ITagHelperActivator
14+
{
15+
private readonly ConcurrentDictionary<Type, PropertyActivator<ViewContext>[]> _injectActions;
16+
private readonly Func<Type, PropertyActivator<ViewContext>[]> _getPropertiesToActivate;
17+
18+
/// <summary>
19+
/// Instantiates a new <see cref="DefaultTagHelperActivator"/> instance.
20+
/// </summary>
21+
public DefaultTagHelperActivator()
22+
{
23+
_injectActions = new ConcurrentDictionary<Type, PropertyActivator<ViewContext>[]>();
24+
_getPropertiesToActivate = type =>
25+
PropertyActivator<ViewContext>.GetPropertiesToActivate(
26+
type, typeof(ActivateAttribute), CreateActivateInfo);
27+
}
28+
29+
/// <inheritdoc />
30+
public void Activate([NotNull] ITagHelper tagHelper, [NotNull] ViewContext context)
31+
{
32+
var propertiesToActivate = _injectActions.GetOrAdd(tagHelper.GetType(),
33+
_getPropertiesToActivate);
34+
35+
for (var i = 0; i < propertiesToActivate.Length; i++)
36+
{
37+
var activateInfo = propertiesToActivate[i];
38+
activateInfo.Activate(tagHelper, context);
39+
}
40+
}
41+
42+
private static PropertyActivator<ViewContext> CreateActivateInfo(PropertyInfo property)
43+
{
44+
Func<ViewContext, object> valueAccessor;
45+
46+
if (property.PropertyType == typeof(ViewContext))
47+
{
48+
valueAccessor = viewContext => viewContext;
49+
}
50+
else
51+
{
52+
valueAccessor = (viewContext) =>
53+
{
54+
var serviceProvider = viewContext.HttpContext.RequestServices;
55+
var service = serviceProvider.GetService(property.PropertyType);
56+
57+
var contextable = service as ICanHasViewContext;
58+
contextable?.Contextualize(viewContext);
59+
60+
return service;
61+
};
62+
}
63+
64+
return new PropertyActivator<ViewContext>(property, valueAccessor);
65+
}
66+
}
67+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
5+
6+
namespace Microsoft.AspNet.Mvc.Razor
7+
{
8+
/// <summary>
9+
/// Provides methods to activate properties on a <see cref="ITagHelper"/> instance.
10+
/// </summary>
11+
public interface ITagHelperActivator
12+
{
13+
/// <summary>
14+
/// When implemented in a type, activates an instantiated <see cref="ITagHelper"/>.
15+
/// </summary>
16+
/// <param name="tagHelper">The <see cref="ITagHelper"/> to activate.</param>
17+
/// <param name="context">The <see cref="ViewContext"/> for the executing view.</param>
18+
void Activate(ITagHelper tagHelper, ViewContext context);
19+
}
20+
}

src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public abstract class RazorPage : IRazorPage
2626
private TextWriter _originalWriter;
2727
private IUrlHelper _urlHelper;
2828
private ITypeActivator _typeActivator;
29+
private ITagHelperActivator _tagHelperActivator;
2930
private bool _renderedBody;
3031

3132
public RazorPage()
@@ -126,6 +127,19 @@ private ITypeActivator TypeActivator
126127
}
127128
}
128129

130+
private ITagHelperActivator TagHelperActivator
131+
{
132+
get
133+
{
134+
if (_tagHelperActivator == null)
135+
{
136+
_tagHelperActivator = ViewContext.HttpContext.RequestServices.GetService<ITagHelperActivator>();
137+
}
138+
139+
return _tagHelperActivator;
140+
}
141+
}
142+
129143
/// <summary>
130144
/// Creates and activates a <see cref="ITagHelper"/>.
131145
/// </summary>
@@ -139,8 +153,7 @@ public TTagHelper CreateTagHelper<TTagHelper>() where TTagHelper : ITagHelper
139153
{
140154
var tagHelper = TypeActivator.CreateInstance<TTagHelper>(ViewContext.HttpContext.RequestServices);
141155

142-
var hasViewContext = tagHelper as ICanHasViewContext;
143-
hasViewContext?.Contextualize(ViewContext);
156+
TagHelperActivator.Activate(tagHelper, ViewContext);
144157

145158
return tagHelper;
146159
}

src/Microsoft.AspNet.Mvc/MvcServices.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ public static IEnumerable<IServiceDescriptor> GetDefaultServices(IConfiguration
3333

3434
yield return describe.Transient<IOptionsAction<MvcOptions>, MvcOptionsSetup>();
3535

36+
// Only want one ITagHelperActivator so it can cache Type activation information. Types won't conflict.
37+
yield return describe.Singleton<ITagHelperActivator, DefaultTagHelperActivator>();
38+
3639
yield return describe.Transient<IControllerFactory, DefaultControllerFactory>();
3740
yield return describe.Singleton<IControllerActivator, DefaultControllerActivator>();
3841

test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageCreateTagHelperTest.cs

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,47 @@ public void CreateTagHelper_CreatesProvidedTagHelperType()
3131
}
3232

3333
[Fact]
34-
public void CreateTagHelper_ActivatesProvidedTagHelperType()
34+
public void CreateTagHelper_ActivatesProvidedTagHelperType_Constructor()
3535
{
3636
// Arrange
3737
var instance = CreateTestRazorPage();
3838

3939
// Act
40-
var tagHelper = instance.CreateTagHelper<ServiceTagHelper>();
40+
var tagHelper = instance.CreateTagHelper<ConstructorServiceTagHelper>();
4141

4242
// Assert
4343
Assert.NotNull(tagHelper.PassedInService);
4444
}
4545

4646
[Fact]
47-
public void CreateTagHelper_ContextualizesProvidedTagHelperType()
47+
public void CreateTagHelper_ActivatesProvidedTagHelperType_Property()
48+
{
49+
// Arrange
50+
var instance = CreateTestRazorPage();
51+
52+
// Act
53+
var tagHelper = instance.CreateTagHelper<ActivateAttributeServiceTagHelper>();
54+
55+
// Assert
56+
Assert.NotNull(tagHelper.ActivatedService);
57+
}
58+
59+
[Fact]
60+
public void CreateTagHelper_ActivatesProvidedTagHelperType_PropertyAndConstructor()
61+
{
62+
// Arrange
63+
var instance = CreateTestRazorPage();
64+
65+
// Act
66+
var tagHelper = instance.CreateTagHelper<AttributeConstructorServiceTagHelper>();
67+
68+
// Assert
69+
Assert.NotNull(tagHelper.ActivatedService);
70+
Assert.NotNull(tagHelper.PassedInService);
71+
}
72+
73+
[Fact]
74+
public void CreateTagHelper_ProvidesTagHelperTypeWithViewContext()
4875
{
4976
// Arrange
5077
var instance = CreateTestRazorPage();
@@ -57,7 +84,7 @@ public void CreateTagHelper_ContextualizesProvidedTagHelperType()
5784
}
5885

5986
[Fact]
60-
public void CreateTagHelper_ContextualizesAndActivatesProvidedTagHelperType()
87+
public void CreateTagHelper_ProvidesTagHelperTypeWithViewContextAndActivates()
6188
{
6289
// Arrange
6390
var instance = CreateTestRazorPage();
@@ -80,6 +107,8 @@ private static TestRazorPage CreateTestRazorPage()
80107
.Returns(myService);
81108
serviceProvider.Setup(mock => mock.GetService(typeof(ITypeActivator)))
82109
.Returns(typeActivator);
110+
serviceProvider.Setup(mock => mock.GetService(typeof(ITagHelperActivator)))
111+
.Returns(new DefaultTagHelperActivator());
83112
var httpContext = new Mock<HttpContext>();
84113
httpContext.SetupGet(c => c.RequestServices)
85114
.Returns(serviceProvider.Object);
@@ -109,26 +138,41 @@ private class NoServiceTagHelper : TagHelper
109138
{
110139
}
111140

112-
private class ServiceTagHelper : TagHelper
141+
private class ConstructorServiceTagHelper : TagHelper
113142
{
114143
public MyService PassedInService { get; set; }
115144

116-
public ServiceTagHelper(MyService service)
145+
public ConstructorServiceTagHelper(MyService service)
117146
{
118147
PassedInService = service;
119148
}
120149
}
121150

122-
private class ViewContextTagHelper : TagHelper, ICanHasViewContext
151+
private class ActivateAttributeServiceTagHelper : TagHelper
123152
{
124-
public ViewContext ViewContext { get; set; }
153+
[Activate]
154+
public MyService ActivatedService { get; set; }
155+
}
156+
157+
private class AttributeConstructorServiceTagHelper : TagHelper
158+
{
159+
[Activate]
160+
public MyService ActivatedService { get; set; }
161+
162+
public MyService PassedInService { get; set; }
125163

126-
public void Contextualize([NotNull]ViewContext viewContext)
164+
public AttributeConstructorServiceTagHelper(MyService service)
127165
{
128-
ViewContext = viewContext;
166+
PassedInService = service;
129167
}
130168
}
131169

170+
private class ViewContextTagHelper : TagHelper
171+
{
172+
[Activate]
173+
public ViewContext ViewContext { get; set; }
174+
}
175+
132176
private class ViewContextServiceTagHelper : ViewContextTagHelper
133177
{
134178
public MyService PassedInService { get; set; }

0 commit comments

Comments
 (0)