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]
|
[Fact]
|
||||||
public void RootDirContainsOnlyBinDir()
|
public void RootDirContainsOnlyBinDir()
|
||||||
{
|
{
|
||||||
var (entrypoint, entrypointData) = GetBclAssemblyForTest("mscorlib");
|
|
||||||
var provider = new ReferencedAssemblyFileProvider(
|
var provider = new ReferencedAssemblyFileProvider(
|
||||||
entrypoint,
|
"mscorlib",
|
||||||
entrypointData,
|
new ReferencedAssemblyResolver(MonoStaticFileProvider.BclFiles, string.Empty));
|
||||||
MonoStaticFileProvider.BclFiles);
|
|
||||||
Assert.Collection(provider.GetDirectoryContents("/"), item =>
|
Assert.Collection(provider.GetDirectoryContents("/"), item =>
|
||||||
{
|
{
|
||||||
Assert.Equal("/_bin", item.PhysicalPath);
|
Assert.Equal("/_bin", item.PhysicalPath);
|
||||||
|
|
@ -31,11 +29,9 @@ namespace Microsoft.Blazor.Server.Test
|
||||||
[Fact]
|
[Fact]
|
||||||
public void FindsReferencedAssemblyGraphSimple()
|
public void FindsReferencedAssemblyGraphSimple()
|
||||||
{
|
{
|
||||||
var (entrypoint, entrypointData) = GetBclAssemblyForTest("System.Linq.Expressions");
|
|
||||||
var provider = new ReferencedAssemblyFileProvider(
|
var provider = new ReferencedAssemblyFileProvider(
|
||||||
entrypoint,
|
"System.Linq.Expressions",
|
||||||
entrypointData,
|
new ReferencedAssemblyResolver(MonoStaticFileProvider.BclFiles, string.Empty));
|
||||||
MonoStaticFileProvider.BclFiles);
|
|
||||||
var contents = provider.GetDirectoryContents("/_bin").OrderBy(i => i.Name).ToList();
|
var contents = provider.GetDirectoryContents("/_bin").OrderBy(i => i.Name).ToList();
|
||||||
Assert.Collection(contents,
|
Assert.Collection(contents,
|
||||||
item => { Assert.Equal("/_bin/mscorlib.dll", item.PhysicalPath); },
|
item => { Assert.Equal("/_bin/mscorlib.dll", item.PhysicalPath); },
|
||||||
|
|
@ -48,11 +44,12 @@ namespace Microsoft.Blazor.Server.Test
|
||||||
public void FindsReferencedAssemblyGraphRealistic()
|
public void FindsReferencedAssemblyGraphRealistic()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var standaloneAppAssemblyLocation = typeof(StandaloneApp.Program).Assembly.Location;
|
var standaloneAppAssembly = typeof(StandaloneApp.Program).Assembly;
|
||||||
var provider = new ReferencedAssemblyFileProvider(
|
var provider = new ReferencedAssemblyFileProvider(
|
||||||
AssemblyDefinition.ReadAssembly(standaloneAppAssemblyLocation),
|
standaloneAppAssembly.GetName().Name,
|
||||||
File.ReadAllBytes(standaloneAppAssemblyLocation),
|
new ReferencedAssemblyResolver(
|
||||||
MonoStaticFileProvider.BclFiles);
|
MonoStaticFileProvider.BclFiles,
|
||||||
|
Path.GetDirectoryName(standaloneAppAssembly.Location)));
|
||||||
var expectedContents = new[]
|
var expectedContents = new[]
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ namespace HostedInAspNet.Server
|
||||||
app.UseBlazorDevelopmentServer("../HostedInAspNet.Client");
|
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.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using MonoSanityClient;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net.Mime;
|
|
||||||
|
|
||||||
namespace MonoSanity
|
namespace MonoSanity
|
||||||
{
|
{
|
||||||
|
|
@ -15,7 +12,7 @@ namespace MonoSanity
|
||||||
{
|
{
|
||||||
app.UseDeveloperExceptionPage();
|
app.UseDeveloperExceptionPage();
|
||||||
app.UseFileServer();
|
app.UseFileServer();
|
||||||
app.UseBlazor(clientAssembly: typeof(MonoSanityClient.Examples).Assembly);
|
app.UseBlazor(clientAssemblyPath: typeof(MonoSanityClient.Examples).Assembly.Location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Hello</h1>
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.Blazor.DevHost.Server
|
||||||
{
|
{
|
||||||
app.UseDeveloperExceptionPage();
|
app.UseDeveloperExceptionPage();
|
||||||
app.UseBlazorDevelopmentServer(".");
|
app.UseBlazorDevelopmentServer(".");
|
||||||
app.UseBlazor(clientAssembly: FindClientAssembly(app));
|
app.UseBlazor(clientAssemblyPath: FindClientAssembly(app).Location);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Assembly FindClientAssembly(IApplicationBuilder app)
|
private static Assembly FindClientAssembly(IApplicationBuilder app)
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,12 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
{
|
{
|
||||||
public static void UseBlazor(
|
public static void UseBlazor(
|
||||||
this IApplicationBuilder applicationBuilder,
|
this IApplicationBuilder applicationBuilder,
|
||||||
Assembly clientAssembly)
|
string clientAssemblyPath)
|
||||||
{
|
{
|
||||||
applicationBuilder.UseStaticFiles(new StaticFileOptions
|
applicationBuilder.UseStaticFiles(new StaticFileOptions
|
||||||
{
|
{
|
||||||
RequestPath = "/_framework",
|
RequestPath = "/_framework",
|
||||||
FileProvider = ClientFileProvider.Instantiate(clientAssembly),
|
FileProvider = ClientFileProvider.Instantiate(clientAssemblyPath),
|
||||||
ContentTypeProvider = CreateContentTypeProvider(),
|
ContentTypeProvider = CreateContentTypeProvider(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,21 @@
|
||||||
using Microsoft.Blazor.Browser;
|
using Microsoft.Blazor.Browser;
|
||||||
using Microsoft.Blazor.Mono;
|
using Microsoft.Blazor.Mono;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Microsoft.Blazor.Server.ClientFilesystem
|
namespace Microsoft.Blazor.Server.ClientFilesystem
|
||||||
{
|
{
|
||||||
internal static class ClientFileProvider
|
internal static class ClientFileProvider
|
||||||
{
|
{
|
||||||
public static IFileProvider Instantiate(Assembly clientApp)
|
public static IFileProvider Instantiate(string clientAssemblyPath)
|
||||||
=> new CompositeFileProvider(
|
=> new CompositeFileProvider(
|
||||||
MonoStaticFileProvider.JsFiles,
|
MonoStaticFileProvider.JsFiles,
|
||||||
BlazorBrowserFileProvider.Instance,
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.Extensions.FileProviders;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Mono.Cecil;
|
using Mono.Cecil;
|
||||||
using Microsoft.Blazor.Internal.Common.FileProviders;
|
using Microsoft.Blazor.Internal.Common.FileProviders;
|
||||||
using System.Reflection;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Microsoft.Blazor.Server.ClientFilesystem
|
namespace Microsoft.Blazor.Server.ClientFilesystem
|
||||||
{
|
{
|
||||||
|
|
@ -16,152 +13,57 @@ namespace Microsoft.Blazor.Server.ClientFilesystem
|
||||||
{
|
{
|
||||||
private const string ClientBinDir = "_bin";
|
private const string ClientBinDir = "_bin";
|
||||||
|
|
||||||
public ReferencedAssemblyFileProvider(Assembly assembly, IFileProvider clientBcl) : this(
|
public ReferencedAssemblyFileProvider(string rootAssemblyName, ReferencedAssemblyResolver resolver)
|
||||||
AssemblyDefinition.ReadAssembly(assembly.Location),
|
: base(ComputeContents(rootAssemblyName, resolver))
|
||||||
File.ReadAllBytes(assembly.Location),
|
|
||||||
clientBcl)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReferencedAssemblyFileProvider(
|
|
||||||
AssemblyDefinition entrypoint,
|
|
||||||
byte[] entrypointData,
|
|
||||||
IFileProvider clientBcl)
|
|
||||||
: base(ComputeContents(entrypoint, entrypointData, clientBcl))
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<(string, Stream)> ComputeContents(
|
private static IEnumerable<(string, Stream)> ComputeContents(
|
||||||
AssemblyDefinition entrypoint,
|
string rootAssemblyName,
|
||||||
byte[] entrypointData,
|
ReferencedAssemblyResolver resolver)
|
||||||
IFileProvider clientBcl)
|
|
||||||
{
|
{
|
||||||
var foundAssemblies = new Dictionary<string, ReferencedAssembly>();
|
var foundAssemblies = new Dictionary<string, ReferencedAssemblyInfo>();
|
||||||
AddWithReferencesRecursive(new ReferencedAssembly(entrypoint, entrypointData), clientBcl, foundAssemblies);
|
AddWithReferencesRecursive(rootAssemblyName, resolver, foundAssemblies);
|
||||||
|
|
||||||
return foundAssemblies.Values.Select(assembly => (
|
return foundAssemblies.Values.Select(assembly => (
|
||||||
$"/{ClientBinDir}/{assembly.Name}.dll",
|
$"/{ClientBinDir}/{assembly.Definition.Name.Name}.dll",
|
||||||
(Stream)new MemoryStream(assembly.Data)));
|
(Stream)new MemoryStream(assembly.Data)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddWithReferencesRecursive(
|
private static void AddWithReferencesRecursive(
|
||||||
ReferencedAssembly root,
|
string name,
|
||||||
IFileProvider clientBcl,
|
ReferencedAssemblyResolver resolver,
|
||||||
IDictionary<string, ReferencedAssembly> results)
|
IDictionary<string, ReferencedAssemblyInfo> results)
|
||||||
{
|
{
|
||||||
results.Add(root.Name, root);
|
if (resolver.TryResolve(name, out var assemblyBytes))
|
||||||
|
|
||||||
foreach (var module in root.Definition.Modules)
|
|
||||||
{
|
{
|
||||||
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))
|
AddWithReferencesRecursive(childReferenceName, resolver, results);
|
||||||
{
|
|
||||||
var resolvedReference = ResolveReference(clientBcl, module, referenceName);
|
|
||||||
if (resolvedReference != null)
|
|
||||||
{
|
|
||||||
AddWithReferencesRecursive(resolvedReference, clientBcl, 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 byte[] Data { get; }
|
||||||
public AssemblyDefinition Definition { get; }
|
public AssemblyDefinition Definition { get; }
|
||||||
|
|
||||||
public ReferencedAssembly(AssemblyDefinition definition, byte[] rawData)
|
public ReferencedAssemblyInfo(byte[] rawData)
|
||||||
{
|
{
|
||||||
Name = definition.Name.Name;
|
|
||||||
Data = rawData;
|
Data = rawData;
|
||||||
Definition = definition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReferencedAssembly(IFileInfo fileInfo)
|
using (var ms = new MemoryStream(rawData))
|
||||||
{
|
|
||||||
using (var ms = new MemoryStream())
|
|
||||||
using (var readStream = fileInfo.CreateReadStream())
|
|
||||||
{
|
{
|
||||||
readStream.CopyTo(ms);
|
Definition = AssemblyDefinition.ReadAssembly(ms);
|
||||||
Data = ms.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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