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

Commit

Permalink
Browse files Browse the repository at this point in the history
[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.
  • 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.
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
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>
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();
}
}
}
}
@@ -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);
}
}
}
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 class DefaultViewComponentInvoker : IViewComponentInvoker
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

0 comments on commit 354400f

Please sign in to comment.