diff --git a/Blazor.sln b/Blazor.sln
index 6206b2d6f1..23ed9142f4 100644
--- a/Blazor.sln
+++ b/Blazor.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.27019.1
+VisualStudioVersion = 15.0.27004.2005
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{F5FDD4E5-6A52-4A86-BE5E-5E42CB1DC8DA}"
EndProject
@@ -38,12 +38,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostedInAspNet.Client", "sa
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostedInAspNet.Server", "samples\HostedInAspNet.Server\HostedInAspNet.Server.csproj", "{F8996835-41F7-4663-91DF-3B5652ADC37D}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Blazor", "src\Microsoft.Blazor\Microsoft.Blazor.csproj", "{7FD8C650-74B3-4153-AEA1-00F4F6AF393D}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Blazor", "src\Microsoft.Blazor\Microsoft.Blazor.csproj", "{7FD8C650-74B3-4153-AEA1-00F4F6AF393D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StandaloneApp", "samples\StandaloneApp\StandaloneApp.csproj", "{B241434A-1642-44CC-AE9A-2012B5C5BD02}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MonoSanity", "MonoSanity", "{2A076721-6081-4517-8329-B9E5110D6DAC}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Blazor.Server.Test", "Microsoft.Blazor.Server.Test\Microsoft.Blazor.Server.Test.csproj", "{71AF445F-0903-4743-B047-44B3B2C19DC9}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -102,6 +104,10 @@ Global
{B241434A-1642-44CC-AE9A-2012B5C5BD02}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B241434A-1642-44CC-AE9A-2012B5C5BD02}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B241434A-1642-44CC-AE9A-2012B5C5BD02}.Release|Any CPU.Build.0 = Release|Any CPU
+ {71AF445F-0903-4743-B047-44B3B2C19DC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {71AF445F-0903-4743-B047-44B3B2C19DC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {71AF445F-0903-4743-B047-44B3B2C19DC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {71AF445F-0903-4743-B047-44B3B2C19DC9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -123,6 +129,7 @@ Global
{7FD8C650-74B3-4153-AEA1-00F4F6AF393D} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
{B241434A-1642-44CC-AE9A-2012B5C5BD02} = {F5FDD4E5-6A52-4A86-BE5E-5E42CB1DC8DA}
{2A076721-6081-4517-8329-B9E5110D6DAC} = {F5FDD4E5-6A52-4A86-BE5E-5E42CB1DC8DA}
+ {71AF445F-0903-4743-B047-44B3B2C19DC9} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {504DA352-6788-4DC0-8705-82167E72A4D3}
diff --git a/Microsoft.Blazor.Server.Test/Microsoft.Blazor.Server.Test.csproj b/Microsoft.Blazor.Server.Test/Microsoft.Blazor.Server.Test.csproj
new file mode 100644
index 0000000000..87ad644bcd
--- /dev/null
+++ b/Microsoft.Blazor.Server.Test/Microsoft.Blazor.Server.Test.csproj
@@ -0,0 +1,20 @@
+
+
+
+ netcoreapp2.0
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Microsoft.Blazor.Server.Test/ReferencedAssemblyFileProviderTest.cs b/Microsoft.Blazor.Server.Test/ReferencedAssemblyFileProviderTest.cs
new file mode 100644
index 0000000000..42ca74ccee
--- /dev/null
+++ b/Microsoft.Blazor.Server.Test/ReferencedAssemblyFileProviderTest.cs
@@ -0,0 +1,25 @@
+// 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.Linq;
+using Xunit;
+
+namespace Microsoft.Blazor.Server.Test
+{
+ public class ReferencedAssemblyFileProviderTest
+ {
+ [Fact]
+ public void FindsEntrypointAssemblyAndReferencedAssemblies()
+ {
+ var provider = new ReferencedAssemblyFileProvider();
+ var contents = provider.GetDirectoryContents(string.Empty).OrderBy(i => i.Name).ToList();
+ Assert.Collection(contents,
+ item => { Assert.Equal("HostedInAspNet.Client.dll", item.PhysicalPath); },
+ item => { Assert.Equal("mscorlib.dll", item.PhysicalPath); },
+ item => { Assert.Equal("System.Console.dll", item.PhysicalPath); },
+ item => { Assert.Equal("System.Core.dll", item.PhysicalPath); },
+ item => { Assert.Equal("System.dll", item.PhysicalPath); },
+ item => { Assert.Equal("System.Runtime.dll", item.PhysicalPath); });
+ }
+ }
+}
diff --git a/samples/HostedInAspNet.Client/Program.cs b/samples/HostedInAspNet.Client/Program.cs
index bd6ec10054..8055591e70 100644
--- a/samples/HostedInAspNet.Client/Program.cs
+++ b/samples/HostedInAspNet.Client/Program.cs
@@ -5,7 +5,7 @@ using System;
namespace HostedInAspNet.Client
{
- class Program
+ public class Program
{
static void Main(string[] args)
{
diff --git a/src/Microsoft.Blazor.Browser/BlazorBrowserFileProvider.cs b/src/Microsoft.Blazor.Browser/BlazorBrowserFileProvider.cs
index e83c802ebc..1d6232020d 100644
--- a/src/Microsoft.Blazor.Browser/BlazorBrowserFileProvider.cs
+++ b/src/Microsoft.Blazor.Browser/BlazorBrowserFileProvider.cs
@@ -13,8 +13,14 @@ namespace Microsoft.Blazor.Browser
typeof(BlazorBrowserFileProvider).Assembly,
"blazor");
+ public static BlazorBrowserFileProvider Instance = new BlazorBrowserFileProvider();
+
+ private BlazorBrowserFileProvider()
+ {
+ }
+
public IFileInfo GetFileInfo(string subpath)
- =>_embeddedFiles.GetFileInfo(subpath.Replace('/', '$'));
+ => _embeddedFiles.GetFileInfo(subpath.Replace('/', '$'));
public IDirectoryContents GetDirectoryContents(string subpath)
=> throw new NotImplementedException(); // Don't need to support this
diff --git a/src/Microsoft.Blazor.Mono/MonoStaticFileProvider.cs b/src/Microsoft.Blazor.Mono/MonoStaticFileProvider.cs
index 231c217509..06f8505e87 100644
--- a/src/Microsoft.Blazor.Mono/MonoStaticFileProvider.cs
+++ b/src/Microsoft.Blazor.Mono/MonoStaticFileProvider.cs
@@ -13,6 +13,12 @@ namespace Microsoft.Blazor.Mono
typeof(MonoStaticFileProvider).Assembly,
"mono");
+ public static MonoStaticFileProvider Instance = new MonoStaticFileProvider();
+
+ private MonoStaticFileProvider()
+ {
+ }
+
public IFileInfo GetFileInfo(string subpath)
{
// EmbeddedFileProvider can't find resources whose names include '/' (or '\'),
@@ -22,7 +28,7 @@ namespace Microsoft.Blazor.Mono
}
public IDirectoryContents GetDirectoryContents(string subpath)
- => throw new NotImplementedException(); // Don't need to support this
+ => _embeddedFiles.GetDirectoryContents(subpath);
public IChangeToken Watch(string filter)
=> throw new NotImplementedException(); // Don't need to support this
diff --git a/src/Microsoft.Blazor.Server/BlazorAppBuilderExtensions.cs b/src/Microsoft.Blazor.Server/BlazorAppBuilderExtensions.cs
index 68932c449a..0b1627734b 100644
--- a/src/Microsoft.Blazor.Server/BlazorAppBuilderExtensions.cs
+++ b/src/Microsoft.Blazor.Server/BlazorAppBuilderExtensions.cs
@@ -18,8 +18,8 @@ namespace Microsoft.AspNetCore.Builder
{
RequestPath = "/_framework",
FileProvider = new CompositeFileProvider(
- new MonoStaticFileProvider(),
- new BlazorBrowserFileProvider()),
+ MonoStaticFileProvider.Instance,
+ BlazorBrowserFileProvider.Instance),
ContentTypeProvider = CreateContentTypeProvider(),
});
}
diff --git a/src/Microsoft.Blazor.Server/Microsoft.Blazor.Server.csproj b/src/Microsoft.Blazor.Server/Microsoft.Blazor.Server.csproj
index f57984dbac..5609517a53 100644
--- a/src/Microsoft.Blazor.Server/Microsoft.Blazor.Server.csproj
+++ b/src/Microsoft.Blazor.Server/Microsoft.Blazor.Server.csproj
@@ -8,6 +8,7 @@
+
diff --git a/src/Microsoft.Blazor.Server/Properties/AssemblyInfo.cs b/src/Microsoft.Blazor.Server/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..015cbd4cb3
--- /dev/null
+++ b/src/Microsoft.Blazor.Server/Properties/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.Blazor.Server.Test")]
diff --git a/src/Microsoft.Blazor.Server/ReferencedAssemblyFileProvider.cs b/src/Microsoft.Blazor.Server/ReferencedAssemblyFileProvider.cs
new file mode 100644
index 0000000000..8f1018d61d
--- /dev/null
+++ b/src/Microsoft.Blazor.Server/ReferencedAssemblyFileProvider.cs
@@ -0,0 +1,169 @@
+// 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 Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.Primitives;
+using System.Collections.Generic;
+using System.Collections;
+using System.Linq;
+using Mono.Cecil;
+using Microsoft.Blazor.Mono;
+
+namespace Microsoft.Blazor.Server
+{
+ internal class ReferencedAssemblyFileProvider : IFileProvider
+ {
+ private static readonly Dictionary _frameworkDlls = ReadFrameworkDlls();
+ private readonly Contents _referencedAssemblyContents = new Contents(FindReferencedAssemblies());
+ private readonly Contents _emptyContents = new Contents(null);
+
+ public IDirectoryContents GetDirectoryContents(string subpath)
+ {
+ return subpath == string.Empty
+ ? _referencedAssemblyContents
+ : _emptyContents;
+ }
+
+ public IFileInfo GetFileInfo(string subpath)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public IChangeToken Watch(string filter)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ private static IEnumerable FindReferencedAssemblies()
+ {
+ var foundAssemblies = new Dictionary();
+ FindReferencedAssembliesRecursive(
+ AssemblyDefinition.ReadAssembly(typeof(TApp).Assembly.Location),
+ foundAssemblies);
+ return foundAssemblies.Values;
+ }
+
+ private static void FindReferencedAssembliesRecursive(AssemblyDefinition root, IDictionary results)
+ {
+ results.Add(root.Name.Name, root);
+
+ foreach (var module in root.Modules)
+ {
+ foreach (var referenceName in module.AssemblyReferences)
+ {
+ if (!results.ContainsKey(referenceName.Name))
+ {
+ var resolvedReference = FindAssembly(referenceName, module.AssemblyResolver);
+
+ // 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.
+ if (resolvedReference != null)
+ {
+ FindReferencedAssembliesRecursive(resolvedReference, results);
+ }
+ }
+ }
+ }
+ }
+
+ private static AssemblyDefinition FindAssembly(AssemblyNameReference name, IAssemblyResolver nativeResolver)
+ {
+ try
+ {
+ return _frameworkDlls.TryGetValue($"{name.Name}.dll", out var fileInfo)
+ ? AssemblyDefinition.ReadAssembly(fileInfo.CreateReadStream())
+ : AllowNativeDllResolution(name) ? nativeResolver.Resolve(name) : null;
+ }
+ catch (AssemblyResolutionException)
+ {
+ return null;
+ }
+ }
+
+ private static bool AllowNativeDllResolution(AssemblyNameReference name)
+ {
+ // System.* assemblies must only be resolved from the browser-reachable FrameworkFiles.
+ // It's no use resolving them using the native resolver, because those files wouldn't
+ // be accessible at runtime anyway.
+ return !name.Name.StartsWith("System.", StringComparison.Ordinal);
+ }
+
+ private static Dictionary ReadFrameworkDlls()
+ {
+ // TODO: Stop leaking knowledge of the Microsoft.Blazor.Mono file provider internal
+ // structure into this unrelated class. Currently it's needed because that file provider
+ // doesn't support proper directory hierarchies and therefore keeps all files in the
+ // top-level directory, putting '$' into filenames in place of directories.
+ // To fix this, make MonoStaticFileProvider expose a regular directory structure, and
+ // then change this method to walk it recursively.
+
+ return MonoStaticFileProvider.Instance
+ .GetDirectoryContents(string.Empty)
+ .Where(file => file.Name.EndsWith(".dll", StringComparison.Ordinal))
+ .ToDictionary(MonoEmbeddedResourceToFilename, file => file);
+
+ string MonoEmbeddedResourceToFilename(IFileInfo fileInfo)
+ {
+ var name = fileInfo.Name;
+ var lastDirSeparatorPos = name.LastIndexOf('$');
+ return name.Substring(lastDirSeparatorPos + 1);
+ }
+ }
+
+ private class Contents : IDirectoryContents
+ {
+ private readonly bool _exists;
+ private readonly IReadOnlyDictionary _items;
+
+ public Contents(IEnumerable assemblies)
+ {
+ _exists = assemblies != null;
+ _items = (assemblies ?? Enumerable.Empty())
+ .Select(assembly => new AssemblyFileInfo(assembly))
+ .ToDictionary(item => item.Name, item => (IFileInfo)item);
+ }
+
+ public bool Exists => _exists;
+
+ public IEnumerator GetEnumerator() => _items.Values.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => _items.Values.GetEnumerator();
+ }
+
+ private class AssemblyFileInfo : IFileInfo
+ {
+ private readonly byte[] _data;
+
+ public bool Exists => true;
+
+ public long Length => _data.Length;
+
+ public string PhysicalPath => Name;
+
+ public string Name { get; }
+
+ public DateTimeOffset LastModified => default(DateTimeOffset);
+
+ public bool IsDirectory => false;
+
+ public Stream CreateReadStream()
+ {
+ return new MemoryStream(_data);
+ }
+
+ public AssemblyFileInfo(AssemblyDefinition assembly)
+ {
+ Name = $"{assembly.Name.Name}.dll";
+
+ using (var ms = new MemoryStream())
+ {
+ assembly.Write(ms);
+ _data = ms.GetBuffer();
+ }
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Blazor.Mono.Test/MonoStaticFileProviderTest.cs b/test/Microsoft.Blazor.Mono.Test/MonoStaticFileProviderTest.cs
index fe3e175817..d4f255ac22 100644
--- a/test/Microsoft.Blazor.Mono.Test/MonoStaticFileProviderTest.cs
+++ b/test/Microsoft.Blazor.Mono.Test/MonoStaticFileProviderTest.cs
@@ -10,8 +10,6 @@ namespace Microsoft.Blazor.Mono.Test
[Fact]
public void SuppliesMonoFiles()
{
- var provider = new MonoStaticFileProvider();
-
// This is not an exhaustive list. The set of BCL facade types is long and
// will probably change. This test is just to verify the resource embedding
// and filename mapping is working correctly.
@@ -26,7 +24,7 @@ namespace Microsoft.Blazor.Mono.Test
foreach (var name in expectedFiles)
{
- var fileInfo = provider.GetFileInfo(name);
+ var fileInfo = MonoStaticFileProvider.Instance.GetFileInfo(name);
Assert.True(fileInfo.Exists);
Assert.False(fileInfo.IsDirectory);
Assert.True(fileInfo.Length > 0);
@@ -36,8 +34,6 @@ namespace Microsoft.Blazor.Mono.Test
[Fact]
public void DoesNotSupplyUnexpectedFiles()
{
- var provider = new MonoStaticFileProvider();
-
var notExpectedFiles = new[]
{
"",
@@ -51,7 +47,7 @@ namespace Microsoft.Blazor.Mono.Test
foreach (var name in notExpectedFiles)
{
- var fileInfo = provider.GetFileInfo(name);
+ var fileInfo = MonoStaticFileProvider.Instance.GetFileInfo(name);
Assert.False(fileInfo.Exists);
}
}