// 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; } } }