Improve assembly resolution. Standalone hosting now works again.
This commit is contained in:
parent
9ec79ae9f7
commit
aff369e86d
|
|
@ -16,11 +16,9 @@ namespace Microsoft.Blazor.Server.Test
|
|||
[Fact]
|
||||
public void RootDirContainsOnlyBinDir()
|
||||
{
|
||||
var (entrypoint, entrypointData) = GetBclAssemblyForTest("mscorlib");
|
||||
var provider = new ReferencedAssemblyFileProvider(
|
||||
entrypoint,
|
||||
entrypointData,
|
||||
MonoStaticFileProvider.BclFiles);
|
||||
"mscorlib",
|
||||
new ReferencedAssemblyResolver(MonoStaticFileProvider.BclFiles, string.Empty));
|
||||
Assert.Collection(provider.GetDirectoryContents("/"), item =>
|
||||
{
|
||||
Assert.Equal("/_bin", item.PhysicalPath);
|
||||
|
|
@ -31,11 +29,9 @@ namespace Microsoft.Blazor.Server.Test
|
|||
[Fact]
|
||||
public void FindsReferencedAssemblyGraphSimple()
|
||||
{
|
||||
var (entrypoint, entrypointData) = GetBclAssemblyForTest("System.Linq.Expressions");
|
||||
var provider = new ReferencedAssemblyFileProvider(
|
||||
entrypoint,
|
||||
entrypointData,
|
||||
MonoStaticFileProvider.BclFiles);
|
||||
"System.Linq.Expressions",
|
||||
new ReferencedAssemblyResolver(MonoStaticFileProvider.BclFiles, string.Empty));
|
||||
var contents = provider.GetDirectoryContents("/_bin").OrderBy(i => i.Name).ToList();
|
||||
Assert.Collection(contents,
|
||||
item => { Assert.Equal("/_bin/mscorlib.dll", item.PhysicalPath); },
|
||||
|
|
@ -48,11 +44,12 @@ namespace Microsoft.Blazor.Server.Test
|
|||
public void FindsReferencedAssemblyGraphRealistic()
|
||||
{
|
||||
// Arrange
|
||||
var standaloneAppAssemblyLocation = typeof(StandaloneApp.Program).Assembly.Location;
|
||||
var standaloneAppAssembly = typeof(StandaloneApp.Program).Assembly;
|
||||
var provider = new ReferencedAssemblyFileProvider(
|
||||
AssemblyDefinition.ReadAssembly(standaloneAppAssemblyLocation),
|
||||
File.ReadAllBytes(standaloneAppAssemblyLocation),
|
||||
MonoStaticFileProvider.BclFiles);
|
||||
standaloneAppAssembly.GetName().Name,
|
||||
new ReferencedAssemblyResolver(
|
||||
MonoStaticFileProvider.BclFiles,
|
||||
Path.GetDirectoryName(standaloneAppAssembly.Location)));
|
||||
var expectedContents = new[]
|
||||
{
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ namespace HostedInAspNet.Server
|
|||
app.UseBlazorDevelopmentServer("../HostedInAspNet.Client");
|
||||
}
|
||||
|
||||
app.UseBlazor(clientAssembly: typeof(Client.Program).Assembly);
|
||||
app.UseBlazor(clientAssemblyPath: typeof(Client.Program).Assembly.Location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,6 @@
|
|||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using MonoSanityClient;
|
||||
using System.IO;
|
||||
using System.Net.Mime;
|
||||
|
||||
namespace MonoSanity
|
||||
{
|
||||
|
|
@ -15,7 +12,7 @@ namespace MonoSanity
|
|||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseFileServer();
|
||||
app.UseBlazor(clientAssembly: typeof(MonoSanityClient.Examples).Assembly);
|
||||
app.UseBlazor(clientAssemblyPath: typeof(MonoSanityClient.Examples).Assembly.Location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<h1>Hello</h1>
|
||||
<script src="/_framework/blazor.js"></script>
|
||||
<script src="/_framework/blazor.js" main="StandaloneApp.dll"
|
||||
references="Microsoft.Blazor.dll, netstandard.dll"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.Blazor.DevHost.Server
|
|||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseBlazorDevelopmentServer(".");
|
||||
app.UseBlazor(clientAssembly: FindClientAssembly(app));
|
||||
app.UseBlazor(clientAssemblyPath: FindClientAssembly(app).Location);
|
||||
}
|
||||
|
||||
private static Assembly FindClientAssembly(IApplicationBuilder app)
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@ namespace Microsoft.AspNetCore.Builder
|
|||
{
|
||||
public static void UseBlazor(
|
||||
this IApplicationBuilder applicationBuilder,
|
||||
Assembly clientAssembly)
|
||||
string clientAssemblyPath)
|
||||
{
|
||||
applicationBuilder.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
RequestPath = "/_framework",
|
||||
FileProvider = ClientFileProvider.Instantiate(clientAssembly),
|
||||
FileProvider = ClientFileProvider.Instantiate(clientAssemblyPath),
|
||||
ContentTypeProvider = CreateContentTypeProvider(),
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,16 +4,21 @@
|
|||
using Microsoft.Blazor.Browser;
|
||||
using Microsoft.Blazor.Mono;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.Blazor.Server.ClientFilesystem
|
||||
{
|
||||
internal static class ClientFileProvider
|
||||
{
|
||||
public static IFileProvider Instantiate(Assembly clientApp)
|
||||
public static IFileProvider Instantiate(string clientAssemblyPath)
|
||||
=> new CompositeFileProvider(
|
||||
MonoStaticFileProvider.JsFiles,
|
||||
BlazorBrowserFileProvider.Instance,
|
||||
new ReferencedAssemblyFileProvider(clientApp, MonoStaticFileProvider.BclFiles));
|
||||
new ReferencedAssemblyFileProvider(
|
||||
Path.GetFileNameWithoutExtension(clientAssemblyPath),
|
||||
new ReferencedAssemblyResolver(
|
||||
MonoStaticFileProvider.BclFiles,
|
||||
Path.GetDirectoryName(clientAssemblyPath))));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,10 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
using Microsoft.Blazor.Internal.Common.FileProviders;
|
||||
using System.Reflection;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Blazor.Server.ClientFilesystem
|
||||
{
|
||||
|
|
@ -16,152 +13,57 @@ namespace Microsoft.Blazor.Server.ClientFilesystem
|
|||
{
|
||||
private const string ClientBinDir = "_bin";
|
||||
|
||||
public ReferencedAssemblyFileProvider(Assembly assembly, IFileProvider clientBcl) : this(
|
||||
AssemblyDefinition.ReadAssembly(assembly.Location),
|
||||
File.ReadAllBytes(assembly.Location),
|
||||
clientBcl)
|
||||
{
|
||||
}
|
||||
|
||||
public ReferencedAssemblyFileProvider(
|
||||
AssemblyDefinition entrypoint,
|
||||
byte[] entrypointData,
|
||||
IFileProvider clientBcl)
|
||||
: base(ComputeContents(entrypoint, entrypointData, clientBcl))
|
||||
public ReferencedAssemblyFileProvider(string rootAssemblyName, ReferencedAssemblyResolver resolver)
|
||||
: base(ComputeContents(rootAssemblyName, resolver))
|
||||
{
|
||||
}
|
||||
|
||||
private static IEnumerable<(string, Stream)> ComputeContents(
|
||||
AssemblyDefinition entrypoint,
|
||||
byte[] entrypointData,
|
||||
IFileProvider clientBcl)
|
||||
string rootAssemblyName,
|
||||
ReferencedAssemblyResolver resolver)
|
||||
{
|
||||
var foundAssemblies = new Dictionary<string, ReferencedAssembly>();
|
||||
AddWithReferencesRecursive(new ReferencedAssembly(entrypoint, entrypointData), clientBcl, foundAssemblies);
|
||||
var foundAssemblies = new Dictionary<string, ReferencedAssemblyInfo>();
|
||||
AddWithReferencesRecursive(rootAssemblyName, resolver, foundAssemblies);
|
||||
|
||||
return foundAssemblies.Values.Select(assembly => (
|
||||
$"/{ClientBinDir}/{assembly.Name}.dll",
|
||||
$"/{ClientBinDir}/{assembly.Definition.Name.Name}.dll",
|
||||
(Stream)new MemoryStream(assembly.Data)));
|
||||
}
|
||||
|
||||
private static void AddWithReferencesRecursive(
|
||||
ReferencedAssembly root,
|
||||
IFileProvider clientBcl,
|
||||
IDictionary<string, ReferencedAssembly> results)
|
||||
string name,
|
||||
ReferencedAssemblyResolver resolver,
|
||||
IDictionary<string, ReferencedAssemblyInfo> results)
|
||||
{
|
||||
results.Add(root.Name, root);
|
||||
|
||||
foreach (var module in root.Definition.Modules)
|
||||
if (resolver.TryResolve(name, out var assemblyBytes))
|
||||
{
|
||||
foreach (var referenceName in module.AssemblyReferences)
|
||||
var assemblyInfo = new ReferencedAssemblyInfo(assemblyBytes);
|
||||
results.Add(assemblyInfo.Definition.Name.Name, assemblyInfo);
|
||||
|
||||
var childReferencesToAdd = assemblyInfo.Definition.Modules
|
||||
.SelectMany(module => module.AssemblyReferences)
|
||||
.Select(childReference => childReference.Name)
|
||||
.Where(childReferenceName => !results.ContainsKey(childReferenceName));
|
||||
foreach (var childReferenceName in childReferencesToAdd)
|
||||
{
|
||||
if (!results.ContainsKey(referenceName.Name))
|
||||
{
|
||||
var resolvedReference = ResolveReference(clientBcl, module, referenceName);
|
||||
if (resolvedReference != null)
|
||||
{
|
||||
AddWithReferencesRecursive(resolvedReference, clientBcl, results);
|
||||
}
|
||||
}
|
||||
AddWithReferencesRecursive(childReferenceName, resolver, results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ReferencedAssembly ResolveReference(IFileProvider clientBcl, ModuleDefinition module, AssemblyNameReference referenceName)
|
||||
private class ReferencedAssemblyInfo
|
||||
{
|
||||
if (SearchInFileProvider(clientBcl, string.Empty, $"{referenceName.Name}.dll", out var bclFile))
|
||||
{
|
||||
// Where possible, we resolve references to client BCL assemblies
|
||||
return new ReferencedAssembly(bclFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// If it's not a client BCL assembly, maybe we can resolve it natively
|
||||
// (e.g., if it's in the app's bin directory, or a NuGet package)
|
||||
var nativelyResolved = module.AssemblyResolver.Resolve(referenceName);
|
||||
return AllowServingAssembly(nativelyResolved)
|
||||
? new ReferencedAssembly(
|
||||
nativelyResolved,
|
||||
File.ReadAllBytes(nativelyResolved.MainModule.FileName))
|
||||
: null;
|
||||
}
|
||||
catch (AssemblyResolutionException)
|
||||
{
|
||||
// Some of the referenced assemblies aren't included in the Mono BCL, e.g.,
|
||||
// Mono.Security.dll which is referenced from System.dll. These ones are not
|
||||
// required at runtime, so just skip them.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AllowServingAssembly(AssemblyDefinition nativelyResolvedAssembly)
|
||||
{
|
||||
// When we use the native assembly resolver, it might return something from a NuGet
|
||||
// packages folder which we do want to serve, or it might return something from the
|
||||
// core .NET BCL which we *don't* want to serve (because the core BCL assemblies
|
||||
// should come from the Mono WASM distribution only). Currently there isn't a good
|
||||
// way to differentiate these cases, so as a temporary heuristic, assume anything
|
||||
// named System.* shouldn't be resolved natively.
|
||||
return !nativelyResolvedAssembly.MainModule.Name.StartsWith(
|
||||
"System.",
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static bool SearchInFileProvider(IFileProvider fileProvider, string searchRootDirNoTrailingSlash, string name, out IFileInfo file)
|
||||
{
|
||||
var possibleFullPath = $"{searchRootDirNoTrailingSlash}/{name}";
|
||||
var possibleResult = fileProvider.GetFileInfo(possibleFullPath);
|
||||
if (possibleResult.Exists)
|
||||
{
|
||||
file = possibleResult;
|
||||
return true;
|
||||
}
|
||||
|
||||
var childDirs = fileProvider.GetDirectoryContents(searchRootDirNoTrailingSlash)
|
||||
.Where(item => item.IsDirectory);
|
||||
foreach (var childDir in childDirs)
|
||||
{
|
||||
if (SearchInFileProvider(fileProvider, childDir.PhysicalPath, name, out file))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
file = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private class ReferencedAssembly
|
||||
{
|
||||
public string Name { get; }
|
||||
public byte[] Data { get; }
|
||||
public AssemblyDefinition Definition { get; }
|
||||
|
||||
public ReferencedAssembly(AssemblyDefinition definition, byte[] rawData)
|
||||
public ReferencedAssemblyInfo(byte[] rawData)
|
||||
{
|
||||
Name = definition.Name.Name;
|
||||
Data = rawData;
|
||||
Definition = definition;
|
||||
}
|
||||
|
||||
public ReferencedAssembly(IFileInfo fileInfo)
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
using (var readStream = fileInfo.CreateReadStream())
|
||||
using (var ms = new MemoryStream(rawData))
|
||||
{
|
||||
readStream.CopyTo(ms);
|
||||
Data = ms.ToArray();
|
||||
Definition = AssemblyDefinition.ReadAssembly(ms);
|
||||
}
|
||||
|
||||
using (var readStream = new MemoryStream(Data))
|
||||
{
|
||||
Definition = AssemblyDefinition.ReadAssembly(readStream);
|
||||
}
|
||||
|
||||
Name = Definition.Name.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Blazor.Server.ClientFilesystem
|
||||
{
|
||||
internal class ReferencedAssemblyResolver
|
||||
{
|
||||
private readonly IFileProvider _bcl;
|
||||
private readonly string _searchDirectory;
|
||||
|
||||
public ReferencedAssemblyResolver(IFileProvider bcl, string searchDirectory)
|
||||
{
|
||||
_bcl = bcl;
|
||||
_searchDirectory = searchDirectory;
|
||||
}
|
||||
|
||||
public bool TryResolve(string name, out byte[] assemblyBytes)
|
||||
{
|
||||
var filename = $"{name}.dll";
|
||||
if (SearchInFileProvider(_bcl, string.Empty, filename, out var fileInfo))
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
using (var fileInfoStream = fileInfo.CreateReadStream())
|
||||
{
|
||||
fileInfoStream.CopyTo(ms);
|
||||
assemblyBytes = ms.ToArray();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var searchDirPath = Path.Combine(_searchDirectory, filename);
|
||||
if (File.Exists(searchDirPath))
|
||||
{
|
||||
assemblyBytes = File.ReadAllBytes(searchDirPath);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
assemblyBytes = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool SearchInFileProvider(IFileProvider fileProvider, string searchRootDirNoTrailingSlash, string name, out IFileInfo file)
|
||||
{
|
||||
var possibleFullPath = $"{searchRootDirNoTrailingSlash}/{name}";
|
||||
var possibleResult = fileProvider.GetFileInfo(possibleFullPath);
|
||||
if (possibleResult.Exists)
|
||||
{
|
||||
file = possibleResult;
|
||||
return true;
|
||||
}
|
||||
|
||||
var childDirs = fileProvider.GetDirectoryContents(searchRootDirNoTrailingSlash)
|
||||
.Where(item => item.IsDirectory);
|
||||
foreach (var childDir in childDirs)
|
||||
{
|
||||
if (SearchInFileProvider(fileProvider, childDir.PhysicalPath, name, out file))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
file = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue