// 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.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ViewFeatures; namespace Microsoft.AspNetCore.Mvc.ViewComponents { /// /// Default implementation of . /// public class DefaultViewComponentDescriptorProvider : IViewComponentDescriptorProvider { private const string AsyncMethodName = "InvokeAsync"; private const string SyncMethodName = "Invoke"; private readonly IAssemblyProvider _assemblyProvider; /// /// Creates a new . /// /// The . public DefaultViewComponentDescriptorProvider(IAssemblyProvider assemblyProvider) { _assemblyProvider = assemblyProvider; } /// public virtual IEnumerable GetViewComponents() { var types = GetCandidateTypes(); return types .Where(IsViewComponentType) .Select(CreateDescriptor); } /// /// Gets the candidate instances. The results of this will be provided to /// for filtering. /// /// A list of instances. protected virtual IEnumerable GetCandidateTypes() { var assemblies = _assemblyProvider.CandidateAssemblies; return assemblies.SelectMany(a => a.ExportedTypes).Select(t => t.GetTypeInfo()); } /// /// Determines whether or not the given is a view component class. /// /// The . /// /// true if represents a view component class, otherwise false. /// protected virtual bool IsViewComponentType(TypeInfo typeInfo) { if (typeInfo == null) { throw new ArgumentNullException(nameof(typeInfo)); } return ViewComponentConventions.IsComponent(typeInfo); } private static ViewComponentDescriptor CreateDescriptor(TypeInfo typeInfo) { var type = typeInfo.AsType(); var candidate = new ViewComponentDescriptor { FullName = ViewComponentConventions.GetComponentFullName(typeInfo), ShortName = ViewComponentConventions.GetComponentName(typeInfo), TypeInfo = typeInfo, MethodInfo = FindMethod(type) }; return candidate; } private static MethodInfo FindMethod(Type componentType) { var componentName = componentType.FullName; var methods = componentType.GetMethods(BindingFlags.Public | BindingFlags.Instance) .Where(method => string.Equals(method.Name, AsyncMethodName, StringComparison.Ordinal) || string.Equals(method.Name, SyncMethodName, StringComparison.Ordinal)) .ToArray(); if (methods.Length == 0) { throw new InvalidOperationException( Resources.FormatViewComponent_CannotFindMethod(SyncMethodName, AsyncMethodName, componentName)); } else if (methods.Length > 1) { throw new InvalidOperationException( Resources.FormatViewComponent_AmbiguousMethods(componentName, AsyncMethodName, SyncMethodName)); } var selectedMethod = methods[0]; if (string.Equals(selectedMethod.Name, AsyncMethodName, StringComparison.Ordinal)) { if (!selectedMethod.ReturnType.GetTypeInfo().IsGenericType || selectedMethod.ReturnType.GetGenericTypeDefinition() != typeof(Task<>)) { throw new InvalidOperationException(Resources.FormatViewComponent_AsyncMethod_ShouldReturnTask( AsyncMethodName, componentName, nameof(Task))); } } else { if (selectedMethod.ReturnType == typeof(void)) { throw new InvalidOperationException(Resources.FormatViewComponent_SyncMethod_ShouldReturnValue( SyncMethodName, componentName)); } else if (selectedMethod.ReturnType.IsAssignableFrom(typeof(Task))) { throw new InvalidOperationException(Resources.FormatViewComponent_SyncMethod_CannotReturnTask( SyncMethodName, componentName, nameof(Task))); } } return selectedMethod; } } }