// 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 Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.Extensions.DependencyModel; namespace Microsoft.AspNetCore.Mvc.Internal { // Discovers assemblies that are part of the MVC application using the DependencyContext. public static class DefaultAssemblyPartDiscoveryProvider { internal static HashSet ReferenceAssemblies { get; } = new HashSet(StringComparer.OrdinalIgnoreCase) { "Microsoft.AspNetCore.Mvc", "Microsoft.AspNetCore.Mvc.Abstractions", "Microsoft.AspNetCore.Mvc.ApiExplorer", "Microsoft.AspNetCore.Mvc.Core", "Microsoft.AspNetCore.Mvc.Cors", "Microsoft.AspNetCore.Mvc.DataAnnotations", "Microsoft.AspNetCore.Mvc.Formatters.Json", "Microsoft.AspNetCore.Mvc.Formatters.Xml", "Microsoft.AspNetCore.Mvc.Localization", "Microsoft.AspNetCore.Mvc.Razor", "Microsoft.AspNetCore.Mvc.Razor.Extensions", "Microsoft.AspNetCore.Mvc.RazorPages", "Microsoft.AspNetCore.Mvc.TagHelpers", "Microsoft.AspNetCore.Mvc.ViewFeatures" }; public static IEnumerable DiscoverAssemblyParts(string entryPointAssemblyName) { var entryAssembly = Assembly.Load(new AssemblyName(entryPointAssemblyName)); var context = DependencyContext.Load(entryAssembly); return GetCandidateAssemblies(entryAssembly, context).Select(p => new AssemblyPart(p)); } internal static IEnumerable GetCandidateAssemblies(Assembly entryAssembly, DependencyContext dependencyContext) { if (dependencyContext == null) { // Use the entry assembly as the sole candidate. return new[] { entryAssembly }; } return GetCandidateLibraries(dependencyContext) .SelectMany(library => library.GetDefaultAssemblyNames(dependencyContext)) .Select(Assembly.Load); } // Returns a list of libraries that references the assemblies in . // By default it returns all assemblies that reference any of the primary MVC assemblies // while ignoring MVC assemblies. // Internal for unit testing internal static IEnumerable GetCandidateLibraries(DependencyContext dependencyContext) { if (ReferenceAssemblies == null) { return Enumerable.Empty(); } var candidatesResolver = new CandidateResolver(dependencyContext.RuntimeLibraries, ReferenceAssemblies); return candidatesResolver.GetCandidates(); } private class CandidateResolver { private readonly IDictionary _runtimeDependencies; public CandidateResolver(IReadOnlyList runtimeDependencies, ISet referenceAssemblies) { var dependenciesWithNoDuplicates = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var dependency in runtimeDependencies) { if (dependenciesWithNoDuplicates.ContainsKey(dependency.Name)) { throw new InvalidOperationException(Resources.FormatCandidateResolver_DifferentCasedReference(dependency.Name)); } dependenciesWithNoDuplicates.Add(dependency.Name, CreateDependency(dependency, referenceAssemblies)); } _runtimeDependencies = dependenciesWithNoDuplicates; } private Dependency CreateDependency(RuntimeLibrary library, ISet referenceAssemblies) { var classification = DependencyClassification.Unknown; if (referenceAssemblies.Contains(library.Name)) { classification = DependencyClassification.MvcReference; } return new Dependency(library, classification); } private DependencyClassification ComputeClassification(string dependency) { if (!_runtimeDependencies.ContainsKey(dependency)) { // Library does not have runtime dependency. Since we can't infer // anything about it's references, we'll assume it does not have a reference to Mvc. return DependencyClassification.DoesNotReferenceMvc; } var candidateEntry = _runtimeDependencies[dependency]; if (candidateEntry.Classification != DependencyClassification.Unknown) { return candidateEntry.Classification; } else { var classification = DependencyClassification.DoesNotReferenceMvc; foreach (var candidateDependency in candidateEntry.Library.Dependencies) { var dependencyClassification = ComputeClassification(candidateDependency.Name); if (dependencyClassification == DependencyClassification.ReferencesMvc || dependencyClassification == DependencyClassification.MvcReference) { classification = DependencyClassification.ReferencesMvc; break; } } candidateEntry.Classification = classification; return classification; } } public IEnumerable GetCandidates() { foreach (var dependency in _runtimeDependencies) { if (ComputeClassification(dependency.Key) == DependencyClassification.ReferencesMvc) { yield return dependency.Value.Library; } } } private class Dependency { public Dependency(RuntimeLibrary library, DependencyClassification classification) { Library = library; Classification = classification; } public RuntimeLibrary Library { get; } public DependencyClassification Classification { get; set; } public override string ToString() { return $"Library: {Library.Name}, Classification: {Classification}"; } } private enum DependencyClassification { Unknown = 0, /// /// References (directly or transitively) one of the Mvc packages listed in /// . /// ReferencesMvc = 1, /// /// Does not reference (directly or transitively) one of the Mvc packages listed by /// . /// DoesNotReferenceMvc = 2, /// /// One of the references listed in . /// MvcReference = 3, } } } }