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

Commit 354400f

Browse files
committed
[Fixes #3752] Refactor and cleanup view components
* Moved instantiation of view components into DefaultViewComponentActivator * Introduced IViewComponentFactory to handling setup of new view component instances. * Added a release method on IViewComponentActivator for handling tear down of view component instances.
1 parent 2b0bea6 commit 354400f

12 files changed

Lines changed: 416 additions & 86 deletions

src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,10 @@ internal static void AddViewServices(IServiceCollection services)
118118
//
119119
// View Components
120120
//
121+
121122
// These do caching so they should stay singleton
122123
services.TryAddSingleton<IViewComponentSelector, DefaultViewComponentSelector>();
124+
services.TryAddSingleton<IViewComponentFactory, DefaultViewComponentFactory>();
123125
services.TryAddSingleton<IViewComponentActivator, DefaultViewComponentActivator>();
124126
services.TryAddSingleton<
125127
IViewComponentDescriptorCollectionProvider,

src/Microsoft.AspNetCore.Mvc.ViewFeatures/Properties/Resources.Designer.cs

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.AspNetCore.Mvc.ViewFeatures/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,4 +277,7 @@
277277
<data name="ViewComponent_AmbiguousMethods" xml:space="preserve">
278278
<value>View component '{0}' must have exactly one public method named '{1}' or '{2}'.</value>
279279
</data>
280+
<data name="ValueInterfaceAbstractOrOpenGenericTypesCannotBeActivated" xml:space="preserve">
281+
<value>The type '{0}' cannot be activated by '{1}' because it is either a value type, an interface, an abstract class or an open generic type.</value>
282+
</data>
280283
</root>

src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentActivator.cs

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Collections.Concurrent;
65
using System.Reflection;
7-
using Microsoft.Extensions.Internal;
6+
using Microsoft.AspNetCore.Mvc.Internal;
7+
using Microsoft.AspNetCore.Mvc.ViewFeatures;
88

99
namespace Microsoft.AspNetCore.Mvc.ViewComponents
1010
{
@@ -18,49 +18,71 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
1818
/// </remarks>
1919
public class DefaultViewComponentActivator : IViewComponentActivator
2020
{
21-
private readonly Func<Type, PropertyActivator<ViewComponentContext>[]> _getPropertiesToActivate;
22-
private readonly ConcurrentDictionary<Type, PropertyActivator<ViewComponentContext>[]> _injectActions;
21+
private readonly ITypeActivatorCache _typeActivatorCache;
2322

2423
/// <summary>
2524
/// Initializes a new instance of <see cref="DefaultViewComponentActivator"/> class.
2625
/// </summary>
27-
public DefaultViewComponentActivator()
26+
/// <param name="typeActivatorCache">
27+
/// The <see cref="ITypeActivatorCache"/> used to create new view component instances.
28+
/// </param>
29+
public DefaultViewComponentActivator(ITypeActivatorCache typeActivatorCache)
2830
{
29-
_injectActions = new ConcurrentDictionary<Type, PropertyActivator<ViewComponentContext>[]>();
30-
_getPropertiesToActivate = type =>
31-
PropertyActivator<ViewComponentContext>.GetPropertiesToActivate(
32-
type,
33-
typeof(ViewComponentContextAttribute),
34-
CreateActivateInfo);
31+
if (typeActivatorCache == null)
32+
{
33+
throw new ArgumentNullException(nameof(typeActivatorCache));
34+
}
35+
36+
_typeActivatorCache = typeActivatorCache;
3537
}
3638

3739
/// <inheritdoc />
38-
public virtual void Activate(object viewComponent, ViewComponentContext context)
40+
public virtual object Create(ViewComponentContext context)
3941
{
40-
if (viewComponent == null)
41-
{
42-
throw new ArgumentNullException(nameof(viewComponent));
43-
}
44-
4542
if (context == null)
4643
{
4744
throw new ArgumentNullException(nameof(context));
4845
}
4946

50-
var propertiesToActivate = _injectActions.GetOrAdd(
51-
viewComponent.GetType(),
52-
_getPropertiesToActivate);
47+
var componentType = context.ViewComponentDescriptor.Type.GetTypeInfo();
5348

54-
for (var i = 0; i < propertiesToActivate.Length; i++)
49+
if (componentType.IsValueType ||
50+
componentType.IsInterface ||
51+
componentType.IsAbstract ||
52+
(componentType.IsGenericType && componentType.IsGenericTypeDefinition))
5553
{
56-
var activateInfo = propertiesToActivate[i];
57-
activateInfo.Activate(viewComponent, context);
54+
var message = Resources.FormatValueInterfaceAbstractOrOpenGenericTypesCannotBeActivated(
55+
componentType.FullName,
56+
GetType().FullName);
57+
58+
throw new InvalidOperationException(message);
5859
}
60+
61+
var viewComponent = _typeActivatorCache.CreateInstance<object>(
62+
context.ViewContext.HttpContext.RequestServices,
63+
context.ViewComponentDescriptor.Type);
64+
65+
return viewComponent;
5966
}
6067

61-
private PropertyActivator<ViewComponentContext> CreateActivateInfo(PropertyInfo property)
68+
/// <inheritdoc />
69+
public virtual void Release(ViewComponentContext context, object viewComponent)
6270
{
63-
return new PropertyActivator<ViewComponentContext>(property, context => context);
71+
if (context == null)
72+
{
73+
throw new InvalidOperationException(nameof(context));
74+
}
75+
76+
if (viewComponent == null)
77+
{
78+
throw new InvalidOperationException(nameof(viewComponent));
79+
}
80+
81+
var disposable = viewComponent as IDisposable;
82+
if (disposable != null)
83+
{
84+
disposable.Dispose();
85+
}
6486
}
6587
}
6688
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright (c) .NET Foundation. 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.Extensions.Internal;
8+
9+
namespace Microsoft.AspNetCore.Mvc.ViewComponents
10+
{
11+
/// <summary>
12+
/// Default implementation for <see cref="IViewComponentFactory"/>.
13+
/// </summary>
14+
public class DefaultViewComponentFactory : IViewComponentFactory
15+
{
16+
private readonly IViewComponentActivator _activator;
17+
private readonly Func<Type, PropertyActivator<ViewComponentContext>[]> _getPropertiesToActivate;
18+
private readonly ConcurrentDictionary<Type, PropertyActivator<ViewComponentContext>[]> _injectActions;
19+
20+
/// <summary>
21+
/// Creates a new instance of <see cref="DefaultViewComponentFactory"/>
22+
/// </summary>
23+
/// <param name="activator">
24+
/// The <see cref="IViewComponentActivator"/> used to create new view component instances.
25+
/// </param>
26+
public DefaultViewComponentFactory(IViewComponentActivator activator)
27+
{
28+
if (activator == null)
29+
{
30+
throw new ArgumentNullException(nameof(activator));
31+
}
32+
33+
_activator = activator;
34+
35+
_getPropertiesToActivate = type => PropertyActivator<ViewComponentContext>.GetPropertiesToActivate(
36+
type,
37+
typeof(ViewComponentContextAttribute),
38+
CreateActivateInfo);
39+
40+
_injectActions = new ConcurrentDictionary<Type, PropertyActivator<ViewComponentContext>[]>();
41+
}
42+
43+
/// <inheritdoc />
44+
public object CreateViewComponent(ViewComponentContext context)
45+
{
46+
if (context == null)
47+
{
48+
throw new ArgumentNullException(nameof(context));
49+
}
50+
51+
var component = _activator.Create(context);
52+
53+
InjectProperties(context, component);
54+
55+
return component;
56+
}
57+
58+
private void InjectProperties(ViewComponentContext context, object viewComponent)
59+
{
60+
var propertiesToActivate = _injectActions.GetOrAdd(
61+
viewComponent.GetType(),
62+
type =>
63+
PropertyActivator<ViewComponentContext>.GetPropertiesToActivate(
64+
type,
65+
typeof(ViewComponentContextAttribute),
66+
CreateActivateInfo));
67+
68+
for (var i = 0; i < propertiesToActivate.Length; i++)
69+
{
70+
var activateInfo = propertiesToActivate[i];
71+
activateInfo.Activate(viewComponent, context);
72+
}
73+
}
74+
75+
private static PropertyActivator<ViewComponentContext> CreateActivateInfo(PropertyInfo property)
76+
{
77+
return new PropertyActivator<ViewComponentContext>(property, context => context);
78+
}
79+
80+
/// <inheritdoc />
81+
public void ReleaseViewComponent(ViewComponentContext context, object component)
82+
{
83+
if (context == null)
84+
{
85+
throw new ArgumentNullException(nameof(context));
86+
}
87+
88+
if (component == null)
89+
{
90+
throw new ArgumentNullException(nameof(component));
91+
}
92+
93+
_activator.Release(context, component);
94+
}
95+
}
96+
}

src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,32 +19,25 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
1919
/// </summary>
2020
public class DefaultViewComponentInvoker : IViewComponentInvoker
2121
{
22-
private readonly ITypeActivatorCache _typeActivatorCache;
23-
private readonly IViewComponentActivator _viewComponentActivator;
22+
private readonly IViewComponentFactory _viewComponentFactory;
2423
private readonly DiagnosticSource _diagnosticSource;
2524
private readonly ILogger _logger;
2625

2726
/// <summary>
2827
/// Initializes a new instance of <see cref="DefaultViewComponentInvoker"/>.
2928
/// </summary>
3029
/// <param name="typeActivatorCache">Caches factories for instantiating view component instances.</param>
31-
/// <param name="viewComponentActivator">The <see cref="IViewComponentActivator"/>.</param>
30+
/// <param name="viewComponentFactory">The <see cref="IViewComponentFactory"/>.</param>
3231
/// <param name="diagnosticSource">The <see cref="DiagnosticSource"/>.</param>
3332
/// <param name="logger">The <see cref="ILogger"/>.</param>
3433
public DefaultViewComponentInvoker(
35-
ITypeActivatorCache typeActivatorCache,
36-
IViewComponentActivator viewComponentActivator,
34+
IViewComponentFactory viewComponentFactory,
3735
DiagnosticSource diagnosticSource,
3836
ILogger logger)
3937
{
40-
if (typeActivatorCache == null)
38+
if (viewComponentFactory == null)
4139
{
42-
throw new ArgumentNullException(nameof(typeActivatorCache));
43-
}
44-
45-
if (viewComponentActivator == null)
46-
{
47-
throw new ArgumentNullException(nameof(viewComponentActivator));
40+
throw new ArgumentNullException(nameof(viewComponentFactory));
4841
}
4942

5043
if (diagnosticSource == null)
@@ -57,8 +50,7 @@ public DefaultViewComponentInvoker(
5750
throw new ArgumentNullException(nameof(logger));
5851
}
5952

60-
_typeActivatorCache = typeActivatorCache;
61-
_viewComponentActivator = viewComponentActivator;
53+
_viewComponentFactory = viewComponentFactory;
6254
_diagnosticSource = diagnosticSource;
6355
_logger = logger;
6456
}
@@ -95,24 +87,9 @@ public async Task InvokeAsync(ViewComponentContext context)
9587
await result.ExecuteAsync(context);
9688
}
9789

98-
private object CreateComponent(ViewComponentContext context)
99-
{
100-
if (context == null)
101-
{
102-
throw new ArgumentNullException(nameof(context));
103-
}
104-
105-
var services = context.ViewContext.HttpContext.RequestServices;
106-
var component = _typeActivatorCache.CreateInstance<object>(
107-
services,
108-
context.ViewComponentDescriptor.Type);
109-
_viewComponentActivator.Activate(component, context);
110-
return component;
111-
}
112-
11390
private async Task<IViewComponentResult> InvokeAsyncCore(ViewComponentContext context)
11491
{
115-
var component = CreateComponent(context);
92+
var component = _viewComponentFactory.CreateViewComponent(context);
11693

11794
using (_logger.ViewComponentScope(context))
11895
{
@@ -129,13 +106,15 @@ private async Task<IViewComponentResult> InvokeAsyncCore(ViewComponentContext co
129106
_logger.ViewComponentExecuted(context, startTimestamp, viewComponentResult);
130107
_diagnosticSource.AfterViewComponent(context, viewComponentResult, component);
131108

109+
_viewComponentFactory.ReleaseViewComponent(context, component);
110+
132111
return viewComponentResult;
133112
}
134113
}
135114

136115
private IViewComponentResult InvokeSyncCore(ViewComponentContext context)
137116
{
138-
var component = CreateComponent(context);
117+
var component = _viewComponentFactory.CreateViewComponent(context);
139118

140119
using (_logger.ViewComponentScope(context))
141120
{
@@ -155,6 +134,8 @@ private IViewComponentResult InvokeSyncCore(ViewComponentContext context)
155134
}
156135
catch (TargetInvocationException ex)
157136
{
137+
_viewComponentFactory.ReleaseViewComponent(context, component);
138+
158139
// Preserve callstack of any user-thrown exceptions.
159140
var exceptionInfo = ExceptionDispatchInfo.Capture(ex.InnerException);
160141
exceptionInfo.Throw();
@@ -165,6 +146,8 @@ private IViewComponentResult InvokeSyncCore(ViewComponentContext context)
165146
_logger.ViewComponentExecuted(context, startTimestamp, viewComponentResult);
166147
_diagnosticSource.AfterViewComponent(context, viewComponentResult, component);
167148

149+
_viewComponentFactory.ReleaseViewComponent(context, component);
150+
168151
return viewComponentResult;
169152
}
170153
}

0 commit comments

Comments
 (0)