File: ViewComponents\DefaultViewComponentSelector.cs
Web Access
Project: src\src\Mvc\Mvc.ViewFeatures\src\Microsoft.AspNetCore.Mvc.ViewFeatures.csproj (Microsoft.AspNetCore.Mvc.ViewFeatures)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Linq;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
 
namespace Microsoft.AspNetCore.Mvc.ViewComponents;
 
/// <summary>
/// Default implementation of <see cref="IViewComponentSelector"/>.
/// </summary>
public class DefaultViewComponentSelector : IViewComponentSelector
{
    private readonly IViewComponentDescriptorCollectionProvider _descriptorProvider;
 
    private ViewComponentDescriptorCache _cache;
 
    /// <summary>
    /// Creates a new <see cref="DefaultViewComponentSelector"/>.
    /// </summary>
    /// <param name="descriptorProvider">The <see cref="IViewComponentDescriptorCollectionProvider"/>.</param>
    public DefaultViewComponentSelector(IViewComponentDescriptorCollectionProvider descriptorProvider)
    {
        _descriptorProvider = descriptorProvider;
    }
 
    /// <inheritdoc />
    public ViewComponentDescriptor SelectComponent(string componentName)
    {
        ArgumentNullException.ThrowIfNull(componentName);
 
        var collection = _descriptorProvider.ViewComponents;
        if (_cache == null || _cache.Version != collection.Version)
        {
            _cache = new ViewComponentDescriptorCache(collection);
        }
 
        // ViewComponent names can either be fully-qualified, or refer to the 'short-name'. If the provided
        // name contains a '.' - then it's a fully-qualified name.
        if (componentName.Contains('.'))
        {
            return _cache.SelectByFullName(componentName);
        }
        else
        {
            return _cache.SelectByShortName(componentName);
        }
    }
 
    private sealed class ViewComponentDescriptorCache
    {
        private readonly ILookup<string, ViewComponentDescriptor> _lookupByShortName;
        private readonly ILookup<string, ViewComponentDescriptor> _lookupByFullName;
 
        public ViewComponentDescriptorCache(ViewComponentDescriptorCollection collection)
        {
            Version = collection.Version;
 
            _lookupByShortName = collection.Items.ToLookup(c => c.ShortName, c => c);
            _lookupByFullName = collection.Items.ToLookup(c => c.FullName, c => c);
        }
 
        public int Version { get; }
 
        public ViewComponentDescriptor SelectByShortName(string name)
        {
            return Select(_lookupByShortName, name);
        }
 
        public ViewComponentDescriptor SelectByFullName(string name)
        {
            return Select(_lookupByFullName, name);
        }
 
        private static ViewComponentDescriptor Select(
            ILookup<string, ViewComponentDescriptor> candidates,
            string name)
        {
            var matches = candidates[name];
 
            var count = matches.Count();
            if (count == 0)
            {
                return null;
            }
            else if (count == 1)
            {
                return matches.Single();
            }
            else
            {
                var matchedTypes = new List<string>();
                foreach (var candidate in matches)
                {
                    matchedTypes.Add(Resources.FormatViewComponent_AmbiguousTypeMatch_Item(
                        candidate.TypeInfo.FullName,
                        candidate.FullName));
                }
 
                var typeNames = string.Join(Environment.NewLine, matchedTypes);
                throw new InvalidOperationException(
                    Resources.FormatViewComponent_AmbiguousTypeMatch(name, Environment.NewLine, typeNames));
            }
        }
    }
}