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

Commit

Permalink
[Fixes #3752] Refactor and cleanup view components
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
javiercn committed Jan 26, 2016
1 parent 2b0bea6 commit 354400f
Show file tree
Hide file tree
Showing 12 changed files with 416 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,10 @@ internal static void AddViewServices(IServiceCollection services)
//
// View Components
//

// These do caching so they should stay singleton
services.TryAddSingleton<IViewComponentSelector, DefaultViewComponentSelector>();
services.TryAddSingleton<IViewComponentFactory, DefaultViewComponentFactory>();
services.TryAddSingleton<IViewComponentActivator, DefaultViewComponentActivator>();
services.TryAddSingleton<
IViewComponentDescriptorCollectionProvider,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Microsoft.AspNetCore.Mvc.ViewFeatures/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -277,4 +277,7 @@
<data name="ViewComponent_AmbiguousMethods" xml:space="preserve">
<value>View component '{0}' must have exactly one public method named '{1}' or '{2}'.</value>
</data>
<data name="ValueInterfaceAbstractOrOpenGenericTypesCannotBeActivated" xml:space="preserve">
<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>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
// 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.Extensions.Internal;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ViewFeatures;

namespace Microsoft.AspNetCore.Mvc.ViewComponents
{
Expand All @@ -18,49 +18,71 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
/// </remarks>
public class DefaultViewComponentActivator : IViewComponentActivator
{
private readonly Func<Type, PropertyActivator<ViewComponentContext>[]> _getPropertiesToActivate;
private readonly ConcurrentDictionary<Type, PropertyActivator<ViewComponentContext>[]> _injectActions;
private readonly ITypeActivatorCache _typeActivatorCache;

/// <summary>
/// Initializes a new instance of <see cref="DefaultViewComponentActivator"/> class.
/// </summary>
public DefaultViewComponentActivator()
/// <param name="typeActivatorCache">
/// The <see cref="ITypeActivatorCache"/> used to create new view component instances.
/// </param>
public DefaultViewComponentActivator(ITypeActivatorCache typeActivatorCache)
{
_injectActions = new ConcurrentDictionary<Type, PropertyActivator<ViewComponentContext>[]>();
_getPropertiesToActivate = type =>
PropertyActivator<ViewComponentContext>.GetPropertiesToActivate(
type,
typeof(ViewComponentContextAttribute),
CreateActivateInfo);
if (typeActivatorCache == null)
{
throw new ArgumentNullException(nameof(typeActivatorCache));
}

_typeActivatorCache = typeActivatorCache;
}

/// <inheritdoc />
public virtual void Activate(object viewComponent, ViewComponentContext context)
public virtual object Create(ViewComponentContext context)
{
if (viewComponent == null)
{
throw new ArgumentNullException(nameof(viewComponent));
}

if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

var propertiesToActivate = _injectActions.GetOrAdd(
viewComponent.GetType(),
_getPropertiesToActivate);
var componentType = context.ViewComponentDescriptor.Type.GetTypeInfo();

for (var i = 0; i < propertiesToActivate.Length; i++)
if (componentType.IsValueType ||
componentType.IsInterface ||
componentType.IsAbstract ||
(componentType.IsGenericType && componentType.IsGenericTypeDefinition))
{
var activateInfo = propertiesToActivate[i];
activateInfo.Activate(viewComponent, context);
var message = Resources.FormatValueInterfaceAbstractOrOpenGenericTypesCannotBeActivated(
componentType.FullName,
GetType().FullName);

throw new InvalidOperationException(message);
}

var viewComponent = _typeActivatorCache.CreateInstance<object>(
context.ViewContext.HttpContext.RequestServices,
context.ViewComponentDescriptor.Type);

return viewComponent;
}

private PropertyActivator<ViewComponentContext> CreateActivateInfo(PropertyInfo property)
/// <inheritdoc />
public virtual void Release(ViewComponentContext context, object viewComponent)
{
return new PropertyActivator<ViewComponentContext>(property, context => context);
if (context == null)
{
throw new InvalidOperationException(nameof(context));
}

if (viewComponent == null)
{
throw new InvalidOperationException(nameof(viewComponent));
}

var disposable = viewComponent as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// 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.Reflection;
using Microsoft.Extensions.Internal;

namespace Microsoft.AspNetCore.Mvc.ViewComponents
{
/// <summary>
/// Default implementation for <see cref="IViewComponentFactory"/>.
/// </summary>
public class DefaultViewComponentFactory : IViewComponentFactory
{
private readonly IViewComponentActivator _activator;
private readonly Func<Type, PropertyActivator<ViewComponentContext>[]> _getPropertiesToActivate;
private readonly ConcurrentDictionary<Type, PropertyActivator<ViewComponentContext>[]> _injectActions;

/// <summary>
/// Creates a new instance of <see cref="DefaultViewComponentFactory"/>
/// </summary>
/// <param name="activator">
/// The <see cref="IViewComponentActivator"/> used to create new view component instances.
/// </param>
public DefaultViewComponentFactory(IViewComponentActivator activator)
{
if (activator == null)
{
throw new ArgumentNullException(nameof(activator));
}

_activator = activator;

_getPropertiesToActivate = type => PropertyActivator<ViewComponentContext>.GetPropertiesToActivate(
type,
typeof(ViewComponentContextAttribute),
CreateActivateInfo);

_injectActions = new ConcurrentDictionary<Type, PropertyActivator<ViewComponentContext>[]>();
}

/// <inheritdoc />
public object CreateViewComponent(ViewComponentContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

var component = _activator.Create(context);

InjectProperties(context, component);

return component;
}

private void InjectProperties(ViewComponentContext context, object viewComponent)
{
var propertiesToActivate = _injectActions.GetOrAdd(
viewComponent.GetType(),
type =>
PropertyActivator<ViewComponentContext>.GetPropertiesToActivate(
type,
typeof(ViewComponentContextAttribute),
CreateActivateInfo));

for (var i = 0; i < propertiesToActivate.Length; i++)
{
var activateInfo = propertiesToActivate[i];
activateInfo.Activate(viewComponent, context);
}
}

private static PropertyActivator<ViewComponentContext> CreateActivateInfo(PropertyInfo property)
{
return new PropertyActivator<ViewComponentContext>(property, context => context);
}

/// <inheritdoc />
public void ReleaseViewComponent(ViewComponentContext context, object component)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

if (component == null)
{
throw new ArgumentNullException(nameof(component));
}

_activator.Release(context, component);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,25 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
/// </summary>
public class DefaultViewComponentInvoker : IViewComponentInvoker
{
private readonly ITypeActivatorCache _typeActivatorCache;
private readonly IViewComponentActivator _viewComponentActivator;
private readonly IViewComponentFactory _viewComponentFactory;
private readonly DiagnosticSource _diagnosticSource;
private readonly ILogger _logger;

/// <summary>
/// Initializes a new instance of <see cref="DefaultViewComponentInvoker"/>.
/// </summary>
/// <param name="typeActivatorCache">Caches factories for instantiating view component instances.</param>
/// <param name="viewComponentActivator">The <see cref="IViewComponentActivator"/>.</param>
/// <param name="viewComponentFactory">The <see cref="IViewComponentFactory"/>.</param>
/// <param name="diagnosticSource">The <see cref="DiagnosticSource"/>.</param>
/// <param name="logger">The <see cref="ILogger"/>.</param>
public DefaultViewComponentInvoker(
ITypeActivatorCache typeActivatorCache,
IViewComponentActivator viewComponentActivator,
IViewComponentFactory viewComponentFactory,
DiagnosticSource diagnosticSource,
ILogger logger)
{
if (typeActivatorCache == null)
if (viewComponentFactory == null)
{
throw new ArgumentNullException(nameof(typeActivatorCache));
}

if (viewComponentActivator == null)
{
throw new ArgumentNullException(nameof(viewComponentActivator));
throw new ArgumentNullException(nameof(viewComponentFactory));
}

if (diagnosticSource == null)
Expand All @@ -57,8 +50,7 @@ public DefaultViewComponentInvoker(
throw new ArgumentNullException(nameof(logger));
}

_typeActivatorCache = typeActivatorCache;
_viewComponentActivator = viewComponentActivator;
_viewComponentFactory = viewComponentFactory;
_diagnosticSource = diagnosticSource;
_logger = logger;
}
Expand Down Expand Up @@ -95,24 +87,9 @@ public async Task InvokeAsync(ViewComponentContext context)
await result.ExecuteAsync(context);
}

private object CreateComponent(ViewComponentContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

var services = context.ViewContext.HttpContext.RequestServices;
var component = _typeActivatorCache.CreateInstance<object>(
services,
context.ViewComponentDescriptor.Type);
_viewComponentActivator.Activate(component, context);
return component;
}

private async Task<IViewComponentResult> InvokeAsyncCore(ViewComponentContext context)
{
var component = CreateComponent(context);
var component = _viewComponentFactory.CreateViewComponent(context);

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

_viewComponentFactory.ReleaseViewComponent(context, component);

return viewComponentResult;
}
}

private IViewComponentResult InvokeSyncCore(ViewComponentContext context)
{
var component = CreateComponent(context);
var component = _viewComponentFactory.CreateViewComponent(context);

using (_logger.ViewComponentScope(context))
{
Expand All @@ -155,6 +134,8 @@ private IViewComponentResult InvokeSyncCore(ViewComponentContext context)
}
catch (TargetInvocationException ex)
{
_viewComponentFactory.ReleaseViewComponent(context, component);

// Preserve callstack of any user-thrown exceptions.
var exceptionInfo = ExceptionDispatchInfo.Capture(ex.InnerException);
exceptionInfo.Throw();
Expand All @@ -165,6 +146,8 @@ private IViewComponentResult InvokeSyncCore(ViewComponentContext context)
_logger.ViewComponentExecuted(context, startTimestamp, viewComponentResult);
_diagnosticSource.AfterViewComponent(context, viewComponentResult, component);

_viewComponentFactory.ReleaseViewComponent(context, component);

return viewComponentResult;
}
}
Expand Down
Loading

0 comments on commit 354400f

Please sign in to comment.