aspnetcore/src/Microsoft.AspNetCore.Mvc.Vi.../ViewComponents/DefaultViewComponentInvoker.cs

185 lines
7.1 KiB
C#

// 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.Diagnostics;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.ViewComponents
{
/// <summary>
/// Default implementation for <see cref="IViewComponentInvoker"/>.
/// </summary>
public class DefaultViewComponentInvoker : IViewComponentInvoker
{
private readonly IViewComponentFactory _viewComponentFactory;
private readonly DiagnosticSource _diagnosticSource;
private readonly ILogger _logger;
/// <summary>
/// Initializes a new instance of <see cref="DefaultViewComponentInvoker"/>.
/// </summary>
/// <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(
IViewComponentFactory viewComponentFactory,
DiagnosticSource diagnosticSource,
ILogger logger)
{
if (viewComponentFactory == null)
{
throw new ArgumentNullException(nameof(viewComponentFactory));
}
if (diagnosticSource == null)
{
throw new ArgumentNullException(nameof(diagnosticSource));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_viewComponentFactory = viewComponentFactory;
_diagnosticSource = diagnosticSource;
_logger = logger;
}
/// <inheritdoc />
public async Task InvokeAsync(ViewComponentContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var methodInfo = context.ViewComponentDescriptor?.MethodInfo;
if (methodInfo == null)
{
throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(
nameof(ViewComponentDescriptor.MethodInfo),
nameof(ViewComponentDescriptor)));
}
var isAsync = typeof(Task).GetTypeInfo().IsAssignableFrom(methodInfo.ReturnType.GetTypeInfo());
IViewComponentResult result;
if (isAsync)
{
result = await InvokeAsyncCore(context);
}
else
{
// We support falling back to synchronous if there is no InvokeAsync method, in this case we'll still
// execute the IViewResult asynchronously.
result = InvokeSyncCore(context);
}
await result.ExecuteAsync(context);
}
private async Task<IViewComponentResult> InvokeAsyncCore(ViewComponentContext context)
{
var component = _viewComponentFactory.CreateViewComponent(context);
using (_logger.ViewComponentScope(context))
{
var method = context.ViewComponentDescriptor.MethodInfo;
var arguments = ControllerActionExecutor.PrepareArguments(context.Arguments, method.GetParameters());
_diagnosticSource.BeforeViewComponent(context, component);
_logger.ViewComponentExecuting(context, arguments);
var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0;
var result = await ControllerActionExecutor.ExecuteAsync(method, component, arguments);
var viewComponentResult = CoerceToViewComponentResult(result);
_logger.ViewComponentExecuted(context, startTimestamp, viewComponentResult);
_diagnosticSource.AfterViewComponent(context, viewComponentResult, component);
_viewComponentFactory.ReleaseViewComponent(context, component);
return viewComponentResult;
}
}
private IViewComponentResult InvokeSyncCore(ViewComponentContext context)
{
var component = _viewComponentFactory.CreateViewComponent(context);
using (_logger.ViewComponentScope(context))
{
var method = context.ViewComponentDescriptor.MethodInfo;
var arguments = ControllerActionExecutor.PrepareArguments(
context.Arguments,
method.GetParameters());
_diagnosticSource.BeforeViewComponent(context, component);
_logger.ViewComponentExecuting(context, arguments);
var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0;
object result;
try
{
result = method.Invoke(component, arguments);
}
catch (TargetInvocationException ex)
{
_viewComponentFactory.ReleaseViewComponent(context, component);
// Preserve callstack of any user-thrown exceptions.
var exceptionInfo = ExceptionDispatchInfo.Capture(ex.InnerException);
exceptionInfo.Throw();
return null; // Unreachable
}
var viewComponentResult = CoerceToViewComponentResult(result);
_logger.ViewComponentExecuted(context, startTimestamp, viewComponentResult);
_diagnosticSource.AfterViewComponent(context, viewComponentResult, component);
_viewComponentFactory.ReleaseViewComponent(context, component);
return viewComponentResult;
}
}
private static IViewComponentResult CoerceToViewComponentResult(object value)
{
if (value == null)
{
throw new InvalidOperationException(Resources.ViewComponent_MustReturnValue);
}
var componentResult = value as IViewComponentResult;
if (componentResult != null)
{
return componentResult;
}
var stringResult = value as string;
if (stringResult != null)
{
return new ContentViewComponentResult(stringResult);
}
var htmlContent = value as IHtmlContent;
if (htmlContent != null)
{
return new HtmlContentViewComponentResult(htmlContent);
}
throw new InvalidOperationException(Resources.FormatViewComponent_InvalidReturnValue(
typeof(string).Name,
typeof(IHtmlContent).Name,
typeof(IViewComponentResult).Name));
}
}
}