// 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.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.ViewComponents
{
///
/// Default implementation for .
///
public class DefaultViewComponentInvoker : IViewComponentInvoker
{
private readonly IViewComponentFactory _viewComponentFactory;
private readonly ViewComponentInvokerCache _viewComponentInvokerCache;
private readonly DiagnosticSource _diagnosticSource;
private readonly ILogger _logger;
///
/// Initializes a new instance of .
///
/// The .
/// The .
/// The .
/// The .
public DefaultViewComponentInvoker(
IViewComponentFactory viewComponentFactory,
#pragma warning disable PUB0001 // Pubternal type in public API
ViewComponentInvokerCache viewComponentInvokerCache,
#pragma warning restore PUB0001
DiagnosticSource diagnosticSource,
ILogger logger)
{
if (viewComponentFactory == null)
{
throw new ArgumentNullException(nameof(viewComponentFactory));
}
if (viewComponentInvokerCache == null)
{
throw new ArgumentNullException(nameof(viewComponentInvokerCache));
}
if (diagnosticSource == null)
{
throw new ArgumentNullException(nameof(diagnosticSource));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_viewComponentFactory = viewComponentFactory;
_viewComponentInvokerCache = viewComponentInvokerCache;
_diagnosticSource = diagnosticSource;
_logger = logger;
}
///
public async Task InvokeAsync(ViewComponentContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var executor = _viewComponentInvokerCache.GetViewComponentMethodExecutor(context);
var returnType = executor.MethodReturnType;
if (returnType == typeof(void) || returnType == typeof(Task))
{
throw new InvalidOperationException(Resources.ViewComponent_MustReturnValue);
}
IViewComponentResult result;
if (executor.IsMethodAsync)
{
result = await InvokeAsyncCore(executor, 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(executor, context);
}
await result.ExecuteAsync(context);
}
private async Task InvokeAsyncCore(ObjectMethodExecutor executor, ViewComponentContext context)
{
var component = _viewComponentFactory.CreateViewComponent(context);
using (_logger.ViewComponentScope(context))
{
var arguments = PrepareArguments(context.Arguments, executor);
_diagnosticSource.BeforeViewComponent(context, component);
_logger.ViewComponentExecuting(context, arguments);
var stopwatch = ValueStopwatch.StartNew();
object resultAsObject;
var returnType = executor.MethodReturnType;
if (returnType == typeof(Task))
{
resultAsObject = await (Task)executor.Execute(component, arguments);
}
else if (returnType == typeof(Task))
{
resultAsObject = await (Task)executor.Execute(component, arguments);
}
else if (returnType == typeof(Task))
{
resultAsObject = await (Task)executor.Execute(component, arguments);
}
else
{
resultAsObject = await executor.ExecuteAsync(component, arguments);
}
var viewComponentResult = CoerceToViewComponentResult(resultAsObject);
_logger.ViewComponentExecuted(context, stopwatch.GetElapsedTime(), viewComponentResult);
_diagnosticSource.AfterViewComponent(context, viewComponentResult, component);
_viewComponentFactory.ReleaseViewComponent(context, component);
return viewComponentResult;
}
}
private IViewComponentResult InvokeSyncCore(ObjectMethodExecutor executor, ViewComponentContext context)
{
var component = _viewComponentFactory.CreateViewComponent(context);
using (_logger.ViewComponentScope(context))
{
var arguments = PrepareArguments(context.Arguments, executor);
_diagnosticSource.BeforeViewComponent(context, component);
_logger.ViewComponentExecuting(context, arguments);
var stopwatch = ValueStopwatch.StartNew();
object result;
try
{
result = executor.Execute(component, arguments);
}
finally
{
_viewComponentFactory.ReleaseViewComponent(context, component);
}
var viewComponentResult = CoerceToViewComponentResult(result);
_logger.ViewComponentExecuted(context, stopwatch.GetElapsedTime(), 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));
}
private static object[] PrepareArguments(
IDictionary parameters,
ObjectMethodExecutor objectMethodExecutor)
{
var declaredParameterInfos = objectMethodExecutor.MethodParameters;
var count = declaredParameterInfos.Length;
if (count == 0)
{
return null;
}
var arguments = new object[count];
for (var index = 0; index < count; index++)
{
var parameterInfo = declaredParameterInfos[index];
if (!parameters.TryGetValue(parameterInfo.Name, out var value))
{
value = objectMethodExecutor.GetDefaultValueForParameter(index);
}
arguments[index] = value;
}
return arguments;
}
}
}