ApplicationPartFactory: The works
This commit is contained in:
parent
390ebbb258
commit
d6176ac7de
|
|
@ -4,7 +4,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Label="Package Versions">
|
<PropertyGroup Label="Package Versions">
|
||||||
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
|
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
|
||||||
<InternalAspNetCoreSdkPackageVersion>2.1.0-preview2-15742</InternalAspNetCoreSdkPackageVersion>
|
<InternalAspNetCoreSdkPackageVersion>2.1.0-preview2-15744</InternalAspNetCoreSdkPackageVersion>
|
||||||
<MicrosoftAspNetCoreAntiforgeryPackageVersion>2.1.0-preview2-30355</MicrosoftAspNetCoreAntiforgeryPackageVersion>
|
<MicrosoftAspNetCoreAntiforgeryPackageVersion>2.1.0-preview2-30355</MicrosoftAspNetCoreAntiforgeryPackageVersion>
|
||||||
<MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>2.1.0-preview2-30355</MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>
|
<MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>2.1.0-preview2-30355</MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>
|
||||||
<MicrosoftAspNetCoreAuthenticationCorePackageVersion>2.1.0-preview2-30355</MicrosoftAspNetCoreAuthenticationCorePackageVersion>
|
<MicrosoftAspNetCoreAuthenticationCorePackageVersion>2.1.0-preview2-30355</MicrosoftAspNetCoreAuthenticationCorePackageVersion>
|
||||||
|
|
@ -75,6 +75,7 @@
|
||||||
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
|
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
|
||||||
<MicrosoftNETCoreApp21PackageVersion>2.1.0-preview2-26314-02</MicrosoftNETCoreApp21PackageVersion>
|
<MicrosoftNETCoreApp21PackageVersion>2.1.0-preview2-26314-02</MicrosoftNETCoreApp21PackageVersion>
|
||||||
<MicrosoftNetHttpHeadersPackageVersion>2.1.0-preview2-30355</MicrosoftNetHttpHeadersPackageVersion>
|
<MicrosoftNetHttpHeadersPackageVersion>2.1.0-preview2-30355</MicrosoftNetHttpHeadersPackageVersion>
|
||||||
|
<MicrosoftNETSdkRazorPackageVersion>2.1.0-preview2-30355</MicrosoftNETSdkRazorPackageVersion>
|
||||||
<MicrosoftNETTestSdkPackageVersion>15.6.0</MicrosoftNETTestSdkPackageVersion>
|
<MicrosoftNETTestSdkPackageVersion>15.6.0</MicrosoftNETTestSdkPackageVersion>
|
||||||
<MoqPackageVersion>4.7.49</MoqPackageVersion>
|
<MoqPackageVersion>4.7.49</MoqPackageVersion>
|
||||||
<NewtonsoftJsonBsonPackageVersion>1.0.1</NewtonsoftJsonBsonPackageVersion>
|
<NewtonsoftJsonBsonPackageVersion>1.0.1</NewtonsoftJsonBsonPackageVersion>
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
version:2.1.0-preview2-15742
|
version:2.1.0-preview2-15744
|
||||||
commithash:21fbb0f2c3fe4a9216e2d59632b98cfd7d685962
|
commithash:9e15cb6062ab5b9790d3fa699e018543a6950713
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="$(MicrosoftExtensionsConfigurationJsonPackageVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="$(MicrosoftExtensionsConfigurationJsonPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(MicrosoftExtensionsLoggingDebugPackageVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(MicrosoftExtensionsLoggingDebugPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Sdk.Razor" Version="$(MicrosoftNETSdkRazorPackageVersion)" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,25 +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.Reflection;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An <see cref="AssemblyPart"/> that was added by an assembly that referenced it through the use
|
|
||||||
/// of an assembly metadata attribute.
|
|
||||||
/// </summary>
|
|
||||||
public class AdditionalAssemblyPart : AssemblyPart, ICompilationReferencesProvider, IApplicationPartTypeProvider
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public AdditionalAssemblyPart(Assembly assembly) : base(assembly)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<string> ICompilationReferencesProvider.GetReferencePaths() => Array.Empty<string>();
|
|
||||||
|
|
||||||
IEnumerable<TypeInfo> IApplicationPartTypeProvider.Types => Array.Empty<TypeInfo>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,283 @@
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
"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",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
var candidatesResolver = new CandidateResolver(dependencyContext.RuntimeLibraries, ReferenceAssemblies);
|
||||||
|
return candidatesResolver.GetCandidates();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CandidateResolver
|
||||||
|
{
|
||||||
|
private readonly IDictionary<string, Dependency> _runtimeDependencies;
|
||||||
|
|
||||||
|
public CandidateResolver(IReadOnlyList<RuntimeLibrary> runtimeDependencies, ISet<string> referenceAssemblies)
|
||||||
|
{
|
||||||
|
var dependenciesWithNoDuplicates = new Dictionary<string, Dependency>(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<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 (!_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<RuntimeLibrary> 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,
|
||||||
|
|
||||||
|
/// <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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
// 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.Reflection;
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Core;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||||
{
|
{
|
||||||
|
|
@ -19,6 +21,11 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||||
{
|
{
|
||||||
public static readonly string DefaultContextName = "Default";
|
public static readonly string DefaultContextName = "Default";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default implementation for <see cref="ApplicationPartFactory"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static ApplicationPartFactory Default { get; } = new DefaultApplicationPartFactory();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets one or more <see cref="ApplicationPart"/> instances for the specified <paramref name="assembly"/>.
|
/// Gets one or more <see cref="ApplicationPart"/> instances for the specified <paramref name="assembly"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -27,5 +34,52 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||||
/// The context name. By default, value of this parameter is <see cref="DefaultContextName"/>.
|
/// The context name. By default, value of this parameter is <see cref="DefaultContextName"/>.
|
||||||
/// </param>
|
/// </param>
|
||||||
public abstract IEnumerable<ApplicationPart> GetApplicationParts(Assembly assembly, string context);
|
public abstract IEnumerable<ApplicationPart> GetApplicationParts(Assembly assembly, string context);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="ApplicationPartFactory"/> for the specified assembly.
|
||||||
|
/// <para>
|
||||||
|
/// An assembly may specify an <see cref="ApplicationPartFactory"/> using <see cref="ProvideApplicationPartFactoryAttribute"/>.
|
||||||
|
/// Otherwise, <see cref="ApplicationPartFactory.Default"/> is used.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assembly">The <see cref="Assembly"/>.</param>
|
||||||
|
/// <returns>An instance of <see cref="ApplicationPartFactory"/>.</returns>
|
||||||
|
public static ApplicationPartFactory GetApplicationPartFactory(Assembly assembly)
|
||||||
|
{
|
||||||
|
if (assembly == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(assembly));
|
||||||
|
}
|
||||||
|
|
||||||
|
var provideAttribute = assembly.GetCustomAttribute<ProvideApplicationPartFactoryAttribute>();
|
||||||
|
if (provideAttribute == null)
|
||||||
|
{
|
||||||
|
return ApplicationPartFactory.Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
var type = provideAttribute.GetFactoryType();
|
||||||
|
if (!typeof(ApplicationPartFactory).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.FormatApplicationPartFactory_InvalidFactoryType(
|
||||||
|
type,
|
||||||
|
nameof(ProvideApplicationPartFactoryAttribute),
|
||||||
|
typeof(ApplicationPartFactory)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ApplicationPartFactory)Activator.CreateInstance(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DefaultApplicationPartFactory : ApplicationPartFactory
|
||||||
|
{
|
||||||
|
public override IEnumerable<ApplicationPart> GetApplicationParts(Assembly assembly, string context)
|
||||||
|
{
|
||||||
|
if (assembly == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(assembly));
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new AssemblyPart(assembly);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||||
{
|
{
|
||||||
|
|
@ -48,5 +49,21 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||||
provider.PopulateFeature(ApplicationParts, feature);
|
provider.PopulateFeature(ApplicationParts, feature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void PopulateDefaultParts(string entryAssemblyName)
|
||||||
|
{
|
||||||
|
var entryAssembly = Assembly.Load(new AssemblyName(entryAssemblyName));
|
||||||
|
var assembliesProvider = new ApplicationAssembliesProvider();
|
||||||
|
var applicationAssemblies = assembliesProvider.ResolveAssemblies(entryAssembly);
|
||||||
|
|
||||||
|
foreach (var assembly in applicationAssemblies)
|
||||||
|
{
|
||||||
|
var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly);
|
||||||
|
foreach (var part in partFactory.GetApplicationParts(assembly, context: ApplicationPartFactory.DefaultContextName))
|
||||||
|
{
|
||||||
|
ApplicationParts.Add(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="ApplicationPartFactory"/> that produces no parts.
|
||||||
|
/// <para>
|
||||||
|
/// This factory may be used to to preempt Mvc's default part discovery allowing for custom configuration at a later stage.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public class NullApplicationPartFactory : ApplicationPartFactory
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IEnumerable<ApplicationPart> GetApplicationParts(Assembly assembly, string context)
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<ApplicationPart>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// 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;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Core;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||||
{
|
{
|
||||||
|
|
@ -11,15 +12,40 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
|
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
|
||||||
public sealed class ProvideApplicationPartFactoryAttribute : Attribute
|
public sealed class ProvideApplicationPartFactoryAttribute : Attribute
|
||||||
{
|
{
|
||||||
|
private readonly Type _applicationPartFactoryType;
|
||||||
|
private readonly string _applicationPartFactoryTypeName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="ProvideApplicationPartFactoryAttribute"/> with the specified type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="factoryType">The factory type.</param>
|
||||||
public ProvideApplicationPartFactoryAttribute(Type factoryType)
|
public ProvideApplicationPartFactoryAttribute(Type factoryType)
|
||||||
{
|
{
|
||||||
ApplicationPartFactoryType = factoryType ?? throw new ArgumentNullException(nameof(factoryType));
|
_applicationPartFactoryType = factoryType ?? throw new ArgumentNullException(nameof(factoryType));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="ProvideApplicationPartFactoryAttribute"/> with the specified type name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="factoryTypeName">The assembly qualified type name.</param>
|
||||||
public ProvideApplicationPartFactoryAttribute(string factoryTypeName)
|
public ProvideApplicationPartFactoryAttribute(string factoryTypeName)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrEmpty(factoryTypeName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(factoryTypeName));
|
||||||
|
}
|
||||||
|
|
||||||
|
_applicationPartFactoryTypeName = factoryTypeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Type ApplicationPartFactoryType { get; }
|
/// <summary>
|
||||||
|
/// Gets the factory type.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Type GetFactoryType()
|
||||||
|
{
|
||||||
|
return _applicationPartFactoryType ??
|
||||||
|
Type.GetType(_applicationPartFactoryTypeName, throwOnError: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,110 @@
|
||||||
// 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using Microsoft.AspNetCore.Mvc.Core;
|
using Microsoft.AspNetCore.Mvc.Core;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies a assembly to load as part of MVC's assembly discovery mechanism.
|
||||||
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
|
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
|
||||||
public sealed class RelatedAssemblyAttribute : Attribute
|
public sealed class RelatedAssemblyAttribute : Attribute
|
||||||
{
|
{
|
||||||
public RelatedAssemblyAttribute(string name)
|
private static readonly Func<string, Assembly> AssemblyLoadFileDelegate = Assembly.LoadFile;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of <see cref="RelatedAssemblyAttribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assemblyFileName">The file name, without extension, of the related assembly.</param>
|
||||||
|
public RelatedAssemblyAttribute(string assemblyFileName)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(name))
|
if (string.IsNullOrEmpty(assemblyFileName))
|
||||||
{
|
{
|
||||||
// Temporary workaround until we have a new build of RazorSdk.
|
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(assemblyFileName));
|
||||||
// TODO: Uncomment the below line.
|
|
||||||
// throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(name));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AssemblyFileName = assemblyFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name { get; }
|
/// <summary>
|
||||||
|
/// Gets the assembly file name without extension.
|
||||||
|
/// </summary>
|
||||||
|
public string AssemblyFileName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets <see cref="Assembly"/> instances specified by <see cref="RelatedAssemblyAttribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assembly">The assembly containing <see cref="RelatedAssemblyAttribute"/> instances.</param>
|
||||||
|
/// <param name="throwOnError">Determines if the method throws if a related assembly could not be located.</param>
|
||||||
|
/// <returns>Related <see cref="Assembly"/> instances.</returns>
|
||||||
|
public static IReadOnlyList<Assembly> GetRelatedAssemblies(Assembly assembly, bool throwOnError)
|
||||||
|
{
|
||||||
|
if (assembly == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(assembly));
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetRelatedAssemblies(assembly, throwOnError, AssemblyLoadFileDelegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static IReadOnlyList<Assembly> GetRelatedAssemblies(Assembly assembly, bool throwOnError, Func<string, Assembly> loadFile)
|
||||||
|
{
|
||||||
|
if (assembly == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(assembly));
|
||||||
|
}
|
||||||
|
|
||||||
|
// MVC will specifically look for related parts in the same physical directory as the assembly.
|
||||||
|
// No-op if the assembly does not have a location.
|
||||||
|
if (assembly.IsDynamic || string.IsNullOrEmpty(assembly.CodeBase))
|
||||||
|
{
|
||||||
|
return Array.Empty<Assembly>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributes = assembly.GetCustomAttributes<RelatedAssemblyAttribute>().ToArray();
|
||||||
|
if (attributes.Length == 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<Assembly>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var assemblyName = assembly.GetName().Name;
|
||||||
|
var assemblyDirectory = Path.GetDirectoryName(assembly.CodeBase);
|
||||||
|
|
||||||
|
var relatedAssemblies = new List<Assembly>();
|
||||||
|
for (var i = 0; i < attributes.Length; i++)
|
||||||
|
{
|
||||||
|
var attribute = attributes[i];
|
||||||
|
if (string.Equals(assemblyName, attribute.AssemblyFileName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatRelatedAssemblyAttribute_AssemblyCannotReferenceSelf(nameof(RelatedAssemblyAttribute), assemblyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
var relatedAssemblyLocation = Path.Combine(assemblyDirectory, attribute.AssemblyFileName + ".dll");
|
||||||
|
if (!File.Exists(relatedAssemblyLocation))
|
||||||
|
{
|
||||||
|
if (throwOnError)
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException(
|
||||||
|
Resources.FormatRelatedAssemblyAttribute_CouldNotBeFound(attribute.AssemblyFileName, assemblyName, assemblyDirectory),
|
||||||
|
relatedAssemblyLocation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var relatedAssembly = loadFile(relatedAssemblyLocation);
|
||||||
|
relatedAssemblies.Add(relatedAssembly);
|
||||||
|
}
|
||||||
|
|
||||||
|
return relatedAssemblies;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,11 +87,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
return manager;
|
return manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
var parts = DefaultAssemblyPartDiscoveryProvider.DiscoverAssemblyParts(entryAssemblyName);
|
manager.PopulateDefaultParts(entryAssemblyName);
|
||||||
foreach (var part in parts)
|
|
||||||
{
|
|
||||||
manager.ApplicationParts.Add(part);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager;
|
return manager;
|
||||||
|
|
|
||||||
|
|
@ -1,406 +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.IO;
|
|
||||||
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
|
|
||||||
{
|
|
||||||
private static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews";
|
|
||||||
private static readonly IReadOnlyList<string> ViewAssemblySuffixes = new string[]
|
|
||||||
{
|
|
||||||
PrecompiledViewsAssemblySuffix,
|
|
||||||
".Views",
|
|
||||||
};
|
|
||||||
|
|
||||||
private const string AdditionalReferenceKey = "Microsoft.AspNetCore.Mvc.AdditionalReference";
|
|
||||||
private static readonly char[] MetadataSeparators = new[] { ',' };
|
|
||||||
|
|
||||||
internal static HashSet<string> ReferenceAssemblies { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
|
||||||
{
|
|
||||||
"Microsoft.AspNetCore.All",
|
|
||||||
"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"
|
|
||||||
};
|
|
||||||
|
|
||||||
// For testing purposes only.
|
|
||||||
internal static Func<string, Assembly> AssemblyLoader { get; set; } = Assembly.LoadFile;
|
|
||||||
internal static Func<string, bool> AssemblyResolver { get; set; } = File.Exists;
|
|
||||||
|
|
||||||
public static IEnumerable<ApplicationPart> DiscoverAssemblyParts(string entryPointAssemblyName)
|
|
||||||
{
|
|
||||||
// We need to produce a stable order of the parts that is given by:
|
|
||||||
// 1) Parts that are not additional parts go before parts that are additional parts.
|
|
||||||
// 2) The entry point part goes before any other part in the system.
|
|
||||||
// 3) The entry point additional parts go before any other additional parts.
|
|
||||||
// 4) Parts are finally ordered by their full name to produce a stable ordering.
|
|
||||||
var entryAssembly = Assembly.Load(new AssemblyName(entryPointAssemblyName));
|
|
||||||
var context = DependencyContext.Load(entryAssembly);
|
|
||||||
|
|
||||||
var candidateAssemblies = new SortedSet<Assembly>(
|
|
||||||
GetCandidateAssemblies(entryAssembly, context),
|
|
||||||
FullNameAssemblyComparer.Instance);
|
|
||||||
|
|
||||||
var (additionalReferences, entryAssemblyAdditionalReferences) = ResolveAdditionalReferences(
|
|
||||||
entryAssembly,
|
|
||||||
candidateAssemblies);
|
|
||||||
|
|
||||||
candidateAssemblies.Remove(entryAssembly);
|
|
||||||
candidateAssemblies.ExceptWith(additionalReferences);
|
|
||||||
candidateAssemblies.ExceptWith(entryAssemblyAdditionalReferences);
|
|
||||||
|
|
||||||
// Create the list of assembly parts.
|
|
||||||
return CreateParts();
|
|
||||||
|
|
||||||
IEnumerable<AssemblyPart> CreateParts()
|
|
||||||
{
|
|
||||||
yield return new AssemblyPart(entryAssembly);
|
|
||||||
foreach (var candidateAssembly in candidateAssemblies)
|
|
||||||
{
|
|
||||||
yield return new AssemblyPart(candidateAssembly);
|
|
||||||
}
|
|
||||||
foreach (var entryAdditionalAssembly in entryAssemblyAdditionalReferences)
|
|
||||||
{
|
|
||||||
yield return new AdditionalAssemblyPart(entryAdditionalAssembly);
|
|
||||||
}
|
|
||||||
foreach (var additionalAssembly in additionalReferences)
|
|
||||||
{
|
|
||||||
yield return new AdditionalAssemblyPart(additionalAssembly);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static AdditionalReferencesPair ResolveAdditionalReferences(
|
|
||||||
Assembly entryAssembly,
|
|
||||||
SortedSet<Assembly> candidateAssemblies)
|
|
||||||
{
|
|
||||||
var additionalAssemblyReferences = candidateAssemblies
|
|
||||||
.Select(ca =>
|
|
||||||
(assembly: ca,
|
|
||||||
metadata: ca.GetCustomAttributes<AssemblyMetadataAttribute>()
|
|
||||||
.Where(ama => ama.Key.Equals(AdditionalReferenceKey, StringComparison.Ordinal)).ToArray()));
|
|
||||||
|
|
||||||
// Find out all the additional references defined by the assembly.
|
|
||||||
// [assembly: AssemblyMetadataAttribute("Microsoft.AspNetCore.Mvc.AdditionalReference", "Library.PrecompiledViews.dll,true|false")]
|
|
||||||
var additionalReferences = new SortedSet<Assembly>(FullNameAssemblyComparer.Instance);
|
|
||||||
var entryAssemblyAdditionalReferences = new SortedSet<Assembly>(FullNameAssemblyComparer.Instance);
|
|
||||||
foreach (var (assembly, metadata) in additionalAssemblyReferences)
|
|
||||||
{
|
|
||||||
if (metadata.Length > 0)
|
|
||||||
{
|
|
||||||
foreach (var metadataAttribute in metadata)
|
|
||||||
{
|
|
||||||
AddAdditionalReference(
|
|
||||||
LoadFromMetadata(assembly, metadataAttribute),
|
|
||||||
entryAssembly,
|
|
||||||
assembly,
|
|
||||||
additionalReferences,
|
|
||||||
entryAssemblyAdditionalReferences);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Fallback to loading the views like in previous versions if the additional reference metadata
|
|
||||||
// attribute is not present.
|
|
||||||
AddAdditionalReference(
|
|
||||||
LoadFromConvention(assembly),
|
|
||||||
entryAssembly,
|
|
||||||
assembly,
|
|
||||||
additionalReferences,
|
|
||||||
entryAssemblyAdditionalReferences);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AdditionalReferencesPair
|
|
||||||
{
|
|
||||||
AdditionalAssemblies = additionalReferences,
|
|
||||||
EntryAssemblyAdditionalAssemblies = entryAssemblyAdditionalReferences
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Assembly LoadFromMetadata(Assembly assembly, AssemblyMetadataAttribute metadataAttribute)
|
|
||||||
{
|
|
||||||
var (metadataPath, metadataIncludeByDefault) = ParseMetadataAttribute(metadataAttribute);
|
|
||||||
if (metadataPath == null ||
|
|
||||||
metadataIncludeByDefault == null ||
|
|
||||||
!string.Equals(metadataIncludeByDefault, "true", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileName = Path.GetFileName(metadataPath);
|
|
||||||
var filePath = Path.Combine(Path.GetDirectoryName(assembly.Location), fileName);
|
|
||||||
var additionalAssembly = LoadAssembly(filePath);
|
|
||||||
|
|
||||||
if (additionalAssembly == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return additionalAssembly;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (string metadataPath, string metadataIncludeByDefault) ParseMetadataAttribute(
|
|
||||||
AssemblyMetadataAttribute metadataAttribute)
|
|
||||||
{
|
|
||||||
var data = metadataAttribute.Value.Split(MetadataSeparators);
|
|
||||||
if (data.Length != 2 || string.IsNullOrWhiteSpace(data[0]) || string.IsNullOrWhiteSpace(data[1]))
|
|
||||||
{
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (data[0], data[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Assembly LoadAssembly(string filePath)
|
|
||||||
{
|
|
||||||
Assembly viewsAssembly = null;
|
|
||||||
if (AssemblyResolver(filePath))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
viewsAssembly = AssemblyLoader(filePath);
|
|
||||||
}
|
|
||||||
catch (FileLoadException)
|
|
||||||
{
|
|
||||||
// Don't throw if assembly cannot be loaded. This can happen if the file is not a managed assembly.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return viewsAssembly;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Assembly LoadFromConvention(Assembly assembly)
|
|
||||||
{
|
|
||||||
if (assembly.IsDynamic || string.IsNullOrEmpty(assembly.Location))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < ViewAssemblySuffixes.Count; i++)
|
|
||||||
{
|
|
||||||
var fileName = assembly.GetName().Name + ViewAssemblySuffixes[i] + ".dll";
|
|
||||||
var filePath = Path.Combine(Path.GetDirectoryName(assembly.Location), fileName);
|
|
||||||
|
|
||||||
var viewsAssembly = LoadAssembly(filePath);
|
|
||||||
if (viewsAssembly != null)
|
|
||||||
{
|
|
||||||
return viewsAssembly;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AddAdditionalReference(
|
|
||||||
Assembly additionalReference,
|
|
||||||
Assembly entryAssembly,
|
|
||||||
Assembly assembly,
|
|
||||||
SortedSet<Assembly> additionalReferences,
|
|
||||||
SortedSet<Assembly> entryAssemblyAdditionalReferences)
|
|
||||||
{
|
|
||||||
if (additionalReference == null ||
|
|
||||||
additionalReferences.Contains(additionalReference) ||
|
|
||||||
entryAssemblyAdditionalReferences.Contains(additionalReference))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (assembly.Equals(entryAssembly))
|
|
||||||
{
|
|
||||||
entryAssemblyAdditionalReferences.Add(additionalReference);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
additionalReferences.Add(additionalReference);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class AdditionalReferencesPair
|
|
||||||
{
|
|
||||||
public SortedSet<Assembly> AdditionalAssemblies { get; set; }
|
|
||||||
public SortedSet<Assembly> EntryAssemblyAdditionalAssemblies { get; set; }
|
|
||||||
|
|
||||||
public void Deconstruct(
|
|
||||||
out SortedSet<Assembly> additionalAssemblies,
|
|
||||||
out SortedSet<Assembly> entryAssemblyAdditionalAssemblies)
|
|
||||||
{
|
|
||||||
additionalAssemblies = AdditionalAssemblies;
|
|
||||||
entryAssemblyAdditionalAssemblies = EntryAssemblyAdditionalAssemblies;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class FullNameAssemblyComparer : IComparer<Assembly>
|
|
||||||
{
|
|
||||||
public static IComparer<Assembly> Instance { get; } = new FullNameAssemblyComparer();
|
|
||||||
|
|
||||||
public int Compare(Assembly x, Assembly y) =>
|
|
||||||
string.Compare(x?.FullName, y?.FullName, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static IEnumerable<Assembly> 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 <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)
|
|
||||||
{
|
|
||||||
if (ReferenceAssemblies == null)
|
|
||||||
{
|
|
||||||
return Enumerable.Empty<RuntimeLibrary>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var candidatesResolver = new CandidateResolver(dependencyContext.RuntimeLibraries, ReferenceAssemblies);
|
|
||||||
return candidatesResolver.GetCandidates();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CandidateResolver
|
|
||||||
{
|
|
||||||
private readonly IDictionary<string, Dependency> _runtimeDependencies;
|
|
||||||
|
|
||||||
public CandidateResolver(IReadOnlyList<RuntimeLibrary> runtimeDependencies, ISet<string> referenceAssemblies)
|
|
||||||
{
|
|
||||||
var dependenciesWithNoDuplicates = new Dictionary<string, Dependency>(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<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 (!_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<RuntimeLibrary> 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,
|
|
||||||
|
|
||||||
/// <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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1368,6 +1368,76 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
||||||
internal static string FormatVirtualFileResultExecutor_NoFileProviderConfigured()
|
internal static string FormatVirtualFileResultExecutor_NoFileProviderConfigured()
|
||||||
=> GetString("VirtualFileResultExecutor_NoFileProviderConfigured");
|
=> GetString("VirtualFileResultExecutor_NoFileProviderConfigured");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Type {0} specified by {1} is invalid. Type specified by {1} must derive from {2}.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApplicationPartFactory_InvalidFactoryType
|
||||||
|
{
|
||||||
|
get => GetString("ApplicationPartFactory_InvalidFactoryType");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Type {0} specified by {1} is invalid. Type specified by {1} must derive from {2}.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApplicationPartFactory_InvalidFactoryType(object p0, object p1, object p2)
|
||||||
|
=> string.Format(CultureInfo.CurrentCulture, GetString("ApplicationPartFactory_InvalidFactoryType"), p0, p1, p2);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// {0} specified on {1} cannot be self referential.
|
||||||
|
/// </summary>
|
||||||
|
internal static string RelatedAssemblyAttribute_AssemblyCannotReferenceSelf
|
||||||
|
{
|
||||||
|
get => GetString("RelatedAssemblyAttribute_AssemblyCannotReferenceSelf");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// {0} specified on {1} cannot be self referential.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatRelatedAssemblyAttribute_AssemblyCannotReferenceSelf(object p0, object p1)
|
||||||
|
=> string.Format(CultureInfo.CurrentCulture, GetString("RelatedAssemblyAttribute_AssemblyCannotReferenceSelf"), p0, p1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Related assembly '{0}' specified by assembly '{1}' could not be found in the directory {2}. Related assemblies must be co-located with the specifying assemblies.
|
||||||
|
/// </summary>
|
||||||
|
internal static string RelatedAssemblyAttribute_CouldNotBeFound
|
||||||
|
{
|
||||||
|
get => GetString("RelatedAssemblyAttribute_CouldNotBeFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Related assembly '{0}' specified by assembly '{1}' could not be found in the directory {2}. Related assemblies must be co-located with the specifying assemblies.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatRelatedAssemblyAttribute_CouldNotBeFound(object p0, object p1, object p2)
|
||||||
|
=> string.Format(CultureInfo.CurrentCulture, GetString("RelatedAssemblyAttribute_CouldNotBeFound"), p0, p1, p2);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Each related assembly must be declared by exactly one assembly. The assembly '{0}' was declared as related assembly by the following:
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApplicationAssembliesProvider_DuplicateRelatedAssembly
|
||||||
|
{
|
||||||
|
get => GetString("ApplicationAssembliesProvider_DuplicateRelatedAssembly");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Each related assembly must be declared by exactly one assembly. The assembly '{0}' was declared as related assembly by the following:
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApplicationAssembliesProvider_DuplicateRelatedAssembly(object p0)
|
||||||
|
=> string.Format(CultureInfo.CurrentCulture, GetString("ApplicationAssembliesProvider_DuplicateRelatedAssembly"), p0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Assembly '{0}' declared as a related assembly by assembly '{1}' cannot define additional related assemblies.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApplicationAssembliesProvider_RelatedAssemblyCannotDefineAdditional
|
||||||
|
{
|
||||||
|
get => GetString("ApplicationAssembliesProvider_RelatedAssemblyCannotDefineAdditional");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Assembly '{0}' declared as a related assembly by assembly '{1}' cannot define additional related assemblies.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApplicationAssembliesProvider_RelatedAssemblyCannotDefineAdditional(object p0, object p1)
|
||||||
|
=> string.Format(CultureInfo.CurrentCulture, GetString("ApplicationAssembliesProvider_RelatedAssemblyCannotDefineAdditional"), p0, p1);
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -421,4 +421,19 @@
|
||||||
<data name="VirtualFileResultExecutor_NoFileProviderConfigured" xml:space="preserve">
|
<data name="VirtualFileResultExecutor_NoFileProviderConfigured" xml:space="preserve">
|
||||||
<value>No file provider has been configured to process the supplied file.</value>
|
<value>No file provider has been configured to process the supplied file.</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
<data name="ApplicationPartFactory_InvalidFactoryType" xml:space="preserve">
|
||||||
|
<value>Type {0} specified by {1} is invalid. Type specified by {1} must derive from {2}.</value>
|
||||||
|
</data>
|
||||||
|
<data name="RelatedAssemblyAttribute_AssemblyCannotReferenceSelf" xml:space="preserve">
|
||||||
|
<value>{0} specified on {1} cannot be self referential.</value>
|
||||||
|
</data>
|
||||||
|
<data name="RelatedAssemblyAttribute_CouldNotBeFound" xml:space="preserve">
|
||||||
|
<value>Related assembly '{0}' specified by assembly '{1}' could not be found in the directory {2}. Related assemblies must be co-located with the specifying assemblies.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApplicationAssembliesProvider_DuplicateRelatedAssembly" xml:space="preserve">
|
||||||
|
<value>Each related assembly must be declared by exactly one assembly. The assembly '{0}' was declared as related assembly by the following:</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApplicationAssembliesProvider_RelatedAssemblyCannotDefineAdditional" xml:space="preserve">
|
||||||
|
<value>Assembly '{0}' declared as a related assembly by assembly '{1}' cannot define additional related assemblies.</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
|
|
@ -13,9 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||||
public class CompiledRazorAssemblyApplicationPartFactory : ApplicationPartFactory
|
public class CompiledRazorAssemblyApplicationPartFactory : ApplicationPartFactory
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override IEnumerable<ApplicationPart> GetApplicationParts(
|
public override IEnumerable<ApplicationPart> GetApplicationParts(Assembly assembly, string configuration)
|
||||||
Assembly assembly,
|
|
||||||
string configuration)
|
|
||||||
{
|
{
|
||||||
if (assembly == null)
|
if (assembly == null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,41 @@
|
||||||
// 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Razor.Hosting;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||||
{
|
{
|
||||||
public class CompiledRazorAssemblyPart : ApplicationPart
|
/// <summary>
|
||||||
|
/// An <see cref="ApplicationPart"/> for compiled Razor assemblies.
|
||||||
|
/// </summary>
|
||||||
|
public class CompiledRazorAssemblyPart : ApplicationPart, IRazorCompiledItemProvider
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of <see cref="CompiledRazorAssemblyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assembly">The <see cref="System.Reflection.Assembly"/></param>
|
||||||
public CompiledRazorAssemblyPart(Assembly assembly)
|
public CompiledRazorAssemblyPart(Assembly assembly)
|
||||||
{
|
{
|
||||||
Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
|
Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="System.Reflection.Assembly"/>.
|
||||||
|
/// </summary>
|
||||||
public Assembly Assembly { get; }
|
public Assembly Assembly { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public override string Name => Assembly.GetName().Name;
|
public override string Name => Assembly.GetName().Name;
|
||||||
|
|
||||||
|
IEnumerable<RazorCompiledItem> IRazorCompiledItemProvider.CompiledItems
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var loader = new RazorCompiledItemLoader();
|
||||||
|
return loader.LoadItems(Assembly);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Razor.Hosting;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Exposes one or more <see cref="RazorCompiledItem"/> instances from an <see cref="ApplicationPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface IRazorCompiledItemProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a sequence of <see cref="RazorCompiledItem"/> instances.
|
||||||
|
/// </summary>
|
||||||
|
IEnumerable<RazorCompiledItem> CompiledItems { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
// 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.Razor;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
|
using Microsoft.AspNetCore.Razor.Hosting;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||||
|
{
|
||||||
|
internal class RazorCompiledItemFeatureProvider : IApplicationFeatureProvider<ViewsFeature>
|
||||||
|
{
|
||||||
|
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewsFeature feature)
|
||||||
|
{
|
||||||
|
foreach (var provider in parts.OfType<IRazorCompiledItemProvider>())
|
||||||
|
{
|
||||||
|
// Ensure parts do not specify views with differing cases. This is not supported
|
||||||
|
// at runtime and we should flag at as such for precompiled views.
|
||||||
|
var duplicates = provider.CompiledItems
|
||||||
|
.GroupBy(i => i.Identifier, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.FirstOrDefault(g => g.Count() > 1);
|
||||||
|
|
||||||
|
if (duplicates != null)
|
||||||
|
{
|
||||||
|
var viewsDiffereningInCase = string.Join(Environment.NewLine, duplicates.Select(d => d.Identifier));
|
||||||
|
|
||||||
|
var message = string.Join(
|
||||||
|
Environment.NewLine,
|
||||||
|
Resources.RazorViewCompiler_ViewPathsDifferOnlyInCase,
|
||||||
|
viewsDiffereningInCase);
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in provider.CompiledItems)
|
||||||
|
{
|
||||||
|
var descriptor = GetCompiledViewDescriptor(item);
|
||||||
|
feature.ViewDescriptors.Add(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CompiledViewDescriptor GetCompiledViewDescriptor(RazorCompiledItem item)
|
||||||
|
{
|
||||||
|
var itemAssembly = item.Type.Assembly;
|
||||||
|
var razorViewAttribute = itemAssembly.GetCustomAttributes<RazorViewAttribute>()
|
||||||
|
.FirstOrDefault(attribute => attribute.ViewType == item.Type);
|
||||||
|
return new CompiledViewDescriptor(item, razorViewAttribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -63,6 +63,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="RazorViewAttribute"/> decorating the view.
|
/// Gets or sets the <see cref="RazorViewAttribute"/> decorating the view.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// May be <c>null</c>.
|
||||||
|
/// </remarks>
|
||||||
public RazorViewAttribute ViewAttribute { get; set; }
|
public RazorViewAttribute ViewAttribute { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -7,136 +7,71 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||||
using Microsoft.AspNetCore.Razor.Hosting;
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An <see cref="IApplicationFeatureProvider{TFeature}"/> for <see cref="ViewsFeature"/>.
|
/// An <see cref="IApplicationFeatureProvider{TFeature}"/> for <see cref="ViewsFeature"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("This type is obsolete and will be removed in a future version. See " + nameof(IRazorCompiledItemProvider) + " for alternatives.")]
|
||||||
public class ViewsFeatureProvider : IApplicationFeatureProvider<ViewsFeature>
|
public class ViewsFeatureProvider : IApplicationFeatureProvider<ViewsFeature>
|
||||||
{
|
{
|
||||||
public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews";
|
public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews";
|
||||||
|
|
||||||
public static readonly IReadOnlyList<string> ViewAssemblySuffixes = new string[]
|
|
||||||
{
|
|
||||||
PrecompiledViewsAssemblySuffix,
|
|
||||||
".Views",
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewsFeature feature)
|
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewsFeature feature)
|
||||||
{
|
{
|
||||||
var knownIdentifiers = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
var descriptors = new List<CompiledViewDescriptor>();
|
|
||||||
foreach (var assemblyPart in parts.OfType<AssemblyPart>())
|
foreach (var assemblyPart in parts.OfType<AssemblyPart>())
|
||||||
{
|
{
|
||||||
var attributes = GetViewAttributes(assemblyPart);
|
var viewAttributes = GetViewAttributes(assemblyPart)
|
||||||
var items = LoadItems(assemblyPart);
|
.Select(attribute => (Attribute: attribute, RelativePath: ViewPath.NormalizePath(attribute.Path)));
|
||||||
|
|
||||||
var merged = Merge(items, attributes);
|
var duplicates = viewAttributes.GroupBy(a => a.RelativePath, StringComparer.OrdinalIgnoreCase)
|
||||||
foreach (var item in merged)
|
.FirstOrDefault(g => g.Count() > 1);
|
||||||
|
|
||||||
|
if (duplicates != null)
|
||||||
{
|
{
|
||||||
var descriptor = new CompiledViewDescriptor(item.item, item.attribute);
|
// Ensure parts do not specify views with differing cases. This is not supported
|
||||||
// We iterate through ApplicationPart instances appear in precendence order.
|
// at runtime and we should flag at as such for precompiled views.
|
||||||
// If a view path appears in multiple views, we'll use the order to break ties.
|
var viewsDiffereningInCase = string.Join(Environment.NewLine, duplicates.Select(d => d.RelativePath));
|
||||||
if (knownIdentifiers.Add(descriptor.RelativePath))
|
|
||||||
|
var message = string.Join(
|
||||||
|
Environment.NewLine,
|
||||||
|
Resources.RazorViewCompiler_ViewPathsDifferOnlyInCase,
|
||||||
|
viewsDiffereningInCase);
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (attribute, relativePath) in viewAttributes)
|
||||||
|
{
|
||||||
|
var viewDescriptor = new CompiledViewDescriptor
|
||||||
{
|
{
|
||||||
feature.ViewDescriptors.Add(descriptor);
|
ExpirationTokens = Array.Empty<IChangeToken>(),
|
||||||
}
|
RelativePath = relativePath,
|
||||||
|
ViewAttribute = attribute,
|
||||||
|
IsPrecompiled = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
feature.ViewDescriptors.Add(viewDescriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ICollection<(RazorCompiledItem item, RazorViewAttribute attribute)> Merge(
|
|
||||||
IReadOnlyList<RazorCompiledItem> items,
|
|
||||||
IEnumerable<RazorViewAttribute> attributes)
|
|
||||||
{
|
|
||||||
// This code is a intentionally defensive. We assume that it's possible to have duplicates
|
|
||||||
// of attributes, and also items that have a single kind of metadata, but not the other.
|
|
||||||
var dictionary = new Dictionary<string, (RazorCompiledItem item, RazorViewAttribute attribute)>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
for (var i = 0; i < items.Count; i++)
|
|
||||||
{
|
|
||||||
var item = items[i];
|
|
||||||
if (!dictionary.TryGetValue(item.Identifier, out var entry))
|
|
||||||
{
|
|
||||||
dictionary.Add(item.Identifier, (item, null));
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (entry.item == null)
|
|
||||||
{
|
|
||||||
dictionary[item.Identifier] = (item, entry.attribute);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var attribute in attributes)
|
|
||||||
{
|
|
||||||
if (!dictionary.TryGetValue(attribute.Path, out var entry))
|
|
||||||
{
|
|
||||||
dictionary.Add(attribute.Path, (null, attribute));
|
|
||||||
}
|
|
||||||
else if (entry.attribute == null)
|
|
||||||
{
|
|
||||||
dictionary[attribute.Path] = (entry.item, attribute);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dictionary.Values;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal virtual IReadOnlyList<RazorCompiledItem> LoadItems(AssemblyPart assemblyPart)
|
|
||||||
{
|
|
||||||
if (assemblyPart == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(assemblyPart));
|
|
||||||
}
|
|
||||||
|
|
||||||
var viewAssembly = assemblyPart.Assembly;
|
|
||||||
if (viewAssembly != null)
|
|
||||||
{
|
|
||||||
var loader = new RazorCompiledItemLoader();
|
|
||||||
return loader.LoadItems(viewAssembly);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.Empty<RazorCompiledItem>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the sequence of <see cref="RazorViewAttribute"/> instances associated with the specified <paramref name="assemblyPart"/>.
|
/// Gets the sequence of <see cref="RazorViewAttribute"/> instances associated with the specified <paramref name="assemblyPart"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="assemblyPart">The <see cref="AssemblyPart"/>.</param>
|
/// <param name="assemblyPart">The <see cref="AssemblyPart"/>.</param>
|
||||||
/// <returns>The sequence of <see cref="RazorViewAttribute"/> instances.</returns>
|
/// <returns>The sequence of <see cref="RazorViewAttribute"/> instances.</returns>
|
||||||
protected virtual IEnumerable<RazorViewAttribute> GetViewAttributes(AssemblyPart assemblyPart)
|
protected virtual IEnumerable<RazorViewAttribute> GetViewAttributes(AssemblyPart assemblyPart)
|
||||||
{
|
|
||||||
// We check if the method was overriden by a subclass and preserve the old behavior in that case.
|
|
||||||
if (GetViewAttributesOverriden())
|
|
||||||
{
|
|
||||||
return GetViewAttributesLegacy(assemblyPart);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// It is safe to call this method for additional assembly parts even if there is a feature provider
|
|
||||||
// present on the pipeline that overrides getviewattributes as dependent parts are later in the list
|
|
||||||
// of application parts.
|
|
||||||
return GetViewAttributesFromCurrentAssembly(assemblyPart);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GetViewAttributesOverriden()
|
|
||||||
{
|
|
||||||
const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
|
|
||||||
return GetType() != typeof(ViewsFeatureProvider) &&
|
|
||||||
GetType().GetMethod(nameof(GetViewAttributes), bindingFlags).DeclaringType != typeof(ViewsFeatureProvider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<RazorViewAttribute> GetViewAttributesLegacy(AssemblyPart assemblyPart)
|
|
||||||
{
|
{
|
||||||
if (assemblyPart == null)
|
if (assemblyPart == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(assemblyPart));
|
throw new ArgumentNullException(nameof(assemblyPart));
|
||||||
}
|
}
|
||||||
|
|
||||||
var featureAssembly = GetViewAssembly(assemblyPart);
|
var featureAssembly = GetFeatureAssembly(assemblyPart);
|
||||||
if (featureAssembly != null)
|
if (featureAssembly != null)
|
||||||
{
|
{
|
||||||
return featureAssembly.GetCustomAttributes<RazorViewAttribute>();
|
return featureAssembly.GetCustomAttributes<RazorViewAttribute>();
|
||||||
|
|
@ -145,48 +80,34 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||||
return Enumerable.Empty<RazorViewAttribute>();
|
return Enumerable.Empty<RazorViewAttribute>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Assembly GetViewAssembly(AssemblyPart assemblyPart)
|
private static Assembly GetFeatureAssembly(AssemblyPart assemblyPart)
|
||||||
{
|
{
|
||||||
if (assemblyPart.Assembly.IsDynamic || string.IsNullOrEmpty(assemblyPart.Assembly.Location))
|
if (assemblyPart.Assembly.IsDynamic || string.IsNullOrEmpty((string)assemblyPart.Assembly.Location))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < ViewAssemblySuffixes.Count; i++)
|
var precompiledAssemblyFileName = assemblyPart.Assembly.GetName().Name
|
||||||
{
|
+ PrecompiledViewsAssemblySuffix
|
||||||
var fileName = assemblyPart.Assembly.GetName().Name + ViewAssemblySuffixes[i] + ".dll";
|
+ ".dll";
|
||||||
var filePath = Path.Combine(Path.GetDirectoryName(assemblyPart.Assembly.Location), fileName);
|
|
||||||
|
|
||||||
if (File.Exists(filePath))
|
var precompiledAssemblyFilePath = Path.Combine(
|
||||||
|
Path.GetDirectoryName(assemblyPart.Assembly.Location),
|
||||||
|
precompiledAssemblyFileName);
|
||||||
|
|
||||||
|
if (File.Exists(precompiledAssemblyFilePath))
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
return Assembly.LoadFile(precompiledAssemblyFilePath);
|
||||||
{
|
}
|
||||||
return Assembly.LoadFile(filePath);
|
catch (FileLoadException)
|
||||||
}
|
{
|
||||||
catch (FileLoadException)
|
// Don't throw if assembly cannot be loaded. This can happen if the file is not a managed assembly.
|
||||||
{
|
|
||||||
// Don't throw if assembly cannot be loaded. This can happen if the file is not a managed assembly.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<RazorViewAttribute> GetViewAttributesFromCurrentAssembly(AssemblyPart assemblyPart)
|
|
||||||
{
|
|
||||||
if (assemblyPart == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(assemblyPart));
|
|
||||||
}
|
|
||||||
|
|
||||||
var featureAssembly = assemblyPart.Assembly;
|
|
||||||
if (featureAssembly != null)
|
|
||||||
{
|
|
||||||
return featureAssembly.GetCustomAttributes<RazorViewAttribute>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Enumerable.Empty<RazorViewAttribute>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor;
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
|
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
|
||||||
|
|
@ -74,10 +75,21 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
builder.PartManager.FeatureProviders.Add(new TagHelperFeatureProvider());
|
builder.PartManager.FeatureProviders.Add(new TagHelperFeatureProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ViewFeature items have precedence semantics - when two views have the same path \ identifier,
|
||||||
|
// the one that appears earlier in the list wins. Therefore the ordering of
|
||||||
|
// RazorCompiledItemFeatureProvider and ViewsFeatureProvider is pertinent - any view compiled
|
||||||
|
// using the Sdk will be prefered to views compiled using MvcPrecompilation.
|
||||||
|
if (!builder.PartManager.FeatureProviders.OfType<RazorCompiledItemFeatureProvider>().Any())
|
||||||
|
{
|
||||||
|
builder.PartManager.FeatureProviders.Add(new RazorCompiledItemFeatureProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
if (!builder.PartManager.FeatureProviders.OfType<ViewsFeatureProvider>().Any())
|
if (!builder.PartManager.FeatureProviders.OfType<ViewsFeatureProvider>().Any())
|
||||||
{
|
{
|
||||||
builder.PartManager.FeatureProviders.Add(new ViewsFeatureProvider());
|
builder.PartManager.FeatureProviders.Add(new ViewsFeatureProvider());
|
||||||
}
|
}
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -100,17 +100,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
{
|
{
|
||||||
logger.ViewCompilerLocatedCompiledView(precompiledView.RelativePath);
|
logger.ViewCompilerLocatedCompiledView(precompiledView.RelativePath);
|
||||||
|
|
||||||
if (_precompiledViews.TryGetValue(precompiledView.RelativePath, out var otherValue))
|
if (!_precompiledViews.ContainsKey(precompiledView.RelativePath))
|
||||||
{
|
{
|
||||||
var message = string.Join(
|
// View ordering has precedence semantics, a view with a higher precedence was
|
||||||
Environment.NewLine,
|
// already added to the list.
|
||||||
Resources.RazorViewCompiler_ViewPathsDifferOnlyInCase,
|
_precompiledViews.Add(precompiledView.RelativePath, precompiledView);
|
||||||
otherValue.RelativePath,
|
|
||||||
precompiledView.RelativePath);
|
|
||||||
throw new InvalidOperationException(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_precompiledViews.Add(precompiledView.RelativePath, precompiledView);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_precompiledViews.Count == 0)
|
if (_precompiledViews.Count == 0)
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,5 @@
|
||||||
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
|
||||||
var compileTask = Compiler.CompileAsync(actionDescriptor.RelativePath);
|
var compileTask = Compiler.CompileAsync(actionDescriptor.RelativePath);
|
||||||
var viewDescriptor = compileTask.GetAwaiter().GetResult();
|
var viewDescriptor = compileTask.GetAwaiter().GetResult();
|
||||||
var pageAttribute = (RazorPageAttribute)viewDescriptor.ViewAttribute;
|
|
||||||
|
|
||||||
var context = new PageApplicationModelProviderContext(actionDescriptor, pageAttribute.ViewType.GetTypeInfo());
|
var context = new PageApplicationModelProviderContext(actionDescriptor, viewDescriptor.Type.GetTypeInfo());
|
||||||
for (var i = 0; i < _applicationModelProviders.Length; i++)
|
for (var i = 0; i < _applicationModelProviders.Length; i++)
|
||||||
{
|
{
|
||||||
_applicationModelProviders[i].OnProvidersExecuting(context);
|
_applicationModelProviders[i].OnProvidersExecuting(context);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,436 @@
|
||||||
|
// 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 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_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 dependencyContext = GetDependencyContext(new[]
|
||||||
|
{
|
||||||
|
GetLibrary(ThisAssembly.GetName().Name, new[] { mvcAssembly.GetName().Name, classLibrary.GetName().Name }),
|
||||||
|
GetLibrary(mvcAssembly.GetName().Name),
|
||||||
|
GetLibrary(classLibrary.GetName().Name, new[] { mvcAssembly.GetName().Name }),
|
||||||
|
});
|
||||||
|
|
||||||
|
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 dependencyContext = GetDependencyContext(new[]
|
||||||
|
{
|
||||||
|
GetLibrary(ThisAssembly.GetName().Name, new[] { relatedPart.GetName().Name, classLibrary.GetName().Name }),
|
||||||
|
GetLibrary(classLibrary.GetName().Name, new[] { mvcAssembly.GetName().Name }),
|
||||||
|
GetLibrary(relatedPart.GetName().Name, new[] { mvcAssembly.GetName().Name }),
|
||||||
|
GetLibrary(mvcAssembly.GetName().Name),
|
||||||
|
});
|
||||||
|
|
||||||
|
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(object).Assembly;
|
||||||
|
var libraryAssembly2 = typeof(HttpContext).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 dependencyContext = GetDependencyContext(new[]
|
||||||
|
{
|
||||||
|
GetLibrary(ThisAssembly.GetName().Name, new[] { relatedPart.GetName().Name, libraryAssembly1.GetName().Name }),
|
||||||
|
GetLibrary(libraryAssembly1.GetName().Name, new[] { mvcAssembly.GetName().Name }),
|
||||||
|
GetLibrary(libraryAssembly2.GetName().Name, new[] { mvcAssembly.GetName().Name }),
|
||||||
|
GetLibrary(mvcAssembly.GetName().Name),
|
||||||
|
});
|
||||||
|
|
||||||
|
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 dependencyContext = GetDependencyContext(new[]
|
||||||
|
{
|
||||||
|
GetLibrary(mixedCaseLibrary),
|
||||||
|
GetLibrary(upperCaseLibrary),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() => ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal($"A duplicate entry for library reference {upperCaseLibrary} 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 = GetLibrary("SomeRandomAssembly", "Microsoft.AspNetCore.Mvc.Abstractions");
|
||||||
|
var dependencyContext = GetDependencyContext(new[]
|
||||||
|
{
|
||||||
|
GetLibrary("Microsoft.AspNetCore.Mvc.Core"),
|
||||||
|
GetLibrary("Microsoft.AspNetCore.Mvc"),
|
||||||
|
GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"),
|
||||||
|
expected,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(new[] { expected }, candidates);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetCandidateLibraries_DoesNotThrow_IfLibraryDoesNotHaveRuntimeComponent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = GetLibrary("MyApplication", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Mvc");
|
||||||
|
var dependencyContext = GetDependencyContext(new[]
|
||||||
|
{
|
||||||
|
expected,
|
||||||
|
GetLibrary("Microsoft.AspNetCore.Server.Kestrel", "Libuv"),
|
||||||
|
GetLibrary("Microsoft.AspNetCore.Mvc"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext).ToList();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(new[] { expected }, candidates);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetCandidateLibraries_ReturnsLibrariesReferencingAnyMvcAssembly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dependencyContext = GetDependencyContext(new[]
|
||||||
|
{
|
||||||
|
GetLibrary("Foo", "Microsoft.AspNetCore.Mvc.Core"),
|
||||||
|
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"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(new[] { "Foo", "Bar", "Baz" }, candidates.Select(a => a.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetCandidateLibraries_LibraryNameComparisonsAreCaseInsensitive()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dependencyContext = GetDependencyContext(new[]
|
||||||
|
{
|
||||||
|
GetLibrary("Foo", "MICROSOFT.ASPNETCORE.MVC.CORE"),
|
||||||
|
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("LibraryA", "LIBRARYB"),
|
||||||
|
GetLibrary("LibraryB", "microsoft.aspnetcore.mvc"),
|
||||||
|
GetLibrary("Microsoft.AspNetCore.Mvc"),
|
||||||
|
GetLibrary("Not.Mvc.Assembly"),
|
||||||
|
GetLibrary("Unofficial.Microsoft.AspNetCore.Mvc"),
|
||||||
|
GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(new[] { "Foo", "Bar", "Baz", "LibraryA", "LibraryB" }, 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 = GetDependencyContext(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"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedLibraries, candidates.Select(a => a.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetCandidateLibraries_SkipsMvcAssemblies()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dependencyContext = GetDependencyContext(new[]
|
||||||
|
{
|
||||||
|
GetLibrary("MvcSandbox", "Microsoft.AspNetCore.Mvc.Core", "Microsoft.AspNetCore.Mvc"),
|
||||||
|
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"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(new[] { "MvcSandbox", "ControllersAssembly" }, candidates.Select(a => a.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test verifies DefaultAssemblyPartDiscoveryProvider.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.Core.Test",
|
||||||
|
"Microsoft.AspNetCore.Mvc.TestCommon",
|
||||||
|
"Microsoft.AspNetCore.Mvc.TestDiagnosticListener",
|
||||||
|
"Microsoft.AspNetCore.Mvc.WebApiCompatShim",
|
||||||
|
};
|
||||||
|
|
||||||
|
var additionalAssemblies = new[]
|
||||||
|
{
|
||||||
|
// The following assemblies are not reachable from Microsoft.AspNetCore.Mvc
|
||||||
|
"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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(RuntimeLibrary[] libraries)
|
||||||
|
{
|
||||||
|
var dependencyContext = new DependencyContext(
|
||||||
|
new TargetInfo("framework", "runtime", "signature", isPortable: true),
|
||||||
|
CompilationOptions.Default,
|
||||||
|
new CompilationLibrary[0],
|
||||||
|
libraries,
|
||||||
|
Enumerable.Empty<RuntimeFallbacks>());
|
||||||
|
return dependencyContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RuntimeLibrary GetLibrary(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 class TestAssembly : Assembly
|
||||||
|
{
|
||||||
|
public override string FullName => "TestRelatedAssembly";
|
||||||
|
|
||||||
|
public override bool IsDefined(Type attributeType, bool inherit)
|
||||||
|
{
|
||||||
|
return attributeType == typeof(RelatedAssemblyAttribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,7 +24,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||||
new ControllersFeatureProvider((f, v) => f.Values.Add($"ControllersFeatureProvider2{v}")));
|
new ControllersFeatureProvider((f, v) => f.Values.Add($"ControllersFeatureProvider2{v}")));
|
||||||
|
|
||||||
var feature = new ControllersFeature();
|
var feature = new ControllersFeature();
|
||||||
var expectedResults = new[] {
|
var expectedResults = new[]
|
||||||
|
{
|
||||||
"ControllersFeatureProvider1ControllersPartA",
|
"ControllersFeatureProvider1ControllersPartA",
|
||||||
"ControllersFeatureProvider1ControllersPartC",
|
"ControllersFeatureProvider1ControllersPartC",
|
||||||
"ControllersFeatureProvider2ControllersPartA",
|
"ControllersFeatureProvider2ControllersPartA",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
// 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.Reflection;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||||
|
{
|
||||||
|
public class RelatedAssemblyPartTest
|
||||||
|
{
|
||||||
|
private static readonly string AssemblyDirectory = Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetRelatedAssemblies_Noops_ForDynamicAssemblies()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var name = new AssemblyName($"DynamicAssembly-{Guid.NewGuid()}");
|
||||||
|
var assembly = AssemblyBuilder.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndCollect);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = RelatedAssemblyAttribute.GetRelatedAssemblies(assembly, throwOnError: true);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetRelatedAssemblies_ThrowsIfRelatedAttributeReferencesSelf()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "RelatedAssemblyAttribute specified on MyAssembly cannot be self referential.";
|
||||||
|
var assembly = new TestAssembly { AttributeAssembly = "MyAssembly" };
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => RelatedAssemblyAttribute.GetRelatedAssemblies(assembly, throwOnError: true));
|
||||||
|
Assert.Equal(expected, ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetRelatedAssemblies_ThrowsIfAssemblyCannotBeFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = $"Related assembly 'DoesNotExist' specified by assembly 'MyAssembly' could not be found in the directory {AssemblyDirectory}. Related assemblies must be co-located with the specifying assemblies.";
|
||||||
|
var assemblyPath = Path.Combine(AssemblyDirectory, "MyAssembly.dll");
|
||||||
|
var assembly = new TestAssembly
|
||||||
|
{
|
||||||
|
AttributeAssembly = "DoesNotExist"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = Assert.Throws<FileNotFoundException>(() => RelatedAssemblyAttribute.GetRelatedAssemblies(assembly, throwOnError: true));
|
||||||
|
Assert.Equal(expected, ex.Message);
|
||||||
|
Assert.Equal(Path.Combine(AssemblyDirectory, "DoesNotExist.dll"), ex.FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetRelatedAssemblies_LoadsRelatedAssembly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var destination = Path.Combine(AssemblyDirectory, "RelatedAssembly.dll");
|
||||||
|
var assembly = new TestAssembly
|
||||||
|
{
|
||||||
|
AttributeAssembly = "RelatedAssembly",
|
||||||
|
};
|
||||||
|
var relatedAssembly = typeof(RelatedAssemblyPartTest).Assembly;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllBytes(destination, new byte[0]);
|
||||||
|
var result = RelatedAssemblyAttribute.GetRelatedAssemblies(assembly, throwOnError: true, file =>
|
||||||
|
{
|
||||||
|
Assert.Equal(file, destination);
|
||||||
|
return relatedAssembly;
|
||||||
|
});
|
||||||
|
Assert.Equal(new[] { relatedAssembly }, result);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
File.Delete(destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestAssembly : Assembly
|
||||||
|
{
|
||||||
|
public override AssemblyName GetName()
|
||||||
|
{
|
||||||
|
return new AssemblyName("MyAssembly");
|
||||||
|
}
|
||||||
|
|
||||||
|
public string AttributeAssembly { get; set; }
|
||||||
|
|
||||||
|
public override string CodeBase => Path.Combine(AssemblyDirectory, "MyAssembly.dll");
|
||||||
|
|
||||||
|
public override object[] GetCustomAttributes(Type attributeType, bool inherit)
|
||||||
|
{
|
||||||
|
var attribute = new RelatedAssemblyAttribute(AttributeAssembly);
|
||||||
|
return new[] { attribute };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,501 +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.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Microsoft.Extensions.DependencyModel;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
|
||||||
{
|
|
||||||
public class DefaultAssemblyPartDiscoveryProviderTests
|
|
||||||
{
|
|
||||||
private static readonly Assembly CurrentAssembly =
|
|
||||||
typeof(DefaultAssemblyPartDiscoveryProviderTests).GetTypeInfo().Assembly;
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CandidateResolver_ThrowsIfDependencyContextContainsDuplicateRuntimeLibraryNames()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var upperCaseLibrary = "Microsoft.AspNetCore.Mvc";
|
|
||||||
var mixedCaseLibrary = "microsoft.aspNetCore.mvc";
|
|
||||||
|
|
||||||
var dependencyContext = new DependencyContext(
|
|
||||||
new TargetInfo("framework", "runtime", "signature", isPortable: true),
|
|
||||||
CompilationOptions.Default,
|
|
||||||
new CompilationLibrary[0],
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
GetLibrary(mixedCaseLibrary),
|
|
||||||
GetLibrary(upperCaseLibrary),
|
|
||||||
},
|
|
||||||
Enumerable.Empty<RuntimeFallbacks>());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var exception = Assert.Throws<InvalidOperationException>(() => DefaultAssemblyPartDiscoveryProvider.GetCandidateLibraries(dependencyContext));
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal($"A duplicate entry for library reference {upperCaseLibrary} 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 = GetLibrary("SomeRandomAssembly", "Microsoft.AspNetCore.Mvc.Abstractions");
|
|
||||||
var dependencyContext = new DependencyContext(
|
|
||||||
new TargetInfo("framework", "runtime", "signature", isPortable: true),
|
|
||||||
CompilationOptions.Default,
|
|
||||||
new CompilationLibrary[0],
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
GetLibrary("Microsoft.AspNetCore.Mvc.Core"),
|
|
||||||
GetLibrary("Microsoft.AspNetCore.Mvc"),
|
|
||||||
GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"),
|
|
||||||
expected,
|
|
||||||
},
|
|
||||||
Enumerable.Empty<RuntimeFallbacks>());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var candidates = DefaultAssemblyPartDiscoveryProvider.GetCandidateLibraries(dependencyContext);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(new[] { expected }, candidates);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(ResolveAdditionalReferencesData))]
|
|
||||||
public void ResolveAdditionalReferences_DiscoversAdditionalReferences(ResolveAdditionalReferencesTestData testData)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var resolver = testData.AssemblyResolver;
|
|
||||||
DefaultAssemblyPartDiscoveryProvider.AssemblyResolver = path => resolver.ContainsKey(path);
|
|
||||||
DefaultAssemblyPartDiscoveryProvider.AssemblyLoader = path => resolver.TryGetValue(path, out var result) ? result : null;
|
|
||||||
|
|
||||||
// Arrange & Act
|
|
||||||
var (additionalReferences, entryAssemblyAdditionalReferences) =
|
|
||||||
DefaultAssemblyPartDiscoveryProvider.ResolveAdditionalReferences(testData.EntryAssembly, testData.CandidateAssemblies);
|
|
||||||
|
|
||||||
var additionalRefs = additionalReferences.Select(a => a.FullName).OrderBy(id => id).ToArray();
|
|
||||||
var entryAssemblyAdditionalRefs = entryAssemblyAdditionalReferences.Select(a => a.FullName).OrderBy(id => id).ToArray();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(testData.ExpectedAdditionalReferences, additionalRefs);
|
|
||||||
Assert.Equal(testData.ExpectedEntryAssemblyAdditionalReferences, entryAssemblyAdditionalRefs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ResolveAdditionalReferencesTestData
|
|
||||||
{
|
|
||||||
public Assembly EntryAssembly { get; set; }
|
|
||||||
public SortedSet<Assembly> CandidateAssemblies { get; set; }
|
|
||||||
public IDictionary<string, Assembly> AssemblyResolver { get; set; }
|
|
||||||
public string[] ExpectedAdditionalReferences { get; set; }
|
|
||||||
public string[] ExpectedEntryAssemblyAdditionalReferences { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TheoryData<ResolveAdditionalReferencesTestData> ResolveAdditionalReferencesData
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var data = new TheoryData<ResolveAdditionalReferencesTestData>();
|
|
||||||
var noCandidates = Array.Empty<Assembly>();
|
|
||||||
var noResolvable = new Dictionary<string, Assembly>();
|
|
||||||
var noAdditionalReferences = new string[] { };
|
|
||||||
|
|
||||||
// Single assembly app no precompilation
|
|
||||||
var aAssembly = new DiscoveryTestAssembly("A");
|
|
||||||
var singleAppNoPrecompilation = new ResolveAdditionalReferencesTestData
|
|
||||||
{
|
|
||||||
EntryAssembly = aAssembly,
|
|
||||||
CandidateAssemblies = CreateCandidates(aAssembly),
|
|
||||||
AssemblyResolver = noResolvable,
|
|
||||||
ExpectedAdditionalReferences = Array.Empty<string>(),
|
|
||||||
ExpectedEntryAssemblyAdditionalReferences = Array.Empty<string>()
|
|
||||||
};
|
|
||||||
data.Add(singleAppNoPrecompilation);
|
|
||||||
|
|
||||||
// Single assembly app with old precompilation not included in the graph
|
|
||||||
var bAssembly = new DiscoveryTestAssembly("B");
|
|
||||||
var (bPath, bPrecompiledViews) = CreateResolvablePrecompiledViewsAssembly("B");
|
|
||||||
var singleAssemblyPrecompilationNotInGraph = new ResolveAdditionalReferencesTestData
|
|
||||||
{
|
|
||||||
EntryAssembly = bAssembly,
|
|
||||||
CandidateAssemblies = CreateCandidates(bAssembly),
|
|
||||||
AssemblyResolver = new Dictionary<string, Assembly> { [bPath] = bPrecompiledViews },
|
|
||||||
ExpectedAdditionalReferences = noAdditionalReferences,
|
|
||||||
ExpectedEntryAssemblyAdditionalReferences = new[] { bPrecompiledViews.FullName }
|
|
||||||
};
|
|
||||||
data.Add(singleAssemblyPrecompilationNotInGraph);
|
|
||||||
|
|
||||||
//// Single assembly app with new precompilation not included in the graph
|
|
||||||
var cAssembly = new DiscoveryTestAssembly(
|
|
||||||
"C",
|
|
||||||
DiscoveryTestAssembly.DefaultLocationBase,
|
|
||||||
new[] { ("C.PrecompiledViews.dll", true) });
|
|
||||||
var (cPath, cPrecompiledViews) = CreateResolvablePrecompiledViewsAssembly("C");
|
|
||||||
var singleAssemblyNewPrecompilationNotInGraph = new ResolveAdditionalReferencesTestData
|
|
||||||
{
|
|
||||||
EntryAssembly = cAssembly,
|
|
||||||
CandidateAssemblies = CreateCandidates(cAssembly),
|
|
||||||
AssemblyResolver = new Dictionary<string, Assembly> { [cPath] = cPrecompiledViews },
|
|
||||||
ExpectedAdditionalReferences = noAdditionalReferences,
|
|
||||||
ExpectedEntryAssemblyAdditionalReferences = new[] { cPrecompiledViews.FullName }
|
|
||||||
};
|
|
||||||
data.Add(singleAssemblyNewPrecompilationNotInGraph);
|
|
||||||
|
|
||||||
//// Single assembly app with new precompilation included in the graph
|
|
||||||
var dAssembly = new DiscoveryTestAssembly(
|
|
||||||
"D",
|
|
||||||
DiscoveryTestAssembly.DefaultLocationBase,
|
|
||||||
new[] { (Path.Combine(DiscoveryTestAssembly.DefaultLocationBase, "subfolder", "D.PrecompiledViews.dll"), true) });
|
|
||||||
var (dPath, dPrecompiledViews) = CreateResolvablePrecompiledViewsAssembly("D");
|
|
||||||
var singleAssemblyNewPrecompilationInGraph = new ResolveAdditionalReferencesTestData
|
|
||||||
{
|
|
||||||
EntryAssembly = dAssembly,
|
|
||||||
CandidateAssemblies = CreateCandidates(dAssembly, dPrecompiledViews),
|
|
||||||
AssemblyResolver = new Dictionary<string, Assembly> { [dPath] = dPrecompiledViews },
|
|
||||||
ExpectedAdditionalReferences = noAdditionalReferences,
|
|
||||||
ExpectedEntryAssemblyAdditionalReferences = new[] { dPrecompiledViews.FullName }
|
|
||||||
};
|
|
||||||
data.Add(singleAssemblyNewPrecompilationInGraph);
|
|
||||||
|
|
||||||
//// Single assembly app with new precompilation included in the graph optional part
|
|
||||||
var hAssembly = new DiscoveryTestAssembly(
|
|
||||||
"h",
|
|
||||||
DiscoveryTestAssembly.DefaultLocationBase,
|
|
||||||
new[] { ("H.PrecompiledViews.dll", false) });
|
|
||||||
var (hPath, hPrecompiledViews) = CreateResolvablePrecompiledViewsAssembly("H");
|
|
||||||
var singleAssemblyNewPrecompilationInGraphOptionalDependency = new ResolveAdditionalReferencesTestData
|
|
||||||
{
|
|
||||||
EntryAssembly = hAssembly,
|
|
||||||
CandidateAssemblies = CreateCandidates(hAssembly, hPrecompiledViews),
|
|
||||||
AssemblyResolver = new Dictionary<string, Assembly> { [hPath] = hPrecompiledViews },
|
|
||||||
ExpectedAdditionalReferences = noAdditionalReferences,
|
|
||||||
ExpectedEntryAssemblyAdditionalReferences = noAdditionalReferences
|
|
||||||
};
|
|
||||||
data.Add(singleAssemblyNewPrecompilationInGraphOptionalDependency);
|
|
||||||
|
|
||||||
//// Entry assembly with two dependencies app with new precompilation included in the graph
|
|
||||||
var eAssembly = new DiscoveryTestAssembly(
|
|
||||||
"E",
|
|
||||||
DiscoveryTestAssembly.DefaultLocationBase,
|
|
||||||
new[] { ("E.PrecompiledViews.dll", true) });
|
|
||||||
var (ePath, ePrecompiledViews) = CreateResolvablePrecompiledViewsAssembly("E");
|
|
||||||
|
|
||||||
var fAssembly = new DiscoveryTestAssembly(
|
|
||||||
"F",
|
|
||||||
DiscoveryTestAssembly.DefaultLocationBase,
|
|
||||||
new[] { ("F.PrecompiledViews.dll", true) });
|
|
||||||
var (fPath, fPrecompiledViews) = CreateResolvablePrecompiledViewsAssembly("F");
|
|
||||||
|
|
||||||
var gAssembly = new DiscoveryTestAssembly(
|
|
||||||
"G",
|
|
||||||
DiscoveryTestAssembly.DefaultLocationBase,
|
|
||||||
new[] { (Path.Combine(DiscoveryTestAssembly.DefaultLocationBase, "subfolder", "G.PrecompiledViews.dll"), true) });
|
|
||||||
|
|
||||||
var (gPath, gPrecompiledViews) = CreateResolvablePrecompiledViewsAssembly("G");
|
|
||||||
var multipleAssembliesNewPrecompilationInGraph = new ResolveAdditionalReferencesTestData
|
|
||||||
{
|
|
||||||
EntryAssembly = gAssembly,
|
|
||||||
CandidateAssemblies = CreateCandidates(
|
|
||||||
fAssembly,
|
|
||||||
fPrecompiledViews,
|
|
||||||
gAssembly,
|
|
||||||
gPrecompiledViews,
|
|
||||||
eAssembly,
|
|
||||||
ePrecompiledViews),
|
|
||||||
AssemblyResolver = new Dictionary<string, Assembly> {
|
|
||||||
[ePath] = ePrecompiledViews,
|
|
||||||
[fPath] = fPrecompiledViews,
|
|
||||||
[gPath] = gPrecompiledViews
|
|
||||||
},
|
|
||||||
ExpectedAdditionalReferences = new[] { ePrecompiledViews.FullName, fPrecompiledViews.FullName },
|
|
||||||
ExpectedEntryAssemblyAdditionalReferences = new[] {
|
|
||||||
gPrecompiledViews.FullName
|
|
||||||
}
|
|
||||||
};
|
|
||||||
data.Add(multipleAssembliesNewPrecompilationInGraph);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SortedSet<Assembly> CreateCandidates(params Assembly[] assemblies) =>
|
|
||||||
new SortedSet<Assembly>(assemblies, DefaultAssemblyPartDiscoveryProvider.FullNameAssemblyComparer.Instance);
|
|
||||||
|
|
||||||
private static (string, Assembly) CreateResolvablePrecompiledViewsAssembly(string name, string path = null) =>
|
|
||||||
(path ?? Path.Combine(DiscoveryTestAssembly.DefaultLocationBase, $"{name}.PrecompiledViews.dll"),
|
|
||||||
new DiscoveryTestAssembly($"{name}.PrecompiledViews"));
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GetCandidateLibraries_DoesNotThrow_IfLibraryDoesNotHaveRuntimeComponent()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expected = GetLibrary("MyApplication", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Mvc");
|
|
||||||
var deps = new DependencyContext(
|
|
||||||
new TargetInfo("netcoreapp2.0", "rurntime", "signature", isPortable: true),
|
|
||||||
CompilationOptions.Default,
|
|
||||||
Enumerable.Empty<CompilationLibrary>(),
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
expected,
|
|
||||||
GetLibrary("Microsoft.AspNetCore.Server.Kestrel", "Libuv"),
|
|
||||||
GetLibrary("Microsoft.AspNetCore.Mvc"),
|
|
||||||
},
|
|
||||||
Enumerable.Empty<RuntimeFallbacks>());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var candidates = DefaultAssemblyPartDiscoveryProvider.GetCandidateLibraries(deps).ToList();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(new[] { expected }, candidates);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CandidateAssemblies_ReturnsEntryAssemblyIfDependencyContextIsNull()
|
|
||||||
{
|
|
||||||
// Arrange & Act
|
|
||||||
var candidates = DefaultAssemblyPartDiscoveryProvider.GetCandidateAssemblies(CurrentAssembly, dependencyContext: null);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(new[] { CurrentAssembly }, candidates);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GetCandidateLibraries_ReturnsLibrariesReferencingAnyMvcAssembly()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var dependencyContext = new DependencyContext(
|
|
||||||
new TargetInfo("framework", "runtime", "signature", isPortable: true),
|
|
||||||
CompilationOptions.Default,
|
|
||||||
new CompilationLibrary[0],
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
GetLibrary("Foo", "Microsoft.AspNetCore.Mvc.Core"),
|
|
||||||
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<RuntimeFallbacks>());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var candidates = DefaultAssemblyPartDiscoveryProvider.GetCandidateLibraries(dependencyContext);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(new[] { "Foo", "Bar", "Baz" }, candidates.Select(a => a.Name));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GetCandidateLibraries_LibraryNameComparisonsAreCaseInsensitive()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var dependencyContext = new DependencyContext(
|
|
||||||
new TargetInfo("framework", "runtime", "signature", isPortable: true),
|
|
||||||
CompilationOptions.Default,
|
|
||||||
new CompilationLibrary[0],
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
GetLibrary("Foo", "MICROSOFT.ASPNETCORE.MVC.CORE"),
|
|
||||||
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("LibraryA", "LIBRARYB"),
|
|
||||||
GetLibrary("LibraryB", "microsoft.aspnetcore.mvc"),
|
|
||||||
GetLibrary("Microsoft.AspNetCore.Mvc"),
|
|
||||||
GetLibrary("Not.Mvc.Assembly"),
|
|
||||||
GetLibrary("Unofficial.Microsoft.AspNetCore.Mvc"),
|
|
||||||
GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"),
|
|
||||||
},
|
|
||||||
Enumerable.Empty<RuntimeFallbacks>());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var candidates = DefaultAssemblyPartDiscoveryProvider.GetCandidateLibraries(dependencyContext);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(new[] { "Foo", "Bar", "Baz", "LibraryA", "LibraryB" }, 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<RuntimeFallbacks>());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var candidates = DefaultAssemblyPartDiscoveryProvider.GetCandidateLibraries(dependencyContext);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(expectedLibraries, candidates.Select(a => a.Name));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GetCandidateLibraries_SkipsMvcAssemblies()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var dependencyContext = new DependencyContext(
|
|
||||||
new TargetInfo("framework", "runtime", "signature", isPortable: true),
|
|
||||||
CompilationOptions.Default,
|
|
||||||
new CompilationLibrary[0],
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
GetLibrary("MvcSandbox", "Microsoft.AspNetCore.Mvc.Core", "Microsoft.AspNetCore.Mvc"),
|
|
||||||
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<RuntimeFallbacks>());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var candidates = DefaultAssemblyPartDiscoveryProvider.GetCandidateLibraries(dependencyContext);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(new[] { "MvcSandbox", "ControllersAssembly" }, candidates.Select(a => a.Name));
|
|
||||||
}
|
|
||||||
|
|
||||||
// This test verifies DefaultAssemblyPartDiscoveryProvider.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.Core.Test",
|
|
||||||
"Microsoft.AspNetCore.Mvc.TestCommon",
|
|
||||||
"Microsoft.AspNetCore.Mvc.TestDiagnosticListener",
|
|
||||||
"Microsoft.AspNetCore.Mvc.WebApiCompatShim",
|
|
||||||
};
|
|
||||||
|
|
||||||
var additionalAssemblies = new[]
|
|
||||||
{
|
|
||||||
// The following assemblies are not reachable from Microsoft.AspNetCore.Mvc
|
|
||||||
"Microsoft.AspNetCore.Mvc.Formatters.Xml",
|
|
||||||
"Microsoft.AspnetCore.All",
|
|
||||||
};
|
|
||||||
|
|
||||||
var dependencyContextLibraries = DependencyContext.Load(CurrentAssembly)
|
|
||||||
.RuntimeLibraries
|
|
||||||
.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 = DefaultAssemblyPartDiscoveryProvider
|
|
||||||
.ReferenceAssemblies
|
|
||||||
.OrderBy(p => p, StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(expected, referenceAssemblies, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static RuntimeLibrary GetLibrary(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 class DiscoveryTestAssembly : Assembly
|
|
||||||
{
|
|
||||||
private readonly string _fullName;
|
|
||||||
private readonly string _location;
|
|
||||||
private readonly Attribute[] _additionalDependencies;
|
|
||||||
public static readonly string DefaultLocationBase =
|
|
||||||
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"c:\app\" : "/app/";
|
|
||||||
|
|
||||||
public DiscoveryTestAssembly(string fullName, string location = null)
|
|
||||||
: this(
|
|
||||||
fullName,
|
|
||||||
location ?? Path.Combine(DefaultLocationBase, new AssemblyName(fullName).Name + ".dll"),
|
|
||||||
Array.Empty<(string, bool)>())
|
|
||||||
{ }
|
|
||||||
|
|
||||||
public DiscoveryTestAssembly(string fullName, string location, IEnumerable<(string, bool)> additionalDependencies)
|
|
||||||
{
|
|
||||||
_fullName = fullName;
|
|
||||||
_location = location;
|
|
||||||
_additionalDependencies = additionalDependencies
|
|
||||||
.Select(ad => new AssemblyMetadataAttribute(
|
|
||||||
"Microsoft.AspNetCore.Mvc.AdditionalReference",
|
|
||||||
$"{ad.Item1},{ad.Item2}")).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string FullName => _fullName;
|
|
||||||
|
|
||||||
public override string Location => _location;
|
|
||||||
|
|
||||||
public override object[] GetCustomAttributes(bool inherit) => _additionalDependencies;
|
|
||||||
|
|
||||||
public override object[] GetCustomAttributes(Type attributeType, bool inherit)
|
|
||||||
{
|
|
||||||
var attributes = _additionalDependencies
|
|
||||||
.Where(t => t.GetType().IsAssignableFrom(attributeType))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
var result = Array.CreateInstance(attributeType, attributes.Length);
|
|
||||||
attributes.CopyTo(result, 0);
|
|
||||||
return (object[])result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override AssemblyName GetName(bool copiedName) => new AssemblyName(FullName);
|
|
||||||
|
|
||||||
public override AssemblyName GetName() => new AssemblyName(FullName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -64,5 +64,18 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
Assert.Equal("Hello from runtime-compiled rzc view!", responseBody.Trim());
|
Assert.Equal("Hello from runtime-compiled rzc view!", responseBody.Trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RzcViewsArePreferredToPrecompiledViews()
|
||||||
|
{
|
||||||
|
// Verifies that when two views have the same paths, the one compiled using rzc is preferred to the one from Precompilation.
|
||||||
|
// Act
|
||||||
|
var response = await Client.GetAsync("http://localhost/Common/View");
|
||||||
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal("Hello from buildtime-compiled rzc view!", responseBody.Trim());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
// 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.AspNetCore.Mvc.Razor.Compilation;
|
||||||
|
using Microsoft.AspNetCore.Razor.Hosting;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||||
|
{
|
||||||
|
public class RazorCompiledItemFeatureProviderTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void PopulateFeature_AddsItemsFromProviderTypes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var item1 = Mock.Of<RazorCompiledItem>(i => i.Identifier == "Item1" && i.Type == typeof(TestView));
|
||||||
|
var item2 = Mock.Of<RazorCompiledItem>(i => i.Identifier == "Item2" && i.Type == typeof(TestPage));
|
||||||
|
var part1 = new AssemblyPart(typeof(RazorCompiledItemFeatureProviderTest).Assembly);
|
||||||
|
var part2 = new Mock<ApplicationPart>();
|
||||||
|
part2
|
||||||
|
.As<IRazorCompiledItemProvider>()
|
||||||
|
.Setup(p => p.CompiledItems).Returns(new[] { item1, item2, });
|
||||||
|
var featureProvider = new RazorCompiledItemFeatureProvider();
|
||||||
|
var feature = new ViewsFeature();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
featureProvider.PopulateFeature(new[] { part1, part2.Object }, feature);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(new[] { item1, item2 }, feature.ViewDescriptors.Select(d => d.Item));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PopulateFeature_PopulatesRazorViewAttributeFromTypeAssembly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var item1 = Mock.Of<RazorCompiledItem>(i => i.Identifier == "Item1" && i.Type == typeof(TestView));
|
||||||
|
var item2 = Mock.Of<RazorCompiledItem>(i => i.Identifier == "Item2" && i.Type == typeof(TestPage));
|
||||||
|
|
||||||
|
var attribute1 = new RazorViewAttribute("Item1", typeof(TestView));
|
||||||
|
var attribute2 = new RazorViewAttribute("Item2", typeof(TestPage));
|
||||||
|
|
||||||
|
var assembly = new TestAssembly(new[] { attribute1, attribute2 });
|
||||||
|
|
||||||
|
var part1 = new AssemblyPart(assembly);
|
||||||
|
var part2 = new Mock<ApplicationPart>();
|
||||||
|
part2
|
||||||
|
.As<IRazorCompiledItemProvider>()
|
||||||
|
.Setup(p => p.CompiledItems).Returns(new[] { item1, item2, });
|
||||||
|
var featureProvider = new RazorCompiledItemFeatureProvider();
|
||||||
|
var feature = new ViewsFeature();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
featureProvider.PopulateFeature(new[] { part1, part2.Object }, feature);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(new[] { item1, item2 }, feature.ViewDescriptors.Select(d => d.Item));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PopulateFeature_AllowsDuplicateItemsFromMultipleParts()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var item1 = Mock.Of<RazorCompiledItem>(i => i.Identifier == "Item" && i.Type == typeof(TestView));
|
||||||
|
var item2 = Mock.Of<RazorCompiledItem>(i => i.Identifier == "Item" && i.Type == typeof(TestPage));
|
||||||
|
var part1 = new Mock<ApplicationPart>();
|
||||||
|
part1
|
||||||
|
.As<IRazorCompiledItemProvider>()
|
||||||
|
.Setup(p => p.CompiledItems).Returns(new[] { item1, });
|
||||||
|
var part2 = new Mock<ApplicationPart>();
|
||||||
|
part2
|
||||||
|
.As<IRazorCompiledItemProvider>()
|
||||||
|
.Setup(p => p.CompiledItems).Returns(new[] { item2, });
|
||||||
|
var featureProvider = new RazorCompiledItemFeatureProvider();
|
||||||
|
var feature = new ViewsFeature();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
featureProvider.PopulateFeature(new[] { part1.Object, part2.Object }, feature);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(new[] { item1, item2 }, feature.ViewDescriptors.Select(d => d.Item));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PopulateFeature_ThrowsIfTwoItemsFromSamePart_OnlyDifferInCase()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var item1 = Mock.Of<RazorCompiledItem>(i => i.Identifier == "Item");
|
||||||
|
var item2 = Mock.Of<RazorCompiledItem>(i => i.Identifier == "item");
|
||||||
|
var expected = string.Join(
|
||||||
|
Environment.NewLine,
|
||||||
|
"The following precompiled view paths differ only in case, which is not supported:",
|
||||||
|
"Item",
|
||||||
|
"item");
|
||||||
|
var part1 = new AssemblyPart(typeof(RazorCompiledItemFeatureProviderTest).Assembly);
|
||||||
|
var part2 = new Mock<ApplicationPart>();
|
||||||
|
part2
|
||||||
|
.As<IRazorCompiledItemProvider>()
|
||||||
|
.Setup(p => p.CompiledItems).Returns(new[] { item1, item2, });
|
||||||
|
var featureProvider = new RazorCompiledItemFeatureProvider();
|
||||||
|
var feature = new ViewsFeature();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => featureProvider.PopulateFeature(new[] { part1, part2.Object }, feature));
|
||||||
|
Assert.Equal(expected, ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestAssembly : Assembly
|
||||||
|
{
|
||||||
|
private readonly object[] _attributes;
|
||||||
|
|
||||||
|
public TestAssembly(object[] attributes)
|
||||||
|
{
|
||||||
|
_attributes = attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object[] GetCustomAttributes(Type attributeType, bool inherit)
|
||||||
|
{
|
||||||
|
return _attributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestView { }
|
||||||
|
|
||||||
|
private class TestPage { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,27 +6,26 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Reflection.Emit;
|
using System.Reflection.Emit;
|
||||||
using System.Text;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||||
using Microsoft.AspNetCore.Razor.Hosting;
|
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||||
{
|
{
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
public class ViewsFeatureProviderTest
|
public class ViewsFeatureProviderTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void PopulateFeature_ReturnsEmptySequenceIfNoAssemblyPartHasViewAssembly()
|
public void PopulateFeature_ReturnsEmptySequenceIfNoAssemblyPartHasViewAssembly()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var partManager = new ApplicationPartManager();
|
var applicationPartManager = new ApplicationPartManager();
|
||||||
partManager.ApplicationParts.Add(new AssemblyPart(typeof(ViewsFeatureProviderTest).Assembly));
|
applicationPartManager.ApplicationParts.Add(
|
||||||
partManager.FeatureProviders.Add(new ViewsFeatureProvider());
|
new AssemblyPart(typeof(ViewsFeatureProviderTest).GetTypeInfo().Assembly));
|
||||||
|
applicationPartManager.FeatureProviders.Add(new ViewsFeatureProvider());
|
||||||
var feature = new ViewsFeature();
|
var feature = new ViewsFeature();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
partManager.PopulateFeature(feature);
|
applicationPartManager.PopulateFeature(feature);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(feature.ViewDescriptors);
|
Assert.Empty(feature.ViewDescriptors);
|
||||||
|
|
@ -36,31 +35,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||||
public void PopulateFeature_ReturnsViewsFromAllAvailableApplicationParts()
|
public void PopulateFeature_ReturnsViewsFromAllAvailableApplicationParts()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var part1 = new AssemblyPart(typeof(object).Assembly);
|
var part1 = new AssemblyPart(typeof(object).GetTypeInfo().Assembly);
|
||||||
var part2 = new AssemblyPart(GetType().Assembly);
|
var part2 = new AssemblyPart(GetType().GetTypeInfo().Assembly);
|
||||||
|
var featureProvider = new TestableViewsFeatureProvider(new Dictionary<AssemblyPart, IEnumerable<RazorViewAttribute>>
|
||||||
var items = new Dictionary<AssemblyPart, IReadOnlyList<RazorCompiledItem>>
|
|
||||||
{
|
|
||||||
{
|
|
||||||
part1,
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new TestRazorCompiledItem(typeof(object), "mvc.1.0.view", "/Views/test/Index.cshtml", new object[]{ }),
|
|
||||||
|
|
||||||
// This one doesn't have a RazorViewAttribute
|
|
||||||
new TestRazorCompiledItem(typeof(StringBuilder), "mvc.1.0.view", "/Views/test/About.cshtml", new object[]{ }),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
part2,
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", "/Areas/Admin/Views/Index.cshtml", new object[]{ }),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var attributes = new Dictionary<AssemblyPart, IEnumerable<RazorViewAttribute>>
|
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
part1,
|
part1,
|
||||||
|
|
@ -74,105 +51,71 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new RazorViewAttribute("/Areas/Admin/Views/Index.cshtml", typeof(string)),
|
new RazorViewAttribute("/Areas/Admin/Views/Index.cshtml", typeof(string)),
|
||||||
|
|
||||||
// This one doesn't have a RazorCompiledItem
|
|
||||||
new RazorViewAttribute("/Areas/Admin/Views/About.cshtml", typeof(int)),
|
new RazorViewAttribute("/Areas/Admin/Views/About.cshtml", typeof(int)),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
var featureProvider = new TestableViewsFeatureProvider(items, attributes);
|
var applicationPartManager = new ApplicationPartManager();
|
||||||
var partManager = new ApplicationPartManager();
|
applicationPartManager.ApplicationParts.Add(part1);
|
||||||
partManager.ApplicationParts.Add(part1);
|
applicationPartManager.ApplicationParts.Add(part2);
|
||||||
partManager.ApplicationParts.Add(part2);
|
applicationPartManager.FeatureProviders.Add(featureProvider);
|
||||||
partManager.FeatureProviders.Add(featureProvider);
|
|
||||||
var feature = new ViewsFeature();
|
var feature = new ViewsFeature();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
partManager.PopulateFeature(feature);
|
applicationPartManager.PopulateFeature(feature);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(feature.ViewDescriptors.OrderBy(f => f.RelativePath, StringComparer.Ordinal),
|
Assert.Collection(feature.ViewDescriptors.OrderBy(f => f.RelativePath, StringComparer.Ordinal),
|
||||||
view =>
|
view =>
|
||||||
{
|
{
|
||||||
Assert.Empty(view.ExpirationTokens);
|
|
||||||
Assert.True(view.IsPrecompiled);
|
|
||||||
Assert.Null(view.Item);
|
|
||||||
Assert.Equal("/Areas/Admin/Views/About.cshtml", view.RelativePath);
|
Assert.Equal("/Areas/Admin/Views/About.cshtml", view.RelativePath);
|
||||||
Assert.Equal(typeof(int), view.Type);
|
|
||||||
Assert.Equal("/Areas/Admin/Views/About.cshtml", view.ViewAttribute.Path);
|
|
||||||
Assert.Equal(typeof(int), view.ViewAttribute.ViewType);
|
Assert.Equal(typeof(int), view.ViewAttribute.ViewType);
|
||||||
},
|
},
|
||||||
view =>
|
view =>
|
||||||
{
|
{
|
||||||
// This one doesn't have a RazorCompiledItem
|
|
||||||
Assert.Empty(view.ExpirationTokens);
|
|
||||||
Assert.True(view.IsPrecompiled);
|
|
||||||
Assert.Equal("/Areas/Admin/Views/Index.cshtml", view.Item.Identifier);
|
|
||||||
Assert.Equal("mvc.1.0.view", view.Item.Kind);
|
|
||||||
Assert.Equal(typeof(string), view.Item.Type);
|
|
||||||
Assert.Equal("/Areas/Admin/Views/Index.cshtml", view.RelativePath);
|
Assert.Equal("/Areas/Admin/Views/Index.cshtml", view.RelativePath);
|
||||||
Assert.Equal(typeof(string), view.Type);
|
|
||||||
Assert.Equal("/Areas/Admin/Views/Index.cshtml", view.ViewAttribute.Path);
|
|
||||||
Assert.Equal(typeof(string), view.ViewAttribute.ViewType);
|
Assert.Equal(typeof(string), view.ViewAttribute.ViewType);
|
||||||
},
|
},
|
||||||
view =>
|
view =>
|
||||||
{
|
{
|
||||||
// This one doesn't have a RazorViewAttribute
|
|
||||||
Assert.Empty(view.ExpirationTokens);
|
|
||||||
Assert.True(view.IsPrecompiled);
|
|
||||||
Assert.Equal("/Views/test/About.cshtml", view.Item.Identifier);
|
|
||||||
Assert.Equal("mvc.1.0.view", view.Item.Kind);
|
|
||||||
Assert.Equal(typeof(StringBuilder), view.Item.Type);
|
|
||||||
Assert.Equal("/Views/test/About.cshtml", view.RelativePath);
|
|
||||||
Assert.Equal(typeof(StringBuilder), view.Type);
|
|
||||||
Assert.Null(view.ViewAttribute);
|
|
||||||
},
|
|
||||||
view =>
|
|
||||||
{
|
|
||||||
Assert.Empty(view.ExpirationTokens);
|
|
||||||
Assert.True(view.IsPrecompiled);
|
|
||||||
Assert.Equal("/Views/test/Index.cshtml", view.Item.Identifier);
|
|
||||||
Assert.Equal("mvc.1.0.view", view.Item.Kind);
|
|
||||||
Assert.Equal(typeof(object), view.Item.Type);
|
|
||||||
Assert.Equal("/Views/test/Index.cshtml", view.RelativePath);
|
Assert.Equal("/Views/test/Index.cshtml", view.RelativePath);
|
||||||
Assert.Equal(typeof(object), view.Type);
|
|
||||||
Assert.Equal("/Views/test/Index.cshtml", view.ViewAttribute.Path);
|
|
||||||
Assert.Equal(typeof(object), view.ViewAttribute.ViewType);
|
Assert.Equal(typeof(object), view.ViewAttribute.ViewType);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void PopulateFeature_PrefersViewsFromPartsWithHigherPrecedence()
|
public void PopulateFeature_ThrowsIfSingleAssemblyContainsMultipleAttributesWithTheSamePath()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var part1 = new AssemblyPart(typeof(ViewsFeatureProvider).Assembly);
|
var path1 = "/Views/test/Index.cshtml";
|
||||||
var item1 = new TestRazorCompiledItem(typeof(StringBuilder), "mvc.1.0.view", "/Areas/Admin/Views/Shared/_Layout.cshtml", new object[] { });
|
var path2 = "/views/test/index.cshtml";
|
||||||
|
var expected = string.Join(
|
||||||
var part2 = new AssemblyPart(GetType().Assembly);
|
Environment.NewLine,
|
||||||
var item2 = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", "/Areas/Admin/Views/Shared/_Layout.cshtml", new object[] { });
|
"The following precompiled view paths differ only in case, which is not supported:",
|
||||||
var item3 = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", "/Areas/Admin/Views/Shared/_Partial.cshtml", new object[] { });
|
path1,
|
||||||
|
path2);
|
||||||
var items = new Dictionary<AssemblyPart, IReadOnlyList<RazorCompiledItem>>
|
var part = new AssemblyPart(typeof(object).GetTypeInfo().Assembly);
|
||||||
|
var featureProvider = new TestableViewsFeatureProvider(new Dictionary<AssemblyPart, IEnumerable<RazorViewAttribute>>
|
||||||
{
|
{
|
||||||
{ part1, new[] { item1 } },
|
{
|
||||||
{ part2, new[] { item2, item3, } },
|
part,
|
||||||
};
|
new[]
|
||||||
|
{
|
||||||
|
new RazorViewAttribute(path1, typeof(object)),
|
||||||
|
new RazorViewAttribute(path2, typeof(object)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
var featureProvider = new TestableViewsFeatureProvider(items, attributes: new Dictionary<AssemblyPart, IEnumerable<RazorViewAttribute>>());
|
var applicationPartManager = new ApplicationPartManager();
|
||||||
var partManager = new ApplicationPartManager();
|
applicationPartManager.ApplicationParts.Add(part);
|
||||||
partManager.ApplicationParts.Add(part1);
|
applicationPartManager.FeatureProviders.Add(featureProvider);
|
||||||
partManager.ApplicationParts.Add(part2);
|
|
||||||
partManager.FeatureProviders.Add(featureProvider);
|
|
||||||
var feature = new ViewsFeature();
|
var feature = new ViewsFeature();
|
||||||
|
|
||||||
// Act
|
// Act & Assert
|
||||||
partManager.PopulateFeature(feature);
|
var ex = Assert.Throws<InvalidOperationException>(() => applicationPartManager.PopulateFeature(feature));
|
||||||
|
Assert.Equal(expected, ex.Message);
|
||||||
// Assert
|
|
||||||
Assert.Collection(feature.ViewDescriptors.OrderBy(f => f.RelativePath, StringComparer.Ordinal),
|
|
||||||
view => Assert.Same(item1, view.Item),
|
|
||||||
view => Assert.Same(item3, view.Item));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -180,168 +123,58 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var name = new AssemblyName($"DynamicAssembly-{Guid.NewGuid()}");
|
var name = new AssemblyName($"DynamicAssembly-{Guid.NewGuid()}");
|
||||||
var assembly = AssemblyBuilder.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndCollect);
|
var assembly = AssemblyBuilder.DefineDynamicAssembly(name,
|
||||||
|
AssemblyBuilderAccess.RunAndCollect);
|
||||||
|
|
||||||
var partManager = new ApplicationPartManager();
|
var applicationPartManager = new ApplicationPartManager();
|
||||||
partManager.ApplicationParts.Add(new AssemblyPart(assembly));
|
applicationPartManager.ApplicationParts.Add(new AssemblyPart(assembly));
|
||||||
partManager.FeatureProviders.Add(new ViewsFeatureProvider());
|
applicationPartManager.FeatureProviders.Add(new ViewsFeatureProvider());
|
||||||
var feature = new ViewsFeature();
|
var feature = new ViewsFeature();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
partManager.PopulateFeature(feature);
|
applicationPartManager.PopulateFeature(feature);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(feature.ViewDescriptors);
|
Assert.Empty(feature.ViewDescriptors);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void PopulateFeature_ReadsAttributesFromTheCurrentAssembly()
|
public void PopulateFeature_DoesNotFail_IfAssemblyHasEmptyLocation()
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var item1 = new RazorCompiledItemAttribute(typeof(string), "mvc.1.0.view", "view");
|
|
||||||
var assembly = new AssemblyWithEmptyLocation(
|
|
||||||
new RazorViewAttribute[] { new RazorViewAttribute("view", typeof(string)) },
|
|
||||||
new RazorCompiledItemAttribute[] { item1 });
|
|
||||||
|
|
||||||
var partManager = new ApplicationPartManager();
|
|
||||||
partManager.ApplicationParts.Add(new AssemblyPart(assembly));
|
|
||||||
partManager.FeatureProviders.Add(new ViewsFeatureProvider());
|
|
||||||
var feature = new ViewsFeature();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
partManager.PopulateFeature(feature);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var descriptor = Assert.Single(feature.ViewDescriptors);
|
|
||||||
Assert.Equal(typeof(string), descriptor.Item.Type);
|
|
||||||
Assert.Equal("mvc.1.0.view", descriptor.Item.Kind);
|
|
||||||
Assert.Equal("view", descriptor.Item.Identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void PopulateFeature_LegacyBehaviorDoesNotFail_IfAssemblyHasEmptyLocation()
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var assembly = new AssemblyWithEmptyLocation();
|
var assembly = new AssemblyWithEmptyLocation();
|
||||||
var partManager = new ApplicationPartManager();
|
var applicationPartManager = new ApplicationPartManager();
|
||||||
partManager.ApplicationParts.Add(new AssemblyPart(assembly));
|
applicationPartManager.ApplicationParts.Add(new AssemblyPart(assembly));
|
||||||
partManager.FeatureProviders.Add(new OverrideViewsFeatureProvider());
|
applicationPartManager.FeatureProviders.Add(new ViewsFeatureProvider());
|
||||||
var feature = new ViewsFeature();
|
var feature = new ViewsFeature();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
partManager.PopulateFeature(feature);
|
applicationPartManager.PopulateFeature(feature);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(feature.ViewDescriptors);
|
Assert.Empty(feature.ViewDescriptors);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void PopulateFeature_PreservesOldBehavior_IfGetViewAttributesWasOverriden()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var assembly = new AssemblyWithEmptyLocation(
|
|
||||||
new RazorViewAttribute[] { new RazorViewAttribute("view", typeof(string)) },
|
|
||||||
new RazorCompiledItemAttribute[] { });
|
|
||||||
|
|
||||||
var partManager = new ApplicationPartManager();
|
|
||||||
partManager.ApplicationParts.Add(new AssemblyPart(assembly));
|
|
||||||
partManager.FeatureProviders.Add(new OverrideViewsFeatureProvider());
|
|
||||||
var feature = new ViewsFeature();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
partManager.PopulateFeature(feature);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Empty(feature.ViewDescriptors);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class OverrideViewsFeatureProvider : ViewsFeatureProvider
|
|
||||||
{
|
|
||||||
protected override IEnumerable<RazorViewAttribute> GetViewAttributes(AssemblyPart assemblyPart)
|
|
||||||
=> base.GetViewAttributes(assemblyPart);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestRazorCompiledItem : RazorCompiledItem
|
|
||||||
{
|
|
||||||
public TestRazorCompiledItem(Type type, string kind, string identifier, object[] metadata)
|
|
||||||
{
|
|
||||||
Type = type;
|
|
||||||
Kind = kind;
|
|
||||||
Identifier = identifier;
|
|
||||||
Metadata = metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string Identifier { get; }
|
|
||||||
|
|
||||||
public override string Kind { get; }
|
|
||||||
|
|
||||||
public override IReadOnlyList<object> Metadata { get; }
|
|
||||||
|
|
||||||
public override Type Type { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestableViewsFeatureProvider : ViewsFeatureProvider
|
private class TestableViewsFeatureProvider : ViewsFeatureProvider
|
||||||
{
|
{
|
||||||
private readonly Dictionary<AssemblyPart, IEnumerable<RazorViewAttribute>> _attributes;
|
private readonly Dictionary<AssemblyPart, IEnumerable<RazorViewAttribute>> _attributeLookup;
|
||||||
private readonly Dictionary<AssemblyPart, IReadOnlyList<RazorCompiledItem>> _items;
|
|
||||||
|
|
||||||
public TestableViewsFeatureProvider(
|
public TestableViewsFeatureProvider(Dictionary<AssemblyPart, IEnumerable<RazorViewAttribute>> attributeLookup)
|
||||||
Dictionary<AssemblyPart, IReadOnlyList<RazorCompiledItem>> items,
|
|
||||||
Dictionary<AssemblyPart, IEnumerable<RazorViewAttribute>> attributes)
|
|
||||||
{
|
{
|
||||||
_items = items;
|
_attributeLookup = attributeLookup;
|
||||||
_attributes = attributes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IEnumerable<RazorViewAttribute> GetViewAttributes(AssemblyPart assemblyPart)
|
protected override IEnumerable<RazorViewAttribute> GetViewAttributes(AssemblyPart assemblyPart)
|
||||||
{
|
{
|
||||||
if (_attributes.TryGetValue(assemblyPart, out var attributes))
|
return _attributeLookup[assemblyPart];
|
||||||
{
|
|
||||||
return attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Enumerable.Empty<RazorViewAttribute>();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override IReadOnlyList<RazorCompiledItem> LoadItems(AssemblyPart assemblyPart)
|
|
||||||
{
|
|
||||||
return _items[assemblyPart];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AssemblyWithEmptyLocation : Assembly
|
private class AssemblyWithEmptyLocation : Assembly
|
||||||
{
|
{
|
||||||
private readonly RazorViewAttribute[] _razorViewAttributes;
|
|
||||||
private readonly RazorCompiledItemAttribute[] _razorCompiledItemAttributes;
|
|
||||||
|
|
||||||
public AssemblyWithEmptyLocation()
|
|
||||||
: this(Array.Empty<RazorViewAttribute>(), Array.Empty<RazorCompiledItemAttribute>())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public AssemblyWithEmptyLocation(
|
|
||||||
RazorViewAttribute[] razorViewAttributes,
|
|
||||||
RazorCompiledItemAttribute[] razorCompiledItemAttributes)
|
|
||||||
{
|
|
||||||
_razorViewAttributes = razorViewAttributes;
|
|
||||||
_razorCompiledItemAttributes = razorCompiledItemAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string Location => string.Empty;
|
public override string Location => string.Empty;
|
||||||
|
|
||||||
public override string FullName => typeof(ViewsFeatureProviderTest).Assembly.FullName;
|
public override string FullName => typeof(ViewsFeatureProviderTest).GetTypeInfo().Assembly.FullName;
|
||||||
|
|
||||||
public override object[] GetCustomAttributes(Type attributeType, bool inherit)
|
|
||||||
{
|
|
||||||
if (attributeType == typeof(RazorViewAttribute))
|
|
||||||
{
|
|
||||||
return _razorViewAttributes;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return _razorCompiledItemAttributes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IEnumerable<TypeInfo> DefinedTypes
|
public override IEnumerable<TypeInfo> DefinedTypes
|
||||||
{
|
{
|
||||||
|
|
@ -360,4 +193,5 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,27 +23,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
{
|
{
|
||||||
public class RazorViewCompilerTest
|
public class RazorViewCompilerTest
|
||||||
{
|
{
|
||||||
[Fact]
|
|
||||||
public void Constructor_ThrowsIfMultiplePrecompiledViewsHavePathsDifferingOnlyInCase()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var fileProvider = new TestFileProvider();
|
|
||||||
var precompiledViews = new[]
|
|
||||||
{
|
|
||||||
new CompiledViewDescriptor { RelativePath = "/Views/Home/About.cshtml" },
|
|
||||||
new CompiledViewDescriptor { RelativePath = "/Views/home/About.cshtml" },
|
|
||||||
};
|
|
||||||
var message = string.Join(
|
|
||||||
Environment.NewLine,
|
|
||||||
"The following precompiled view paths differ only in case, which is not supported:",
|
|
||||||
precompiledViews[0].RelativePath,
|
|
||||||
precompiledViews[1].RelativePath);
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
var ex = Assert.Throws<InvalidOperationException>(() => GetViewCompiler(fileProvider, precompiledViews: precompiledViews));
|
|
||||||
Assert.Equal(message, ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CompileAsync_ReturnsResultWithNullAttribute_IfFileIsNotFoundInFileSystem()
|
public async Task CompileAsync_ReturnsResultWithNullAttribute_IfFileIsNotFoundInFileSystem()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,10 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
feature => Assert.IsType<ViewComponentFeatureProvider>(feature),
|
feature => Assert.IsType<ViewComponentFeatureProvider>(feature),
|
||||||
feature => Assert.IsType<MetadataReferenceFeatureProvider>(feature),
|
feature => Assert.IsType<MetadataReferenceFeatureProvider>(feature),
|
||||||
feature => Assert.IsType<TagHelperFeatureProvider>(feature),
|
feature => Assert.IsType<TagHelperFeatureProvider>(feature),
|
||||||
|
feature => Assert.IsType<RazorCompiledItemFeatureProvider>(feature),
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
feature => Assert.IsType<ViewsFeatureProvider>(feature));
|
feature => Assert.IsType<ViewsFeatureProvider>(feature));
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
#pragma checksum "D:\k\Mvc\test\WebSites\RazorBuildWebSite\Views\Common\CommonView.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "a09a0106df2e63aecf6fc6ddf30df39b489d9783"
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
[assembly: global::Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorViewAttribute(@"/Views/Common/CommonView.cshtml", typeof(RazorBuildWebSite.Views.Precompilation._Views_Precompilation_CommonView))]
|
||||||
|
namespace RazorBuildWebSite.Views.Precompilation
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
public class _Views_Precompilation_CommonView : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
|
||||||
|
{
|
||||||
|
#pragma warning disable 1998
|
||||||
|
public async override global::System.Threading.Tasks.Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
BeginContext(0, 48, true);
|
||||||
|
WriteLiteral("Hello from buildtime-compiled precompilation view!");
|
||||||
|
EndContext();
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||||
|
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
|
||||||
|
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||||
|
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
|
||||||
|
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||||
|
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
|
||||||
|
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||||
|
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
|
||||||
|
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||||
|
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// Generated by the MSBuild WriteCodeFragment class.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
[assembly: Microsoft.AspNetCore.Mvc.ApplicationParts.ProvideApplicationPartFactoryAttribute("Microsoft.AspNetCore.Mvc.ApplicationParts.CompiledRazorAssemblyApplicationPartFac" +
|
||||||
|
"tory, Microsoft.AspNetCore.Mvc.Razor")]
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
#pragma checksum "D:\k\Mvc\test\WebSites\RazorBuildWebSite\Views\Common\CommonView.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "56260f24a80ae2b8854fd2c9a1005bae485882cc"
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(RazorBuildWebSite.Views.Rzc.Views_Rzc_View), @"mvc.1.0.view", @"/Views/Common/CommonView.cshtml")]
|
||||||
|
[assembly:global::Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorViewAttribute(@"/Views/CommonView.cshtml", typeof(RazorBuildWebSite.Views.Rzc.Views_Rzc_View))]
|
||||||
|
namespace RazorBuildWebSite.Views.Rzc
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
[global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"56260f24a80ae2b8854fd2c9a1005bae485882cc", @"/Views/Common/CommonView.cshtml")]
|
||||||
|
[global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"f265f06036e4378eada2a78f5366ad0e13e1d8af", @"/_ViewImports.cshtml")]
|
||||||
|
internal class Views_Rzc_CommonView : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
|
||||||
|
{
|
||||||
|
#pragma warning disable 1998
|
||||||
|
public async override global::System.Threading.Tasks.Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
BeginContext(0, 39, true);
|
||||||
|
WriteLiteral("Hello from buildtime-compiled rzc view!");
|
||||||
|
EndContext();
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||||
|
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
|
||||||
|
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||||
|
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
|
||||||
|
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||||
|
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
|
||||||
|
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||||
|
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
|
||||||
|
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||||
|
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace RazorBuildWebSite.Controllers
|
||||||
|
{
|
||||||
|
public class CommonController : Controller
|
||||||
|
{
|
||||||
|
public new ActionResult View()
|
||||||
|
{
|
||||||
|
return base.View("CommonView");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue