diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultAssemblyPartDiscoveryProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultAssemblyPartDiscoveryProvider.cs index 11d755086f..26ff3c1630 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultAssemblyPartDiscoveryProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultAssemblyPartDiscoveryProvider.cs @@ -63,14 +63,96 @@ namespace Microsoft.AspNetCore.Mvc.Internal return Enumerable.Empty(); } - return dependencyContext.RuntimeLibraries.Where(IsCandidateLibrary); + var candidatesResolver = new CandidateResolver(dependencyContext.RuntimeLibraries, ReferenceAssemblies); + return candidatesResolver.GetCandidates(); } - private static bool IsCandidateLibrary(RuntimeLibrary library) + private class CandidateResolver { - Debug.Assert(ReferenceAssemblies != null); - return !ReferenceAssemblies.Contains(library.Name) && - library.Dependencies.Any(dependency => ReferenceAssemblies.Contains(dependency.Name)); + private readonly IDictionary _dependencies; + + public CandidateResolver(IReadOnlyList dependencies, ISet referenceAssemblies) + { + _dependencies = dependencies + .ToDictionary(d => d.Name, d => CreateDependency(d, referenceAssemblies)); + } + + 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) + { + Debug.Assert(_dependencies.ContainsKey(dependency)); + + var candidateEntry = _dependencies[dependency]; + if (candidateEntry.Classification != DependencyClassification.Unknown) + { + return candidateEntry.Classification; + } + else + { + var classification = DependencyClassification.NotCandidate; + foreach (var candidateDependency in candidateEntry.Library.Dependencies) + { + var dependencyClassification = ComputeClassification(candidateDependency.Name); + if (dependencyClassification == DependencyClassification.Candidate || + dependencyClassification == DependencyClassification.MvcReference) + { + classification = DependencyClassification.Candidate; + break; + } + } + + candidateEntry.Classification = classification; + + return classification; + } + } + + public IEnumerable GetCandidates() + { + foreach (var dependency in _dependencies) + { + if (ComputeClassification(dependency.Key) == DependencyClassification.Candidate) + { + 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, + Candidate = 1, + NotCandidate = 2, + MvcReference = 3 + } } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultAssemblyPartDiscoveryProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultAssemblyPartDiscoveryProviderTest.cs index c63d621448..45d87810ba 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultAssemblyPartDiscoveryProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultAssemblyPartDiscoveryProviderTest.cs @@ -63,6 +63,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal GetLibrary("Bar", "Microsoft.AspNetCore.Mvc"), GetLibrary("Qux", "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc"), GetLibrary("Baz", "Microsoft.AspNetCore.Mvc.Abstractions"), + GetLibrary("Microsoft.AspNetCore.Mvc.Core"), + GetLibrary("Microsoft.AspNetCore.Mvc"), + GetLibrary("Not.Mvc.Assembly"), + GetLibrary("Unofficial.Microsoft.AspNetCore.Mvc"), + GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"), + }, Enumerable.Empty()); @@ -73,6 +79,45 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Equal(new[] { "Foo", "Bar", "Baz" }, candidates.Select(a => a.Name)); } + [Fact] + public void GetCandidateLibraries_ReturnsLibrariesWithTransitiveReferencesToAnyMvcAssembly() + { + // Arrange + var expectedLibraries = new[] { "Foo", "Bar", "Baz", "LibraryA", "LibraryB", "LibraryC", "LibraryE", "LibraryG", "LibraryH" }; + + var dependencyContext = new DependencyContext( + new TargetInfo("framework", "runtime", "signature", isPortable: true), + CompilationOptions.Default, + new CompilationLibrary[0], + new[] + { + GetLibrary("Foo", "Bar"), + GetLibrary("Bar", "Microsoft.AspNetCore.Mvc"), + GetLibrary("Qux", "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc"), + GetLibrary("Baz", "Microsoft.AspNetCore.Mvc.Abstractions"), + GetLibrary("Microsoft.AspNetCore.Mvc"), + GetLibrary("Not.Mvc.Assembly"), + GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"), + GetLibrary("Unofficial.Microsoft.AspNetCore.Mvc"), + GetLibrary("LibraryA", "LibraryB"), + GetLibrary("LibraryB","LibraryC"), + GetLibrary("LibraryC", "LibraryD", "Microsoft.AspNetCore.Mvc.Abstractions"), + GetLibrary("LibraryD"), + GetLibrary("LibraryE","LibraryF","LibraryG"), + GetLibrary("LibraryF"), + GetLibrary("LibraryG", "LibraryH"), + GetLibrary("LibraryH", "LibraryI", "Microsoft.AspNetCore.Mvc"), + GetLibrary("LibraryI") + }, + Enumerable.Empty()); + + // Act + var candidates = DefaultAssemblyPartDiscoveryProvider.GetCandidateLibraries(dependencyContext); + + // Assert + Assert.Equal(expectedLibraries, candidates.Select(a => a.Name)); + } + [Fact] public void GetCandidateLibraries_SkipsMvcAssemblies() { @@ -84,11 +129,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal new[] { GetLibrary("MvcSandbox", "Microsoft.AspNetCore.Mvc.Core", "Microsoft.AspNetCore.Mvc"), - GetLibrary("Microsoft.AspNetCore.Mvc.TagHelpers", "Microsoft.AspNetCore.Mvc.Razor"), - GetLibrary("Microsoft.AspNetCore.Mvc", "Microsoft.AspNetCore.Mvc.Abstractions", "Microsoft.AspNetCore.Mvc.Core"), GetLibrary("Microsoft.AspNetCore.Mvc.Core", "Microsoft.AspNetCore.HttpAbstractions"), + GetLibrary("Microsoft.AspNetCore.HttpAbstractions"), + GetLibrary("Microsoft.AspNetCore.Mvc", "Microsoft.AspNetCore.Mvc.Abstractions", "Microsoft.AspNetCore.Mvc.Core"), + GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"), + GetLibrary("Microsoft.AspNetCore.Mvc.TagHelpers", "Microsoft.AspNetCore.Mvc.Razor"), + GetLibrary("Microsoft.AspNetCore.Mvc.Razor"), GetLibrary("ControllersAssembly", "Microsoft.AspNetCore.Mvc"), - }, Enumerable.Empty());