using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;
using Microsoft.Extensions.DependencyModel.Resolution;
namespace Amazon.Lambda.TestTool.Runtime
{
public class LambdaAssemblyLoadContext : AssemblyLoadContext
{
#if !NETCOREAPP2_1
private AssemblyDependencyResolver _builtInResolver;
#endif
private CustomAssemblyResolver _customResolver;
private CustomAssemblyResolver _customDefaultContextResolver;
public LambdaAssemblyLoadContext(string lambdaPath)
:
#if !NETCOREAPP2_1
base("LambdaContext")
{
_builtInResolver = new AssemblyDependencyResolver(lambdaPath);
#else
base()
{
#endif
_customResolver = new CustomAssemblyResolver(this, lambdaPath);
_customDefaultContextResolver = new CustomAssemblyResolver(AssemblyLoadContext.Default, lambdaPath);
AssemblyLoadContext.Default.Resolving += OnDefaultAssemblyLoadContextResolving;
}
private Assembly OnDefaultAssemblyLoadContextResolving(AssemblyLoadContext context, AssemblyName assemblyName)
{
string assemblyPath = _customDefaultContextResolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
return LoadFromAssemblyPath(assemblyPath);
}
return null;
}
protected override Assembly Load(AssemblyName assemblyName)
{
if (assemblyName.Name.StartsWith("Amazon.Lambda.Core"))
return null;
string assemblyPath = null;
#if !NETCOREAPP2_1
assemblyPath = _builtInResolver.ResolveAssemblyToPath(assemblyName);
#endif
if (assemblyPath == null || !File.Exists(assemblyPath))
{
assemblyPath = _customResolver.ResolveAssemblyToPath(assemblyName);
}
if (assemblyPath == null)
{
assemblyPath = SearchMicrosoftAspNetCoreApp(assemblyName);
}
if (assemblyPath != null)
{
return LoadFromAssemblyPath(assemblyPath);
}
return null;
}
///
/// See if the assembly being loaded is coming from the Microsoft.AspNetCore.App runtime. If so
/// then load that assembly.
///
/// This is done because if fallback to the Default AssemblyLoadContext is used then services added
/// to the IServiceCollection will not be resolved. They will be added from the Lambda context but be
/// attempted to resolved in the Default context. The Default context doesn't have access to the types in the
/// Lambda context and so they will fail to resolve.
///
///
///
private string SearchMicrosoftAspNetCoreApp(AssemblyName assemblyName)
{
var pathMicrosoftAspNetCoreApp = Path.GetDirectoryName(typeof(string).Assembly.Location).Replace("Microsoft.NETCore.App", "Microsoft.AspNetCore.App");
var assemblyDllName = assemblyName.Name + ".dll";
var fullPath = Path.Combine(pathMicrosoftAspNetCoreApp, assemblyDllName);
return File.Exists(fullPath) ? fullPath : null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string libraryPath = null;
#if !NETCOREAPP2_1
libraryPath = _builtInResolver.ResolveUnmanagedDllToPath(unmanagedDllName);
#endif
if (libraryPath != null)
{
return LoadUnmanagedDllFromPath(libraryPath);
}
return IntPtr.Zero;
}
class CustomAssemblyResolver
{
private readonly ICompilationAssemblyResolver assemblyResolver;
private readonly DependencyContext dependencyContext;
public CustomAssemblyResolver(AssemblyLoadContext assemblyLoadContext, string rootAssemblyPath)
{
var assembly = assemblyLoadContext.LoadFromAssemblyPath(rootAssemblyPath);
this.dependencyContext = DependencyContext.Load(assembly);
this.assemblyResolver = new CompositeCompilationAssemblyResolver
(new ICompilationAssemblyResolver[]
{
new AppBaseCompilationAssemblyResolver(Path.GetDirectoryName(rootAssemblyPath)),
new ReferenceAssemblyPathResolver(),
new PackageCompilationAssemblyResolver()
});
}
public string ResolveAssemblyToPath(AssemblyName name)
{
bool NamesMatch(RuntimeLibrary runtime)
{
return string.Equals(runtime.Name, name.Name, StringComparison.OrdinalIgnoreCase);
}
bool ResourceAssetPathMatch(RuntimeLibrary runtime)
{
foreach (var group in runtime.RuntimeAssemblyGroups)
{
foreach (var path in group.AssetPaths)
{
if (path.EndsWith("/" + name.Name + ".dll"))
{
return true;
}
}
}
return false;
}
RuntimeLibrary library =
this.dependencyContext.RuntimeLibraries.FirstOrDefault(NamesMatch);
if (library == null)
library = this.dependencyContext.RuntimeLibraries.FirstOrDefault(ResourceAssetPathMatch);
if (library != null)
{
var wrapper = new CompilationLibrary(
library.Type,
library.Name,
library.Version,
library.Hash,
library.RuntimeAssemblyGroups.SelectMany(g => g.AssetPaths),
library.Dependencies,
library.Serviceable);
var assemblies = new List();
this.assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies);
if (assemblies.Count > 0)
{
return assemblies[0];
}
}
return null;
}
}
}
}