Use attributes for application part discovery (#10271)

* Use attributes for application part discovery

Fixes https://github.com/aspnet/AspNetCore/issues/4332
This commit is contained in:
Pranav K 2019-05-20 09:10:12 -07:00 committed by GitHub
parent e92b4800df
commit 200c9e21bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 223 additions and 998 deletions

View File

@ -56,9 +56,6 @@
<ExternalAspNetCoreAppReference Include="System.Security.Cryptography.Xml" Version="$(SystemSecurityCryptographyXmlPackageVersion)" /> <ExternalAspNetCoreAppReference Include="System.Security.Cryptography.Xml" Version="$(SystemSecurityCryptographyXmlPackageVersion)" />
<ExternalAspNetCoreAppReference Include="System.Threading.Channels" Version="$(SystemThreadingChannelsPackageVersion)" /> <ExternalAspNetCoreAppReference Include="System.Threading.Channels" Version="$(SystemThreadingChannelsPackageVersion)" />
<!-- Dependencies from dotnet/core-setup -->
<ExternalAspNetCoreAppReference Include="Microsoft.Extensions.DependencyModel" Version="$(MicrosoftExtensionsDependencyModelPackageVersion)" />
<!-- <!--
Transitive dependencies of other assemblies in the shared framework. These are listed separately and should not be included directly Transitive dependencies of other assemblies in the shared framework. These are listed separately and should not be included directly
when setting `<Reference>`. These are listed for the purpose of tests and servicing builds only. when setting `<Reference>`. These are listed for the purpose of tests and servicing builds only.

View File

@ -15,7 +15,6 @@
<Reference Include="Microsoft.AspNetCore.Routing.Abstractions" /> <Reference Include="Microsoft.AspNetCore.Routing.Abstractions" />
<Reference Include="Microsoft.AspNetCore.Routing" /> <Reference Include="Microsoft.AspNetCore.Routing" />
<Reference Include="Microsoft.Extensions.DependencyInjection" /> <Reference Include="Microsoft.Extensions.DependencyInjection" />
<Reference Include="Microsoft.Extensions.DependencyModel" />
<Reference Include="Microsoft.Extensions.FileProviders.Abstractions" /> <Reference Include="Microsoft.Extensions.FileProviders.Abstractions" />
<Reference Include="Microsoft.Extensions.Logging.Abstractions" /> <Reference Include="Microsoft.Extensions.Logging.Abstractions" />
<Reference Include="Microsoft.Extensions.ParameterDefaultValue.Sources" /> <Reference Include="Microsoft.Extensions.ParameterDefaultValue.Sources" />

View File

@ -1547,6 +1547,12 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
protected ApplicationPart() { } protected ApplicationPart() { }
public abstract string Name { get; } public abstract string Name { get; }
} }
[System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=true)]
public sealed partial class ApplicationPartAttribute : System.Attribute
{
public ApplicationPartAttribute(string assemblyName) { }
public string AssemblyName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
}
public abstract partial class ApplicationPartFactory public abstract partial class ApplicationPartFactory
{ {
protected ApplicationPartFactory() { } protected ApplicationPartFactory() { }
@ -1560,13 +1566,12 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
public System.Collections.Generic.IList<Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationFeatureProvider> FeatureProviders { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public System.Collections.Generic.IList<Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationFeatureProvider> FeatureProviders { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public void PopulateFeature<TFeature>(TFeature feature) { } public void PopulateFeature<TFeature>(TFeature feature) { }
} }
public partial class AssemblyPart : Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPart, Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationPartTypeProvider, Microsoft.AspNetCore.Mvc.ApplicationParts.ICompilationReferencesProvider public partial class AssemblyPart : Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPart, Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationPartTypeProvider
{ {
public AssemblyPart(System.Reflection.Assembly assembly) { } public AssemblyPart(System.Reflection.Assembly assembly) { }
public System.Reflection.Assembly Assembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public System.Reflection.Assembly Assembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public override string Name { get { throw null; } } public override string Name { get { throw null; } }
public System.Collections.Generic.IEnumerable<System.Reflection.TypeInfo> Types { get { throw null; } } public System.Collections.Generic.IEnumerable<System.Reflection.TypeInfo> Types { get { throw null; } }
public System.Collections.Generic.IEnumerable<string> GetReferencePaths() { throw null; }
} }
public partial class DefaultApplicationPartFactory : Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartFactory public partial class DefaultApplicationPartFactory : Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartFactory
{ {

View File

@ -1,295 +0,0 @@
// 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.Core;
using Microsoft.Extensions.DependencyModel;
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
{
internal class ApplicationAssembliesProvider
{
internal static HashSet<string> ReferenceAssemblies { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
// The deps file for the Microsoft.AspNetCore.App shared runtime is authored in a way where it does not say
// it depends on Microsoft.AspNetCore.Mvc even though it does. Explicitly list it so that referencing this runtime causes
// assembly discovery to work correctly.
"Microsoft.AspNetCore.App",
"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.NewtonsoftJson",
"Microsoft.AspNetCore.Mvc.Razor",
"Microsoft.AspNetCore.Mvc.RazorPages",
"Microsoft.AspNetCore.Mvc.TagHelpers",
"Microsoft.AspNetCore.Mvc.ViewFeatures",
};
/// <summary>
/// Returns an ordered list of application assemblies.
/// <para>
/// The order is as follows:
/// * Entry assembly
/// * Assemblies specified in the application's deps file ordered by name.
/// <para>
/// Each assembly is immediately followed by assemblies specified by annotated <see cref="RelatedAssemblyAttribute"/> ordered by name.
/// </para>
/// </para>
/// </summary>
public IEnumerable<Assembly> ResolveAssemblies(Assembly entryAssembly)
{
var dependencyContext = LoadDependencyContext(entryAssembly);
IEnumerable<AssemblyItem> assemblyItems;
if (dependencyContext == null || dependencyContext.CompileLibraries.Count == 0)
{
// If an application was built with PreserveCompilationContext = false, CompileLibraries will be empty and we
// can no longer reliably infer the dependency closure. In this case, treat it the same as a missing
// deps file.
assemblyItems = new[] { GetAssemblyItem(entryAssembly) };
}
else
{
assemblyItems = ResolveFromDependencyContext(dependencyContext);
}
assemblyItems = assemblyItems
.OrderBy(item => item.Assembly == entryAssembly ? 0 : 1)
.ThenBy(item => item.Assembly.FullName, StringComparer.Ordinal);
foreach (var item in assemblyItems)
{
yield return item.Assembly;
foreach (var associatedAssembly in item.RelatedAssemblies.Distinct().OrderBy(assembly => assembly.FullName, StringComparer.Ordinal))
{
yield return associatedAssembly;
}
}
}
protected virtual DependencyContext LoadDependencyContext(Assembly assembly) => DependencyContext.Load(assembly);
private List<AssemblyItem> ResolveFromDependencyContext(DependencyContext dependencyContext)
{
var assemblyItems = new List<AssemblyItem>();
var relatedAssemblies = new Dictionary<Assembly, AssemblyItem>();
var candidateAssemblies = GetCandidateLibraries(dependencyContext)
.SelectMany(library => GetLibraryAssemblies(dependencyContext, library));
foreach (var assembly in candidateAssemblies)
{
var assemblyItem = GetAssemblyItem(assembly);
assemblyItems.Add(assemblyItem);
foreach (var relatedAssembly in assemblyItem.RelatedAssemblies)
{
if (relatedAssemblies.TryGetValue(relatedAssembly, out var otherEntry))
{
var message = string.Join(
Environment.NewLine,
Resources.FormatApplicationAssembliesProvider_DuplicateRelatedAssembly(relatedAssembly.FullName),
otherEntry.Assembly.FullName,
assembly.FullName);
throw new InvalidOperationException(message);
}
relatedAssemblies.Add(relatedAssembly, assemblyItem);
}
}
// Remove any top level assemblies that appear as an associated assembly.
assemblyItems.RemoveAll(item => relatedAssemblies.ContainsKey(item.Assembly));
return assemblyItems;
}
protected virtual IEnumerable<Assembly> GetLibraryAssemblies(DependencyContext dependencyContext, RuntimeLibrary runtimeLibrary)
{
foreach (var assemblyName in runtimeLibrary.GetDefaultAssemblyNames(dependencyContext))
{
var assembly = Assembly.Load(assemblyName);
yield return assembly;
}
}
protected virtual IReadOnlyList<Assembly> GetRelatedAssemblies(Assembly assembly)
{
// Do not require related assemblies to be available in the default code path.
return RelatedAssemblyAttribute.GetRelatedAssemblies(assembly, throwOnError: false);
}
private AssemblyItem GetAssemblyItem(Assembly assembly)
{
var relatedAssemblies = GetRelatedAssemblies(assembly);
// Ensure we don't have any cycles. A cycle could be formed if a related assembly points to the primary assembly.
foreach (var relatedAssembly in relatedAssemblies)
{
if (relatedAssembly.IsDefined(typeof(RelatedAssemblyAttribute)))
{
throw new InvalidOperationException(
Resources.FormatApplicationAssembliesProvider_RelatedAssemblyCannotDefineAdditional(relatedAssembly.FullName, assembly.FullName));
}
}
return new AssemblyItem(assembly, relatedAssemblies);
}
// Returns a list of libraries that references the assemblies in <see cref="ReferenceAssemblies"/>.
// 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<RuntimeLibrary> GetCandidateLibraries(DependencyContext dependencyContext)
{
// When using the Microsoft.AspNetCore.App shared runtimes, entries in the RuntimeLibraries
// get "erased" and it is no longer accurate to query to determine a library's dependency closure.
// We'll use CompileLibraries to calculate the dependency graph and runtime library to resolve assemblies to inspect.
var candidatesResolver = new CandidateResolver(dependencyContext.CompileLibraries, ReferenceAssemblies);
foreach (var library in dependencyContext.RuntimeLibraries)
{
if (candidatesResolver.IsCandidate(library))
{
yield return library;
}
}
}
private class CandidateResolver
{
private readonly IDictionary<string, Dependency> _compileLibraries;
public CandidateResolver(IReadOnlyList<Library> compileLibraries, ISet<string> referenceAssemblies)
{
var compileLibrariesWithNoDuplicates = new Dictionary<string, Dependency>(StringComparer.OrdinalIgnoreCase);
foreach (var library in compileLibraries)
{
if (compileLibrariesWithNoDuplicates.ContainsKey(library.Name))
{
throw new InvalidOperationException(Resources.FormatCandidateResolver_DifferentCasedReference(library.Name));
}
compileLibrariesWithNoDuplicates.Add(library.Name, CreateDependency(library, referenceAssemblies));
}
_compileLibraries = compileLibrariesWithNoDuplicates;
}
public bool IsCandidate(Library library)
{
var classification = ComputeClassification(library.Name);
return classification == DependencyClassification.ReferencesMvc;
}
private Dependency CreateDependency(Library library, ISet<string> referenceAssemblies)
{
var classification = DependencyClassification.Unknown;
if (referenceAssemblies.Contains(library.Name))
{
classification = DependencyClassification.MvcReference;
}
return new Dependency(library, classification);
}
private DependencyClassification ComputeClassification(string dependency)
{
if (!_compileLibraries.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 = _compileLibraries[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;
}
}
private class Dependency
{
public Dependency(Library library, DependencyClassification classification)
{
Library = library;
Classification = classification;
}
public Library Library { get; }
public DependencyClassification Classification { get; set; }
public override string ToString()
{
return $"Library: {Library.Name}, Classification: {Classification}";
}
}
private enum DependencyClassification
{
Unknown = 0,
/// <summary>
/// References (directly or transitively) one of the Mvc packages listed in
/// <see cref="ReferenceAssemblies"/>.
/// </summary>
ReferencesMvc = 1,
/// <summary>
/// Does not reference (directly or transitively) one of the Mvc packages listed by
/// <see cref="ReferenceAssemblies"/>.
/// </summary>
DoesNotReferenceMvc = 2,
/// <summary>
/// One of the references listed in <see cref="ReferenceAssemblies"/>.
/// </summary>
MvcReference = 3,
}
}
private readonly struct AssemblyItem
{
public AssemblyItem(Assembly assembly, IReadOnlyList<Assembly> associatedAssemblies)
{
Assembly = assembly;
RelatedAssemblies = associatedAssemblies;
}
public Assembly Assembly { get; }
public IReadOnlyList<Assembly> RelatedAssemblies { get; }
}
}
}

View File

@ -0,0 +1,33 @@
// 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;
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
{
/// <summary>
/// Specifies an assembly to be added as an <see cref="ApplicationPart" />.
/// <para>
/// In the ordinary case, MVC will generate <see cref="ApplicationPartAttribute" />
/// instances on the entry assembly for each dependency that references MVC.
/// Each of these assemblies is treated as an <see cref="ApplicationPart" />.
/// </para>
/// </summary>
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class ApplicationPartAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of <see cref="ApplicationPartAttribute" />.
/// </summary>
/// <param name="assemblyName">The assembly name.</param>
public ApplicationPartAttribute(string assemblyName)
{
AssemblyName = assemblyName ?? throw new ArgumentNullException(nameof(assemblyName));
}
/// <summary>
/// Gets the assembly name.
/// </summary>
public string AssemblyName { get; }
}
}

View File

@ -52,17 +52,55 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
internal void PopulateDefaultParts(string entryAssemblyName) internal void PopulateDefaultParts(string entryAssemblyName)
{ {
var entryAssembly = Assembly.Load(new AssemblyName(entryAssemblyName)); var assemblies = GetApplicationPartAssemblies(entryAssemblyName);
var assembliesProvider = new ApplicationAssembliesProvider();
var applicationAssemblies = assembliesProvider.ResolveAssemblies(entryAssembly);
foreach (var assembly in applicationAssemblies) var seenAssemblies = new HashSet<Assembly>();
foreach (var assembly in assemblies)
{ {
var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly); if (!seenAssemblies.Add(assembly))
foreach (var part in partFactory.GetApplicationParts(assembly))
{ {
ApplicationParts.Add(part); // "assemblies" may contain duplicate values, but we want unique ApplicationPart instances.
// Note that we prefer using a HashSet over Distinct since the latter isn't
// guaranteed to preserve the original ordering.
continue;
} }
var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly);
foreach (var applicationPart in partFactory.GetApplicationParts(assembly))
{
ApplicationParts.Add(applicationPart);
}
}
}
private static IEnumerable<Assembly> GetApplicationPartAssemblies(string entryAssemblyName)
{
var entryAssembly = Assembly.Load(new AssemblyName(entryAssemblyName));
// Use ApplicationPartAttribute to get the closure of direct or transitive dependencies
// that reference MVC.
var assembliesFromAttributes = entryAssembly.GetCustomAttributes<ApplicationPartAttribute>()
.Select(name => Assembly.Load(name.AssemblyName))
.OrderBy(assembly => assembly.FullName, StringComparer.Ordinal)
.SelectMany(GetAsemblyClosure);
// The SDK will not include the entry assembly as an application part. We'll explicitly list it
// and have it appear before all other assemblies \ ApplicationParts.
return GetAsemblyClosure(entryAssembly)
.Concat(assembliesFromAttributes);
}
private static IEnumerable<Assembly> GetAsemblyClosure(Assembly assembly)
{
yield return assembly;
var relatedAssemblies = RelatedAssemblyAttribute.GetRelatedAssemblies(assembly, throwOnError: false)
.OrderBy(assembly => assembly.FullName, StringComparer.Ordinal);
foreach (var relatedAssembly in relatedAssemblies)
{
yield return relatedAssembly;
} }
} }
} }

View File

@ -5,17 +5,13 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.Extensions.DependencyModel;
namespace Microsoft.AspNetCore.Mvc.ApplicationParts namespace Microsoft.AspNetCore.Mvc.ApplicationParts
{ {
/// <summary> /// <summary>
/// An <see cref="ApplicationPart"/> backed by an <see cref="System.Reflection.Assembly"/>. /// An <see cref="ApplicationPart"/> backed by an <see cref="System.Reflection.Assembly"/>.
/// </summary> /// </summary>
public class AssemblyPart : public class AssemblyPart : ApplicationPart, IApplicationPartTypeProvider
ApplicationPart,
IApplicationPartTypeProvider,
ICompilationReferencesProvider
{ {
/// <summary> /// <summary>
/// Initializes a new <see cref="AssemblyPart"/> instance. /// Initializes a new <see cref="AssemblyPart"/> instance.
@ -39,27 +35,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<TypeInfo> Types => Assembly.DefinedTypes; public IEnumerable<TypeInfo> Types => Assembly.DefinedTypes;
/// <inheritdoc />
public IEnumerable<string> GetReferencePaths()
{
if (Assembly.IsDynamic)
{
// Skip loading process for dynamic assemblies. This prevents DependencyContextLoader from reading the
// .deps.json file from either manifest resources or the assembly location, which will fail.
return Enumerable.Empty<string>();
}
var dependencyContext = DependencyContext.Load(Assembly);
if (dependencyContext != null)
{
return dependencyContext.CompileLibraries.SelectMany(library => library.ResolveReferencePaths());
}
// If an application has been compiled without preserveCompilationContext, return the path to the assembly
// as a reference. For runtime compilation, this will allow the compilation to succeed as long as it least
// one application part has been compiled with preserveCompilationContext and contains a super set of types
// required for the compilation to succeed.
return new[] { Assembly.Location };
}
} }
} }

View File

@ -42,7 +42,6 @@ Microsoft.AspNetCore.Mvc.RouteAttribute</Description>
<Reference Include="Microsoft.AspNetCore.Routing.Abstractions" /> <Reference Include="Microsoft.AspNetCore.Routing.Abstractions" />
<Reference Include="Microsoft.AspNetCore.Routing" /> <Reference Include="Microsoft.AspNetCore.Routing" />
<Reference Include="Microsoft.Extensions.DependencyInjection" /> <Reference Include="Microsoft.Extensions.DependencyInjection" />
<Reference Include="Microsoft.Extensions.DependencyModel" />
<Reference Include="Microsoft.Extensions.FileProviders.Abstractions" /> <Reference Include="Microsoft.Extensions.FileProviders.Abstractions" />
<Reference Include="Microsoft.Extensions.Logging.Abstractions" /> <Reference Include="Microsoft.Extensions.Logging.Abstractions" />
</ItemGroup> </ItemGroup>

View File

@ -1,544 +0,0 @@
// 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.Http;
using Microsoft.Extensions.DependencyModel;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
{
public class ApplicationAssembliesProviderTest
{
private static readonly Assembly ThisAssembly = typeof(ApplicationAssembliesProviderTest).Assembly;
[Fact]
public void ResolveAssemblies_ReturnsCurrentAssembly_IfNoDepsFileIsPresent()
{
// Arrange
var provider = new TestApplicationAssembliesProvider();
// Act
var result = provider.ResolveAssemblies(ThisAssembly);
// Assert
Assert.Equal(new[] { ThisAssembly }, result);
}
[Fact]
public void ResolveAssemblies_ReturnsCurrentAssembly_IfDepsFileDoesNotHaveAnyCompileLibraries()
{
// Arrange
var runtimeLibraries = new[]
{
GetRuntimeLibrary("MyApp", "Microsoft.AspNetCore.App"),
GetRuntimeLibrary("Microsoft.AspNetCore.App", "Microsoft.NETCore.App"),
GetRuntimeLibrary("Microsoft.NETCore.App"),
GetRuntimeLibrary("ClassLibrary"),
};
var dependencyContext = GetDependencyContext(compileLibraries: Array.Empty<CompilationLibrary>(), runtimeLibraries);
var provider = new TestApplicationAssembliesProvider
{
DependencyContext = dependencyContext,
};
// Act
var result = provider.ResolveAssemblies(ThisAssembly);
// Assert
Assert.Equal(new[] { ThisAssembly }, result);
}
[Fact]
public void ResolveAssemblies_ReturnsRelatedAssembliesOrderedByName()
{
// Arrange
var assembly1 = typeof(ApplicationAssembliesProvider).Assembly;
var assembly2 = typeof(IActionResult).Assembly;
var assembly3 = typeof(FactAttribute).Assembly;
var relatedAssemblies = new[] { assembly1, assembly2, assembly3 };
var provider = new TestApplicationAssembliesProvider
{
GetRelatedAssembliesDelegate = (assembly) => relatedAssemblies,
};
// Act
var result = provider.ResolveAssemblies(ThisAssembly);
// Assert
Assert.Equal(new[] { ThisAssembly, assembly2, assembly1, assembly3 }, result);
}
[Fact]
public void ResolveAssemblies_ReturnsLibrariesFromTheDepsFileThatReferenceMvc()
{
// Arrange
var mvcAssembly = typeof(IActionResult).Assembly;
var classLibrary = typeof(FactAttribute).Assembly;
var libraries = new Dictionary<string, string[]>
{
[ThisAssembly.GetName().Name] = new[] { mvcAssembly.GetName().Name, classLibrary.GetName().Name },
[mvcAssembly.GetName().Name] = Array.Empty<string>(),
[classLibrary.GetName().Name] = new[] { mvcAssembly.GetName().Name },
};
var dependencyContext = GetDependencyContext(libraries);
var provider = new TestApplicationAssembliesProvider
{
DependencyContext = dependencyContext,
};
// Act
var result = provider.ResolveAssemblies(ThisAssembly);
// Assert
Assert.Equal(new[] { ThisAssembly, classLibrary, }, result);
}
[Fact]
public void ResolveAssemblies_ReturnsRelatedAssembliesForLibrariesFromDepsFile()
{
// Arrange
var mvcAssembly = typeof(IActionResult).Assembly;
var classLibrary = typeof(object).Assembly;
var relatedPart = typeof(FactAttribute).Assembly;
var libraries = new Dictionary<string, string[]>
{
[ThisAssembly.GetName().Name] = new[] { relatedPart.GetName().Name, classLibrary.GetName().Name },
[classLibrary.GetName().Name] = new[] { mvcAssembly.GetName().Name },
[relatedPart.GetName().Name] = new[] { mvcAssembly.GetName().Name },
[mvcAssembly.GetName().Name] = Array.Empty<string>(),
};
var dependencyContext = GetDependencyContext(libraries);
var provider = new TestApplicationAssembliesProvider
{
DependencyContext = dependencyContext,
GetRelatedAssembliesDelegate = (assembly) =>
{
if (assembly == classLibrary)
{
return new[] { relatedPart };
}
return Array.Empty<Assembly>();
},
};
// Act
var result = provider.ResolveAssemblies(ThisAssembly);
// Assert
Assert.Equal(new[] { ThisAssembly, classLibrary, relatedPart, }, result);
}
[Fact]
public void ResolveAssemblies_ThrowsIfRelatedAssemblyDefinesAdditionalRelatedAssemblies()
{
// Arrange
var expected = $"Assembly 'TestRelatedAssembly' declared as a related assembly by assembly '{ThisAssembly}' cannot define additional related assemblies.";
var assembly1 = typeof(ApplicationAssembliesProvider).Assembly;
var assembly2 = new TestAssembly();
var relatedAssemblies = new[] { assembly1, assembly2 };
var provider = new TestApplicationAssembliesProvider
{
GetRelatedAssembliesDelegate = (assembly) => relatedAssemblies,
};
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => provider.ResolveAssemblies(ThisAssembly).ToArray());
Assert.Equal(expected, ex.Message);
}
[Fact]
public void ResolveAssemblies_ThrowsIfMultipleAssembliesDeclareTheSameRelatedPart()
{
// Arrange
var mvcAssembly = typeof(IActionResult).Assembly;
var libraryAssembly1 = typeof(HttpContext).Assembly;
var libraryAssembly2 = typeof(JsonConverter).Assembly;
var relatedPart = typeof(FactAttribute).Assembly;
var expected = string.Join(
Environment.NewLine,
$"Each related assembly must be declared by exactly one assembly. The assembly '{relatedPart.FullName}' was declared as related assembly by the following:",
libraryAssembly1.FullName,
libraryAssembly2.FullName);
var libraries = new Dictionary<string, string[]>
{
[ThisAssembly.GetName().Name] = new[] { relatedPart.GetName().Name, libraryAssembly1.GetName().Name },
[libraryAssembly1.GetName().Name] = new[] { mvcAssembly.GetName().Name },
[libraryAssembly2.GetName().Name] = new[] { mvcAssembly.GetName().Name },
[mvcAssembly.GetName().Name] = Array.Empty<string>(),
};
var dependencyContext = GetDependencyContext(libraries);
var provider = new TestApplicationAssembliesProvider
{
DependencyContext = dependencyContext,
GetRelatedAssembliesDelegate = (assembly) =>
{
if (assembly == libraryAssembly1 || assembly == libraryAssembly2)
{
return new[] { relatedPart };
}
return Array.Empty<Assembly>();
},
};
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => provider.ResolveAssemblies(ThisAssembly).ToArray());
Assert.Equal(expected, ex.Message);
}
[Fact]
public void CandidateResolver_ThrowsIfDependencyContextContainsDuplicateRuntimeLibraryNames()
{
// Arrange
var upperCaseLibrary = "Microsoft.AspNetCore.Mvc";
var mixedCaseLibrary = "microsoft.aspNetCore.mvc";
var libraries = new Dictionary<string, string[]>
{
[upperCaseLibrary] = Array.Empty<string>(),
[mixedCaseLibrary] = Array.Empty<string>(),
};
var dependencyContext = GetDependencyContext(libraries);
// Act
var exception = Assert.Throws<InvalidOperationException>(() => ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext).ToArray());
// Assert
Assert.Equal($"A duplicate entry for library reference {mixedCaseLibrary} was found. Please check that all package references in all projects use the same casing for the same package references.", exception.Message);
}
[Fact]
public void GetCandidateLibraries_IgnoresMvcAssemblies()
{
// Arrange
var expected = GetRuntimeLibrary("SomeRandomAssembly", "Microsoft.AspNetCore.Mvc.Abstractions");
var runtimeLibraries = new[]
{
GetRuntimeLibrary("Microsoft.AspNetCore.Mvc.Core"),
GetRuntimeLibrary("Microsoft.AspNetCore.Mvc"),
GetRuntimeLibrary("Microsoft.AspNetCore.Mvc.Abstractions"),
expected,
};
var compileLibraries = new[]
{
GetCompileLibrary("Microsoft.AspNetCore.Mvc.Core"),
GetCompileLibrary("Microsoft.AspNetCore.Mvc"),
GetCompileLibrary("Microsoft.AspNetCore.Mvc.Abstractions"),
GetCompileLibrary("SomeRandomAssembly", "Microsoft.AspNetCore.Mvc.Abstractions"),
};
var dependencyContext = GetDependencyContext(compileLibraries, runtimeLibraries);
// Act
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext);
// Assert
Assert.Equal(new[] { expected }, candidates);
}
[Fact]
public void GetCandidateLibraries_ReturnsRuntimeLibraries_IfCompileLibraryDependencyToMvcIsPresent()
{
// Arrange
// When an app is running against Microsoft.AspNetCore.App shared runtime or if the DependencyContext is queried
// from an app that's running on Microsoft.NETCore.App (e.g. in a unit testing scenario), the
// runtime library does not state that the app references Mvc whereas the compile library does. This test validates
// that we correctly recognize this scenario.
var expected = GetRuntimeLibrary("MyApp", "Microsoft.AspNetCore.App");
var runtimeLibraries = new[]
{
expected,
GetRuntimeLibrary("Microsoft.AspNetCore.App", "Microsoft.NETCore.App"),
GetRuntimeLibrary("Microsoft.NETCore.App"),
};
var compileLibraries = new[]
{
GetCompileLibrary("MyApp", "Microsoft.AspNetCore.App"),
GetCompileLibrary("Microsoft.AspNetCore.App", "Microsoft.AspNetCore.Mvc", "Microsoft.NETCore.App"),
GetCompileLibrary("Microsoft.AspNetCore.Mvc"),
GetCompileLibrary("Microsoft.NETCore.App"),
};
var dependencyContext = GetDependencyContext(compileLibraries, runtimeLibraries);
// Act
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext);
// Assert
Assert.Equal(new[] { expected }, candidates);
}
[Fact]
public void GetCandidateLibraries_DoesNotThrow_IfLibraryDoesNotAppearAsCompileOrRuntimeLibrary()
{
// Arrange
var expected = GetRuntimeLibrary("MyApplication", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Mvc");
var compileLibraries = new[]
{
GetCompileLibrary("MyApplication", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Mvc"),
GetCompileLibrary("Microsoft.AspNetCore.Mvc"),
GetCompileLibrary("Microsoft.AspNetCore.Server.Kestrel", "Libuv"),
};
var runtimeLibraries = new[]
{
expected,
GetRuntimeLibrary("Microsoft.AspNetCore.Server.Kestrel", "Libuv"),
GetRuntimeLibrary("Microsoft.AspNetCore.Mvc"),
};
var dependencyContext = GetDependencyContext(compileLibraries, runtimeLibraries);
// Act
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext).ToList();
// Assert
Assert.Equal(new[] { expected }, candidates);
}
[Fact]
public void GetCandidateLibraries_DoesNotThrow_IfCompileLibraryIsPresentButNotRuntimeLibrary()
{
// Arrange
var expected = GetRuntimeLibrary("MyApplication", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Mvc");
var compileLibraries = new[]
{
GetCompileLibrary("MyApplication", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Mvc"),
GetCompileLibrary("Microsoft.AspNetCore.Mvc"),
GetCompileLibrary("Microsoft.AspNetCore.Server.Kestrel", "Libuv"),
GetCompileLibrary("Libuv"),
};
var runtimeLibraries = new[]
{
expected,
GetRuntimeLibrary("Microsoft.AspNetCore.Server.Kestrel", "Libuv"),
GetRuntimeLibrary("Microsoft.AspNetCore.Mvc"),
};
var dependencyContext = GetDependencyContext(compileLibraries, runtimeLibraries);
// Act
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext).ToList();
// Assert
Assert.Equal(new[] { expected }, candidates);
}
[Fact]
public void GetCandidateLibraries_ReturnsLibrariesReferencingAnyMvcAssembly()
{
// Arrange
var libraries = new Dictionary<string, string[]>
{
["Foo"] = new[] { "Microsoft.AspNetCore.Mvc.Core" },
["Bar"] = new[] { "Microsoft.AspNetCore.Mvc" },
["Qux"] = new[] { "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc" },
["Baz"] = new[] { "Microsoft.AspNetCore.Mvc.Abstractions" },
["Microsoft.AspNetCore.Mvc.Core"] = Array.Empty<string>(),
["Microsoft.AspNetCore.Mvc"] = Array.Empty<string>(),
["Not.Mvc.Assembly"] = Array.Empty<string>(),
["Unofficial.Microsoft.AspNetCore.Mvc"] = Array.Empty<string>(),
["Microsoft.AspNetCore.Mvc.Abstractions"] = Array.Empty<string>(),
};
var dependencyContext = GetDependencyContext(libraries);
// Act
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext);
// Assert
Assert.Equal(new[] { "Bar", "Baz", "Foo", }, candidates.Select(a => a.Name));
}
[Fact]
public void GetCandidateLibraries_LibraryNameComparisonsAreCaseInsensitive()
{
// Arrange
var libraries = new Dictionary<string, string[]>
{
["Foo"] = new[] { "MICROSOFT.ASPNETCORE.MVC.CORE" },
["Bar"] = new[] { "microsoft.aspnetcore.mvc" },
["Qux"] = new[] { "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc" },
["Baz"] = new[] { "mIcRoSoFt.AsPnEtCoRe.MvC.aBsTrAcTiOnS" },
["Microsoft.AspNetCore.Mvc.Core"] = Array.Empty<string>(),
["LibraryA"] = new[] { "LIBRARYB" },
["LibraryB"] = new[] { "microsoft.aspnetcore.mvc" },
["Microsoft.AspNetCore.Mvc"] = Array.Empty<string>(),
["Not.Mvc.Assembly"] = Array.Empty<string>(),
["Unofficial.Microsoft.AspNetCore.Mvc"] = Array.Empty<string>(),
["Microsoft.AspNetCore.Mvc.Abstractions"] = Array.Empty<string>(),
};
var dependencyContext = GetDependencyContext(libraries);
// Act
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext);
// Assert
Assert.Equal(new[] { "Bar", "Baz", "Foo", "LibraryA", "LibraryB" }, candidates.Select(a => a.Name));
}
[Fact]
public void GetCandidateLibraries_ReturnsLibrariesWithTransitiveReferencesToAnyMvcAssembly()
{
// Arrange
var expectedLibraries = new[] { "Bar", "Baz", "Foo", "LibraryA", "LibraryB", "LibraryC", "LibraryE", "LibraryG", "LibraryH" };
var libraries = new Dictionary<string, string[]>
{
["Foo"] = new[] { "Bar" },
["Bar"] = new[] { "Microsoft.AspNetCore.Mvc" },
["Qux"] = new[] { "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc" },
["Baz"] = new[] { "Microsoft.AspNetCore.Mvc.Abstractions" },
["Microsoft.AspNetCore.Mvc"] = Array.Empty<string>(),
["Not.Mvc.Assembly"] = Array.Empty<string>(),
["Microsoft.AspNetCore.Mvc.Abstractions"] = Array.Empty<string>(),
["Unofficial.Microsoft.AspNetCore.Mvc"] = Array.Empty<string>(),
["LibraryA"] = new[] { "LibraryB" },
["LibraryB"] = new[] { "LibraryC" },
["LibraryC"] = new[] { "LibraryD", "Microsoft.AspNetCore.Mvc.Abstractions" },
["LibraryD"] = Array.Empty<string>(),
["LibraryE"] = new[] { "LibraryF", "LibraryG" },
["LibraryF"] = Array.Empty<string>(),
["LibraryG"] = new[] { "LibraryH" },
["LibraryH"] = new[] { "LibraryI", "Microsoft.AspNetCore.Mvc" },
["LibraryI"] = Array.Empty<string>(),
};
var dependencyContext = GetDependencyContext(libraries);
// Act
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext);
// Assert
Assert.Equal(expectedLibraries, candidates.Select(a => a.Name));
}
[Fact]
public void GetCandidateLibraries_SkipsMvcAssemblies()
{
// Arrange
var libraries = new Dictionary<string, string[]>
{
["MvcSandbox"] = new[] { "Microsoft.AspNetCore.Mvc.Core", "Microsoft.AspNetCore.Mvc" },
["Microsoft.AspNetCore.Mvc.Core"] = new[] { "Microsoft.AspNetCore.HttpAbstractions" },
["Microsoft.AspNetCore.HttpAbstractions"] = Array.Empty<string>(),
["Microsoft.AspNetCore.Mvc"] = new[] { "Microsoft.AspNetCore.Mvc.Abstractions", "Microsoft.AspNetCore.Mvc.Core" },
["Microsoft.AspNetCore.Mvc.Abstractions"] = Array.Empty<string>(),
["Microsoft.AspNetCore.Mvc.TagHelpers"] = new[] { "Microsoft.AspNetCore.Mvc.Razor" },
["Microsoft.AspNetCore.Mvc.Razor"] = Array.Empty<string>(),
["ControllersAssembly"] = new[] { "Microsoft.AspNetCore.Mvc" },
};
var dependencyContext = GetDependencyContext(libraries);
// Act
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext);
// Assert
Assert.Equal(new[] { "ControllersAssembly", "MvcSandbox" }, candidates.Select(a => a.Name));
}
private class TestApplicationAssembliesProvider : ApplicationAssembliesProvider
{
public DependencyContext DependencyContext { get; set; }
public Func<Assembly, IReadOnlyList<Assembly>> GetRelatedAssembliesDelegate { get; set; } = (assembly) => Array.Empty<Assembly>();
protected override DependencyContext LoadDependencyContext(Assembly assembly) => DependencyContext;
protected override IReadOnlyList<Assembly> GetRelatedAssemblies(Assembly assembly) => GetRelatedAssembliesDelegate(assembly);
protected override IEnumerable<Assembly> GetLibraryAssemblies(DependencyContext dependencyContext, RuntimeLibrary runtimeLibrary)
{
var assemblyName = new AssemblyName(runtimeLibrary.Name);
yield return Assembly.Load(assemblyName);
}
}
private static DependencyContext GetDependencyContext(IDictionary<string, string[]> libraries)
{
var compileLibraries = new List<CompilationLibrary>();
var runtimeLibraries = new List<RuntimeLibrary>();
foreach (var kvp in libraries.OrderBy(kvp => kvp.Key, StringComparer.Ordinal))
{
var compileLibrary = GetCompileLibrary(kvp.Key, kvp.Value);
compileLibraries.Add(compileLibrary);
var runtimeLibrary = GetRuntimeLibrary(kvp.Key, kvp.Value);
runtimeLibraries.Add(runtimeLibrary);
}
return GetDependencyContext(compileLibraries, runtimeLibraries);
}
private static DependencyContext GetDependencyContext(
IReadOnlyList<CompilationLibrary> compileLibraries,
IReadOnlyList<RuntimeLibrary> runtimeLibraries)
{
var dependencyContext = new DependencyContext(
new TargetInfo("framework", "runtime", "signature", isPortable: true),
CompilationOptions.Default,
compileLibraries,
runtimeLibraries,
Enumerable.Empty<RuntimeFallbacks>());
return dependencyContext;
}
private static RuntimeLibrary GetRuntimeLibrary(string name, params string[] dependencyNames)
{
var dependencies = dependencyNames?.Select(d => new Dependency(d, "42.0.0")) ?? new Dependency[0];
return new RuntimeLibrary(
"package",
name,
"23.0.0",
"hash",
new RuntimeAssetGroup[0],
new RuntimeAssetGroup[0],
new ResourceAssembly[0],
dependencies: dependencies.ToArray(),
serviceable: true);
}
private static CompilationLibrary GetCompileLibrary(string name, params string[] dependencyNames)
{
var dependencies = dependencyNames?.Select(d => new Dependency(d, "42.0.0")) ?? new Dependency[0];
return new CompilationLibrary(
"package",
name,
"23.0.0",
"hash",
Enumerable.Empty<string>(),
dependencies: dependencies.ToArray(),
serviceable: true);
}
private class TestAssembly : Assembly
{
public override string FullName => "TestRelatedAssembly";
public override bool IsDefined(Type attributeType, bool inherit)
{
return attributeType == typeof(RelatedAssemblyAttribute);
}
}
}
}

View File

@ -50,55 +50,5 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
// Act & Assert // Act & Assert
Assert.Equal(part.Assembly, assembly); Assert.Equal(part.Assembly, assembly);
} }
[Fact]
public void GetReferencePaths_ReturnsReferencesFromDependencyContext_IfPreserveCompilationContextIsSet()
{
// Arrange
var assembly = GetType().GetTypeInfo().Assembly;
var part = new AssemblyPart(assembly);
// Act
var references = part.GetReferencePaths().ToList();
// Assert
Assert.Contains(assembly.Location, references);
Assert.Contains(
typeof(AssemblyPart).GetTypeInfo().Assembly.GetName().Name,
references.Select(Path.GetFileNameWithoutExtension));
}
[Fact]
public void GetReferencePaths_ReturnsAssemblyLocation_IfPreserveCompilationContextIsNotSet()
{
// Arrange
// src projects do not have preserveCompilationContext specified.
var assembly = typeof(AssemblyPart).GetTypeInfo().Assembly;
var part = new AssemblyPart(assembly);
// Act
var references = part.GetReferencePaths().ToList();
// Assert
var actual = Assert.Single(references);
Assert.Equal(assembly.Location, actual);
}
[Fact]
public void GetReferencePaths_ReturnsEmptySequenceForDynamicAssembly()
{
// Arrange
var name = new AssemblyName($"DynamicAssembly-{Guid.NewGuid()}");
var assembly = AssemblyBuilder.DefineDynamicAssembly(name,
AssemblyBuilderAccess.RunAndCollect);
var part = new AssemblyPart(assembly);
// Act
var references = part.GetReferencePaths().ToList();
// Assert
Assert.Empty(references);
}
} }
} }

View File

@ -1,8 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework> <TargetFramework>netcoreapp3.0</TargetFramework>
<PreserveCompilationContext>true</PreserveCompilationContext>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -9,5 +9,6 @@
<Reference Include="Microsoft.AspNetCore.Mvc.RazorPages" /> <Reference Include="Microsoft.AspNetCore.Mvc.RazorPages" />
<Reference Include="Microsoft.AspNetCore.Razor.Runtime" /> <Reference Include="Microsoft.AspNetCore.Razor.Runtime" />
<Reference Include="Microsoft.CodeAnalysis.Razor" /> <Reference Include="Microsoft.CodeAnalysis.Razor" />
<Reference Include="Microsoft.Extensions.DependencyModel" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,6 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
{
public static partial class AssemblyPartExtensions
{
public static System.Collections.Generic.IEnumerable<string> GetReferencePaths(this Microsoft.AspNetCore.Mvc.ApplicationParts.AssemblyPart assemblyPart) { throw null; }
}
}
namespace Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation namespace Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation
{ {
public partial class FileProviderRazorProjectItem : Microsoft.AspNetCore.Razor.Language.RazorProjectItem public partial class FileProviderRazorProjectItem : Microsoft.AspNetCore.Razor.Language.RazorProjectItem

View File

@ -0,0 +1,38 @@
// 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 Microsoft.Extensions.DependencyModel;
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
{
public static class AssemblyPartExtensions
{
/// <inheritdoc />
public static IEnumerable<string> GetReferencePaths(this AssemblyPart assemblyPart)
{
var assembly = assemblyPart?.Assembly ?? throw new ArgumentNullException(nameof(assemblyPart));
if (assembly.IsDynamic)
{
// Skip loading process for dynamic assemblies. This prevents DependencyContextLoader from reading the
// .deps.json file from either manifest resources or the assembly location, which will fail.
return Enumerable.Empty<string>();
}
var dependencyContext = DependencyContext.Load(assembly);
if (dependencyContext != null)
{
return dependencyContext.CompileLibraries.SelectMany(library => library.ResolveReferencePaths());
}
// If an application has been compiled without preserveCompilationContext, return the path to the assembly
// as a reference. For runtime compilation, this will allow the compilation to succeed as long as it least
// one application part has been compiled with preserveCompilationContext and contains a super set of types
// required for the compilation to succeed.
return new[] { assembly.Location };
}
}
}

View File

@ -14,6 +14,7 @@
<Reference Include="Microsoft.AspNetCore.Mvc.RazorPages" /> <Reference Include="Microsoft.AspNetCore.Mvc.RazorPages" />
<Reference Include="Microsoft.AspNetCore.Razor.Runtime" /> <Reference Include="Microsoft.AspNetCore.Razor.Runtime" />
<Reference Include="Microsoft.CodeAnalysis.Razor" /> <Reference Include="Microsoft.CodeAnalysis.Razor" />
<Reference Include="Microsoft.Extensions.DependencyModel" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -53,14 +52,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation
// For unit testing // For unit testing
internal IEnumerable<string> GetReferencePaths() internal IEnumerable<string> GetReferencePaths()
{ {
var referencesFromApplicationParts = _partManager var referencePaths = new List<string>();
.ApplicationParts
.OfType<ICompilationReferencesProvider>()
.SelectMany(part => part.GetReferencePaths());
var referencePaths = referencesFromApplicationParts foreach (var part in _partManager.ApplicationParts)
.Concat(_options.AdditionalReferencePaths) {
.Distinct(StringComparer.OrdinalIgnoreCase); if (part is ICompilationReferencesProvider compilationReferenceProvider)
{
referencePaths.AddRange(compilationReferenceProvider.GetReferencePaths());
}
else if (part is AssemblyPart assemblyPart)
{
referencePaths.AddRange(assemblyPart.GetReferencePaths());
}
}
referencePaths.AddRange(_options.AdditionalReferencePaths);
return referencePaths; return referencePaths;
} }

View File

@ -0,0 +1,65 @@
// 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.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
{
public class AssemblyPartExtensionTest
{
[Fact]
public void GetReferencePaths_ReturnsReferencesFromDependencyContext_IfPreserveCompilationContextIsSet()
{
// Arrange
var assembly = GetType().GetTypeInfo().Assembly;
var part = new AssemblyPart(assembly);
// Act
var references = part.GetReferencePaths().ToList();
// Assert
Assert.Contains(assembly.Location, references);
Assert.Contains(
typeof(AssemblyPart).GetTypeInfo().Assembly.GetName().Name,
references.Select(Path.GetFileNameWithoutExtension));
}
[Fact]
public void GetReferencePaths_ReturnsAssemblyLocation_IfPreserveCompilationContextIsNotSet()
{
// Arrange
// src projects do not have preserveCompilationContext specified.
var assembly = typeof(AssemblyPart).GetTypeInfo().Assembly;
var part = new AssemblyPart(assembly);
// Act
var references = part.GetReferencePaths().ToList();
// Assert
var actual = Assert.Single(references);
Assert.Equal(assembly.Location, actual);
}
[Fact]
public void GetReferencePaths_ReturnsEmptySequenceForDynamicAssembly()
{
// Arrange
var name = new AssemblyName($"DynamicAssembly-{Guid.NewGuid()}");
var assembly = AssemblyBuilder.DefineDynamicAssembly(name,
AssemblyBuilderAccess.RunAndCollect);
var part = new AssemblyPart(assembly);
// Act
var references = part.GetReferencePaths().ToList();
// Assert
Assert.Empty(references);
}
}
}

View File

@ -8,6 +8,7 @@
<Reference Include="Microsoft.AspNetCore.TestHost" /> <Reference Include="Microsoft.AspNetCore.TestHost" />
<Reference Include="Microsoft.AspNetCore.Mvc.Core" /> <Reference Include="Microsoft.AspNetCore.Mvc.Core" />
<Reference Include="Microsoft.Extensions.HostFactoryResolver.Sources" /> <Reference Include="Microsoft.Extensions.HostFactoryResolver.Sources" />
<Reference Include="Microsoft.Extensions.DependencyModel" />
<Reference Include="Microsoft.Extensions.Hosting" /> <Reference Include="Microsoft.Extensions.Hosting" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -13,6 +13,7 @@
<Reference Include="Microsoft.AspNetCore.TestHost" /> <Reference Include="Microsoft.AspNetCore.TestHost" />
<Reference Include="Microsoft.AspNetCore.Mvc.Core" /> <Reference Include="Microsoft.AspNetCore.Mvc.Core" />
<Reference Include="Microsoft.Extensions.HostFactoryResolver.Sources" /> <Reference Include="Microsoft.Extensions.HostFactoryResolver.Sources" />
<Reference Include="Microsoft.Extensions.DependencyModel" />
<Reference Include="Microsoft.Extensions.Hosting" /> <Reference Include="Microsoft.Extensions.Hosting" />
</ItemGroup> </ItemGroup>

View File

@ -1,58 +0,0 @@
// 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.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyModel;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
{
public class ApplicationAssembliesProviderTest
{
private static readonly Assembly ThisAssembly = typeof(ApplicationAssembliesProviderTest).Assembly;
// This test verifies ApplicationAssembliesProviderTest.ReferenceAssemblies reflects the actual loadable assemblies
// of the libraries that Microsoft.AspNetCore.Mvc depends on.
// If we add or remove dependencies, this test should be changed together.
[Fact]
public void ReferenceAssemblies_ReturnsLoadableReferenceAssemblies()
{
// Arrange
var excludeAssemblies = new string[]
{
"Microsoft.AspNetCore.Mvc.Analyzers",
"Microsoft.AspNetCore.Mvc.Test",
"Microsoft.AspNetCore.Mvc.Core.TestCommon",
};
var additionalAssemblies = new[]
{
// The following assemblies are not reachable from Microsoft.AspNetCore.Mvc
"Microsoft.AspNetCore.App",
"Microsoft.AspNetCore.Mvc.NewtonsoftJson",
"Microsoft.AspNetCore.Mvc.Formatters.Xml",
};
var dependencyContextLibraries = DependencyContext.Load(ThisAssembly)
.CompileLibraries
.Where(r => r.Name.StartsWith("Microsoft.AspNetCore.Mvc", StringComparison.OrdinalIgnoreCase) &&
!excludeAssemblies.Contains(r.Name, StringComparer.OrdinalIgnoreCase))
.Select(r => r.Name);
var expected = dependencyContextLibraries
.Concat(additionalAssemblies)
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(p => p, StringComparer.OrdinalIgnoreCase);
// Act
var referenceAssemblies = ApplicationAssembliesProvider
.ReferenceAssemblies
.OrderBy(p => p, StringComparer.OrdinalIgnoreCase);
// Assert
Assert.Equal(expected, referenceAssemblies, StringComparer.OrdinalIgnoreCase);
}
}
}

View File

@ -0,0 +1,7 @@
// 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 Microsoft.AspNetCore.Mvc.ApplicationParts;
// This will eventually be codegened, but it's fine for duplicates to be present
[assembly: ApplicationPart("RazorPagesClassLibrary")]