1- // Copyright (c) .NET Foundation. All rights reserved.
1+ // Copyright (c) .NET Foundation. All rights reserved.
22// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33
44using System ;
55using System . Collections . Generic ;
66using System . IO ;
77using System . Linq ;
88using System . Reflection ;
9+ using System . Runtime . Loader ;
910using Microsoft . AspNetCore . Mvc . Core ;
1011
1112namespace Microsoft . AspNetCore . Mvc . ApplicationParts
@@ -16,7 +17,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
1617 [ AttributeUsage ( AttributeTargets . Assembly , AllowMultiple = true ) ]
1718 public sealed class RelatedAssemblyAttribute : Attribute
1819 {
19- private static readonly Func < string , Assembly > AssemblyLoadFileDelegate = Assembly . LoadFile ;
20+ private static readonly Func < string , Assembly > LoadFromAssemblyPathDelegate =
21+ AssemblyLoadContext . GetLoadContext ( typeof ( RelatedAssemblyAttribute ) . Assembly ) . LoadFromAssemblyPath ;
2022
2123 /// <summary>
2224 /// Initializes a new instance of <see cref="RelatedAssemblyAttribute"/>.
@@ -50,7 +52,7 @@ public static IReadOnlyList<Assembly> GetRelatedAssemblies(Assembly assembly, bo
5052 throw new ArgumentNullException ( nameof ( assembly ) ) ;
5153 }
5254
53- return GetRelatedAssemblies ( assembly , throwOnError , File . Exists , AssemblyLoadFileDelegate ) ;
55+ return GetRelatedAssemblies ( assembly , throwOnError , File . Exists , LoadFromAssemblyPathDelegate ) ;
5456 }
5557
5658 internal static IReadOnlyList < Assembly > GetRelatedAssemblies (
@@ -66,7 +68,7 @@ internal static IReadOnlyList<Assembly> GetRelatedAssemblies(
6668
6769 // MVC will specifically look for related parts in the same physical directory as the assembly.
6870 // No-op if the assembly does not have a location.
69- if ( assembly . IsDynamic || string . IsNullOrEmpty ( assembly . Location ) )
71+ if ( assembly . IsDynamic )
7072 {
7173 return Array . Empty < Assembly > ( ) ;
7274 }
@@ -78,8 +80,10 @@ internal static IReadOnlyList<Assembly> GetRelatedAssemblies(
7880 }
7981
8082 var assemblyName = assembly . GetName ( ) . Name ;
81- var assemblyLocation = assembly . Location ;
82- var assemblyDirectory = Path . GetDirectoryName ( assemblyLocation ) ;
83+ // Assembly.Location may be null for a single-file exe. In this case, attempt to look for related parts in the app's base directory
84+ var assemblyDirectory = string . IsNullOrEmpty ( assembly . Location ) ?
85+ AppContext . BaseDirectory :
86+ Path . GetDirectoryName ( assembly . Location ) ;
8387
8488 var relatedAssemblies = new List < Assembly > ( ) ;
8589 for ( var i = 0 ; i < attributes . Length ; i ++ )
@@ -91,6 +95,22 @@ internal static IReadOnlyList<Assembly> GetRelatedAssemblies(
9195 Resources . FormatRelatedAssemblyAttribute_AssemblyCannotReferenceSelf ( nameof ( RelatedAssemblyAttribute ) , assemblyName ) ) ;
9296 }
9397
98+ var relatedAssemblyName = new AssemblyName ( attribute . AssemblyFileName ) ;
99+ Assembly relatedAssembly ;
100+ try
101+ {
102+ // Perform a cursory check to determine if the Assembly has already been loaded
103+ // before going to disk. In the ordinary case, related parts that are part of
104+ // application's reference closure should already be loaded.
105+ relatedAssembly = Assembly . Load ( relatedAssemblyName ) ;
106+ relatedAssemblies . Add ( relatedAssembly ) ;
107+ continue ;
108+ }
109+ catch ( IOException )
110+ {
111+ // The assembly isn't already loaded. Patience, we'll attempt to load it from disk next.
112+ }
113+
94114 var relatedAssemblyLocation = Path . Combine ( assemblyDirectory , attribute . AssemblyFileName + ".dll" ) ;
95115 if ( ! fileExists ( relatedAssemblyLocation ) )
96116 {
@@ -106,7 +126,7 @@ internal static IReadOnlyList<Assembly> GetRelatedAssemblies(
106126 }
107127 }
108128
109- var relatedAssembly = loadFile ( relatedAssemblyLocation ) ;
129+ relatedAssembly = loadFile ( relatedAssemblyLocation ) ;
110130 relatedAssemblies . Add ( relatedAssembly ) ;
111131 }
112132
0 commit comments