Initial debugger support

This commit is contained in:
Steve Sanderson 2018-07-03 14:57:10 +01:00
parent c881a63a78
commit cafb56569d
22 changed files with 411 additions and 86 deletions

View File

@ -68,7 +68,7 @@
<script type="blazor-boot"></script> <script type="blazor-boot"></script>
<script src="loader.js"></script> <script src="loader.js"></script>
<script> <script>
initMono(['/_framework/_bin/MonoSanityClient.dll'], function () { initMono(['_framework/_bin/MonoSanityClient.dll', '_framework/_bin/MonoSanityClient.pdb'], function () {
var buttons = document.getElementsByTagName('button'); var buttons = document.getElementsByTagName('button');
for (var i = 0; i < buttons.length; i++) { for (var i = 0; i < buttons.length; i++) {
buttons[i].disabled = false; buttons[i].disabled = false;

View File

@ -30,8 +30,9 @@
preloadAssemblies(loadAssemblyUrls); preloadAssemblies(loadAssemblyUrls);
}], }],
postRun: [function () { postRun: [function () {
var load_runtime = Module.cwrap('mono_wasm_load_runtime', null, ['string']); var load_runtime = Module.cwrap('mono_wasm_load_runtime', null, ['string', 'number']);
load_runtime('appBinDir'); load_runtime('appBinDir', 1);
MONO.mono_wasm_runtime_is_ready = true;
onReadyCallback(); onReadyCallback();
}] }]
}; };
@ -85,13 +86,22 @@
.concat(loadBclAssemblies.map(function (name) { return '_framework/_bin/' + name + '.dll'; })); .concat(loadBclAssemblies.map(function (name) { return '_framework/_bin/' + name + '.dll'; }));
Module.FS_createPath('/', 'appBinDir', true, true); Module.FS_createPath('/', 'appBinDir', true, true);
MONO.loaded_files = []; // Used by debugger
allAssemblyUrls.forEach(function (url) { allAssemblyUrls.forEach(function (url) {
FS.createPreloadedFile('appBinDir', getAssemblyNameFromUrl(url) + '.dll', url, true, false, null, function onError(err) { FS.createPreloadedFile('appBinDir', getFileNameFromUrl(url), url, true, false,
throw err; /* success */ function() { MONO.loaded_files.push(toAbsoluteUrl(url)); },
}); /* failure */ function onError(err) { throw err; }
);
}); });
} }
var anchorTagForAbsoluteUrlConversions = document.createElement('a');
function toAbsoluteUrl(possiblyRelativeUrl) {
anchorTagForAbsoluteUrlConversions.href = possiblyRelativeUrl;
return anchorTagForAbsoluteUrlConversions.href;
}
function asyncLoad(url, onload, onerror) { function asyncLoad(url, onload, onerror) {
var xhr = new XMLHttpRequest; var xhr = new XMLHttpRequest;
xhr.open('GET', url, /* async: */ true); xhr.open('GET', url, /* async: */ true);
@ -161,11 +171,11 @@
document.body.appendChild(scriptElem); document.body.appendChild(scriptElem);
} }
function getAssemblyNameFromUrl(url) { function getFileNameFromUrl(url) {
var lastSegment = url.substring(url.lastIndexOf('/') + 1); var lastSegment = url.substring(url.lastIndexOf('/') + 1);
var queryStringStartPos = lastSegment.indexOf('?'); var queryStringStartPos = lastSegment.indexOf('?');
var filename = queryStringStartPos < 0 ? lastSegment : lastSegment.substring(0, queryStringStartPos); var filename = queryStringStartPos < 0 ? lastSegment : lastSegment.substring(0, queryStringStartPos);
return filename.replace(/\.dll$/, ''); return filename;
} }
})(); })();

View File

@ -1,9 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Razor" TreatAsLocalProperty="BlazorLinkOnBuild"> <Project Sdk="Microsoft.NET.Sdk.Razor" TreatAsLocalProperty="BlazorLinkOnBuild">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<BlazorLinkOnBuild>false</BlazorLinkOnBuild> <BlazorLinkOnBuild>false</BlazorLinkOnBuild>
<!-- loader.js is hard-coded to assume it can load .pdbs regardless of Debug/Release configuration -->
<BlazorEnableDebugging>true</BlazorEnableDebugging>
</PropertyGroup> </PropertyGroup>
<!-- Local alternative to <PackageReference Include="Microsoft.AspNetCore.Blazor.Build" /> --> <!-- Local alternative to <PackageReference Include="Microsoft.AspNetCore.Blazor.Build" /> -->

View File

@ -1,6 +1,6 @@
import '../../Microsoft.JSInterop/JavaScriptRuntime/src/Microsoft.JSInterop'; import '../../Microsoft.JSInterop/JavaScriptRuntime/src/Microsoft.JSInterop';
import { platform } from './Environment'; import { platform } from './Environment';
import { getAssemblyNameFromUrl } from './Platform/DotNet'; import { getAssemblyNameFromUrl } from './Platform/Url';
import './GlobalExports'; import './GlobalExports';
async function boot() { async function boot() {

View File

@ -1,6 +0,0 @@
export function getAssemblyNameFromUrl(url: string) {
const lastSegment = url.substring(url.lastIndexOf('/') + 1);
const queryStringStartPos = lastSegment.indexOf('?');
const filename = queryStringStartPos < 0 ? lastSegment : lastSegment.substring(0, queryStringStartPos);
return filename.replace(/\.dll$/, '');
}

View File

@ -0,0 +1,50 @@
import { getAssemblyNameFromUrl, getFileNameFromUrl } from '../Url';
const currentBrowserIsChrome = (window as any).chrome
&& navigator.userAgent.indexOf('Edge') < 0; // Edge pretends to be Chrome
let hasReferencedPdbs = false;
export function hasDebuggingEnabled() {
return hasReferencedPdbs && currentBrowserIsChrome;
}
export function attachDebuggerHotkey(loadAssemblyUrls: string[]) {
hasReferencedPdbs = loadAssemblyUrls
.some(url => /\.pdb$/.test(getFileNameFromUrl(url)));
// Use the combination shift+alt+D because it isn't used by the major browsers
// for anything else by default
const altKeyName = navigator.platform.match(/^Mac/i) ? 'Cmd' : 'Alt';
if (hasDebuggingEnabled()) {
console.info(`Debugging hotkey: Shift+${altKeyName}+D (when application has focus)`);
}
// Even if debugging isn't enabled, we register the hotkey so we can report why it's not enabled
document.addEventListener('keydown', evt => {
if (evt.shiftKey && (evt.metaKey || evt.altKey) && evt.code === 'KeyD') {
if (!hasReferencedPdbs) {
console.error('Cannot start debugging, because the application was not compiled with debugging enabled.');
} else if (!currentBrowserIsChrome) {
console.error('Currently, only Chrome is supported for debugging.');
} else {
launchDebugger();
}
}
});
}
function launchDebugger() {
// The noopener flag is essential, because otherwise Chrome tracks the association with the
// parent tab, and then when the parent tab pauses in the debugger, the child tab does so
// too (even if it's since navigated to a different page). This means that the debugger
// itself freezes, and not just the page being debugged.
//
// We have to construct a link element and simulate a click on it, because the more obvious
// window.open(..., 'noopener') always opens a new window instead of a new tab.
const link = document.createElement('a');
link.href = `_framework/debug?url=${encodeURIComponent(location.href)}`;
link.target = '_blank';
link.rel = 'noopener noreferrer';
link.click();
}

View File

@ -1,5 +1,6 @@
import { MethodHandle, System_Object, System_String, System_Array, Pointer, Platform } from '../Platform'; import { MethodHandle, System_Object, System_String, System_Array, Pointer, Platform } from '../Platform';
import { getAssemblyNameFromUrl } from '../DotNet'; import { getAssemblyNameFromUrl, getFileNameFromUrl } from '../Url';
import { attachDebuggerHotkey, hasDebuggingEnabled } from './MonoDebugger';
const assemblyHandleCache: { [assemblyName: string]: number } = {}; const assemblyHandleCache: { [assemblyName: string]: number } = {};
const typeHandleCache: { [fullyQualifiedTypeName: string]: number } = {}; const typeHandleCache: { [fullyQualifiedTypeName: string]: number } = {};
@ -11,14 +12,16 @@ let find_method: (typeHandle: number, methodName: string, unknownArg: number) =>
let invoke_method: (method: MethodHandle, target: System_Object, argsArrayPtr: number, exceptionFlagIntPtr: number) => System_Object; let invoke_method: (method: MethodHandle, target: System_Object, argsArrayPtr: number, exceptionFlagIntPtr: number) => System_Object;
let mono_string_get_utf8: (managedString: System_String) => Mono.Utf8Ptr; let mono_string_get_utf8: (managedString: System_String) => Mono.Utf8Ptr;
let mono_string: (jsString: string) => System_String; let mono_string: (jsString: string) => System_String;
const appBinDirName = 'appBinDir';
export const monoPlatform: Platform = { export const monoPlatform: Platform = {
start: function start(loadAssemblyUrls: string[]) { start: function start(loadAssemblyUrls: string[]) {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
attachDebuggerHotkey(loadAssemblyUrls);
// mono.js assumes the existence of this // mono.js assumes the existence of this
window['Browser'] = { window['Browser'] = {
init: () => { }, init: () => { }
asyncLoad: asyncLoad
}; };
// Emscripten works by expecting the module config to be a global // Emscripten works by expecting the module config to be a global
window['Module'] = createEmscriptenModuleInstance(loadAssemblyUrls, resolve, reject); window['Module'] = createEmscriptenModuleInstance(loadAssemblyUrls, resolve, reject);
@ -192,8 +195,9 @@ function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: ()
const module = {} as typeof Module; const module = {} as typeof Module;
const wasmBinaryFile = '_framework/wasm/mono.wasm'; const wasmBinaryFile = '_framework/wasm/mono.wasm';
const asmjsCodeFile = '_framework/asmjs/mono.asm.js'; const asmjsCodeFile = '_framework/asmjs/mono.asm.js';
const suppressMessages = ['DEBUGGING ENABLED'];
module.print = line => console.log(`WASM: ${line}`); module.print = line => (suppressMessages.indexOf(line) < 0 && console.log(`WASM: ${line}`));
module.printErr = line => console.error(`WASM: ${line}`); module.printErr = line => console.error(`WASM: ${line}`);
module.preRun = []; module.preRun = [];
module.postRun = []; module.postRun = [];
@ -216,14 +220,39 @@ function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: ()
mono_string_get_utf8 = Module.cwrap('mono_wasm_string_get_utf8', 'number', ['number']); mono_string_get_utf8 = Module.cwrap('mono_wasm_string_get_utf8', 'number', ['number']);
mono_string = Module.cwrap('mono_wasm_string_from_js', 'number', ['string']); mono_string = Module.cwrap('mono_wasm_string_from_js', 'number', ['string']);
Module.FS_createPath('/', 'appBinDir', true, true); Module.FS_createPath('/', appBinDirName, true, true);
loadAssemblyUrls.forEach(url => MONO.loaded_files = [];
FS.createPreloadedFile('appBinDir', `${getAssemblyNameFromUrl(url)}.dll`, url, true, false, undefined, onError));
loadAssemblyUrls.forEach(url => {
const filename = getFileNameFromUrl(url);
const runDependencyId = `blazor:${filename}`;
addRunDependency(runDependencyId);
asyncLoad(url).then(
data => {
Module.FS_createDataFile(appBinDirName, filename, data, true, false, false);
MONO.loaded_files.push(toAbsoluteUrl(url));
removeRunDependency(runDependencyId);
},
errorInfo => {
// If it's a 404 on a .pdb, we don't want to block the app from starting up.
// We'll just skip that file and continue (though the 404 is logged in the console).
// This happens if you build a Debug build but then run in Production environment.
const isPdb404 = errorInfo instanceof XMLHttpRequest
&& errorInfo.status === 404
&& filename.match(/\.pdb$/);
if (!isPdb404) {
onError(errorInfo);
}
removeRunDependency(runDependencyId);
}
);
});
}); });
module.postRun.push(() => { module.postRun.push(() => {
const load_runtime = Module.cwrap('mono_wasm_load_runtime', null, ['string']); const load_runtime = Module.cwrap('mono_wasm_load_runtime', null, ['string', 'number']);
load_runtime('appBinDir'); load_runtime(appBinDirName, hasDebuggingEnabled() ? 1 : 0);
MONO.mono_wasm_runtime_is_ready = true;
attachInteropInvoker(); attachInteropInvoker();
onReady(); onReady();
}); });
@ -231,20 +260,28 @@ function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: ()
return module; return module;
} }
function asyncLoad(url, onload, onerror) { const anchorTagForAbsoluteUrlConversions = document.createElement('a');
var xhr = new XMLHttpRequest; function toAbsoluteUrl(possiblyRelativeUrl: string) {
xhr.open('GET', url, /* async: */ true); anchorTagForAbsoluteUrlConversions.href = possiblyRelativeUrl;
xhr.responseType = 'arraybuffer'; return anchorTagForAbsoluteUrlConversions.href;
xhr.onload = function xhr_onload() { }
if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
var asm = new Uint8Array(xhr.response); function asyncLoad(url) {
onload(asm); return new Promise((resolve, reject) => {
} else { var xhr = new XMLHttpRequest;
onerror(xhr); xhr.open('GET', url, /* async: */ true);
} xhr.responseType = 'arraybuffer';
}; xhr.onload = function xhr_onload() {
xhr.onerror = onerror; if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
xhr.send(null); var asm = new Uint8Array(xhr.response);
resolve(asm);
} else {
reject(xhr);
}
};
xhr.onerror = reject;
xhr.send(null);
});
} }
function getArrayDataPointer<T>(array: System_Array<T>): number { function getArrayDataPointer<T>(array: System_Array<T>): number {

View File

@ -1,4 +1,4 @@
declare namespace Module { declare namespace Module {
function UTF8ToString(utf8: Mono.Utf8Ptr): string; function UTF8ToString(utf8: Mono.Utf8Ptr): string;
var preloadPlugins: any[]; var preloadPlugins: any[];
@ -11,7 +11,17 @@
function FS_createDataFile(parent, name, data, canRead, canWrite, canOwn); function FS_createDataFile(parent, name, data, canRead, canWrite, canOwn);
} }
// Emscripten declares these globals
declare const addRunDependency: any;
declare const removeRunDependency: any;
declare namespace Mono { declare namespace Mono {
interface Utf8Ptr { Utf8Ptr__DO_NOT_IMPLEMENT: any } interface Utf8Ptr { Utf8Ptr__DO_NOT_IMPLEMENT: any }
interface StackSaveHandle { StackSaveHandle__DO_NOT_IMPLEMENT: any } interface StackSaveHandle { StackSaveHandle__DO_NOT_IMPLEMENT: any }
} }
// Mono uses this global to hang various debugging-related items on
declare namespace MONO {
var loaded_files: string[];
var mono_wasm_runtime_is_ready: boolean;
}

View File

@ -0,0 +1,11 @@
export function getFileNameFromUrl(url: string) {
// This could also be called "get last path segment from URL", but the primary
// use case is to extract things that look like filenames
const lastSegment = url.substring(url.lastIndexOf('/') + 1);
const queryStringStartPos = lastSegment.indexOf('?');
return queryStringStartPos < 0 ? lastSegment : lastSegment.substring(0, queryStringStartPos);
}
export function getAssemblyNameFromUrl(url: string) {
return getFileNameFromUrl(url).replace(/\.dll$/, '');
}

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
@ -45,7 +46,14 @@ namespace Microsoft.AspNetCore.Blazor.Build
assemblyResolutionContext.ResolveAssemblies(); assemblyResolutionContext.ResolveAssemblies();
var paths = assemblyResolutionContext.Results.Select(r => r.Path); var paths = assemblyResolutionContext.Results.Select(r => r.Path);
return paths; return paths.Concat(FindPdbs(paths));
}
private static IEnumerable<string> FindPdbs(IEnumerable<string> dllPaths)
{
return dllPaths
.Select(path => Path.ChangeExtension(path, "pdb"))
.Where(path => File.Exists(path));
} }
public class AssemblyResolutionContext public class AssemblyResolutionContext

View File

@ -1,12 +1,15 @@
<Project> <Project>
<Import Project="Blazor.MonoRuntime.props" /> <Import Project="Blazor.MonoRuntime.props" />
<PropertyGroup> <PropertyGroup>
<DefaultWebContentItemExcludes>$(DefaultWebContentItemExcludes);wwwroot\**</DefaultWebContentItemExcludes> <DefaultWebContentItemExcludes>$(DefaultWebContentItemExcludes);wwwroot\**</DefaultWebContentItemExcludes>
<!-- By default, enable auto rebuilds for debug builds. Note that the server will not enable it in production environments regardless. --> <!-- By default, enable auto rebuilds for debug builds. Note that the server will not enable it in production environments regardless. -->
<BlazorRebuildOnFileChange Condition="'$(Configuration)' == 'Debug'">true</BlazorRebuildOnFileChange> <BlazorRebuildOnFileChange Condition="'$(Configuration)' == 'Debug' AND '$(BlazorRebuildOnFileChange)' == ''">true</BlazorRebuildOnFileChange>
<!-- By default, enable debugging for debug builds. -->
<BlazorEnableDebugging Condition="'$(Configuration)' == 'Debug' AND '$(BlazorEnableDebugging)' == ''">true</BlazorEnableDebugging>
<!-- When using IISExpress with a standalone app, there's no point restarting IISExpress after build. It slows things unnecessarily and breaks in-flight HTTP requests. --> <!-- When using IISExpress with a standalone app, there's no point restarting IISExpress after build. It slows things unnecessarily and breaks in-flight HTTP requests. -->
<NoRestartServerOnBuild>true</NoRestartServerOnBuild> <NoRestartServerOnBuild>true</NoRestartServerOnBuild>
</PropertyGroup> </PropertyGroup>

View File

@ -24,6 +24,7 @@
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Lines="$(MSBuildProjectFullPath)" Overwrite="true" Encoding="Unicode"/> <WriteLinesToFile File="$(BlazorMetadataFilePath)" Lines="$(MSBuildProjectFullPath)" Overwrite="true" Encoding="Unicode"/>
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Lines="$(OutDir)$(AssemblyName).dll" Overwrite="false" Encoding="Unicode"/> <WriteLinesToFile File="$(BlazorMetadataFilePath)" Lines="$(OutDir)$(AssemblyName).dll" Overwrite="false" Encoding="Unicode"/>
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Condition="'$(BlazorRebuildOnFileChange)'=='true'" Lines="autorebuild:true" Overwrite="false" Encoding="Unicode"/> <WriteLinesToFile File="$(BlazorMetadataFilePath)" Condition="'$(BlazorRebuildOnFileChange)'=='true'" Lines="autorebuild:true" Overwrite="false" Encoding="Unicode"/>
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Condition="'$(BlazorEnableDebugging)'=='true'" Lines="debug:true" Overwrite="false" Encoding="Unicode"/>
<ItemGroup> <ItemGroup>
<ContentWithTargetPath Include="$(BlazorMetadataFilePath)" TargetPath="$(BlazorMetadataFileName)" CopyToOutputDirectory="PreserveNewest" /> <ContentWithTargetPath Include="$(BlazorMetadataFilePath)" TargetPath="$(BlazorMetadataFileName)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup> </ItemGroup>

View File

@ -11,7 +11,7 @@
<PropertyGroup Label="Blazor build outputs"> <PropertyGroup Label="Blazor build outputs">
<MonoLinkerI18NAssemblies>none</MonoLinkerI18NAssemblies> <!-- See Mono linker docs - allows comma-separated values from: none,all,cjk,mideast,other,rare,west --> <MonoLinkerI18NAssemblies>none</MonoLinkerI18NAssemblies> <!-- See Mono linker docs - allows comma-separated values from: none,all,cjk,mideast,other,rare,west -->
<AdditionalMonoLinkerOptions>-c link -u link -t --verbose </AdditionalMonoLinkerOptions> <AdditionalMonoLinkerOptions>-c link -u link -b true -t --verbose </AdditionalMonoLinkerOptions>
<BaseBlazorDistPath>dist/</BaseBlazorDistPath> <BaseBlazorDistPath>dist/</BaseBlazorDistPath>
<BaseBlazorPackageContentOutputPath>$(BaseBlazorDistPath)_content/</BaseBlazorPackageContentOutputPath> <BaseBlazorPackageContentOutputPath>$(BaseBlazorDistPath)_content/</BaseBlazorPackageContentOutputPath>
<BaseBlazorRuntimeOutputPath>$(BaseBlazorDistPath)_framework/</BaseBlazorRuntimeOutputPath> <BaseBlazorRuntimeOutputPath>$(BaseBlazorDistPath)_framework/</BaseBlazorRuntimeOutputPath>

View File

@ -272,6 +272,7 @@
<_BlazorCommonInput Include="@(IntermediateAssembly)" /> <_BlazorCommonInput Include="@(IntermediateAssembly)" />
<_BlazorCommonInput Include="@(_BlazorDependencyInput)" /> <_BlazorCommonInput Include="@(_BlazorDependencyInput)" />
<_BlazorCommonInput Include="$(_BlazorShouldLinkApplicationAssemblies)" /> <_BlazorCommonInput Include="$(_BlazorShouldLinkApplicationAssemblies)" />
<_BlazorCommonInput Include="$(BlazorEnableDebugging)" />
<_BlazorLinkingOption Condition="'$(_BlazorShouldLinkApplicationAssemblies)' == ''" Include="false" /> <_BlazorLinkingOption Condition="'$(_BlazorShouldLinkApplicationAssemblies)' == ''" Include="false" />
<_BlazorLinkingOption Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''" Include="true" /> <_BlazorLinkingOption Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''" Include="true" />
</ItemGroup> </ItemGroup>
@ -340,11 +341,15 @@
</ReadLinesFromFile> </ReadLinesFromFile>
<ItemGroup> <ItemGroup>
<BlazorItemOutput Include="@(_OptimizedFiles)"> <BlazorItemOutput Include="@(_OptimizedFiles->WithMetadataValue('Extension','.dll'))">
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath> <TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<Type>Assembly</Type> <Type>Assembly</Type>
<PrimaryOutput Condition="'%(FileName)' == @(IntermediateAssembly->'%(FileName)')">true</PrimaryOutput> <PrimaryOutput Condition="'%(FileName)' == @(IntermediateAssembly->'%(FileName)')">true</PrimaryOutput>
</BlazorItemOutput> </BlazorItemOutput>
<BlazorItemOutput Include="@(_OptimizedFiles->WithMetadataValue('Extension','.pdb'))">
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<Type>Pdb</Type>
</BlazorItemOutput>
<FileWrites Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(TargetOutputPath)')" /> <FileWrites Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(TargetOutputPath)')" />
</ItemGroup> </ItemGroup>
@ -443,6 +448,7 @@
<!-- Collect the contents of /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker/ --> <!-- Collect the contents of /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker/ -->
<ItemGroup> <ItemGroup>
<_BlazorLinkerOutput Include="$(BlazorIntermediateLinkerOutputPath)*.dll" /> <_BlazorLinkerOutput Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
<_BlazorLinkerOutput Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" />
</ItemGroup> </ItemGroup>
<!-- <!--
@ -497,14 +503,17 @@
--> -->
<ItemGroup> <ItemGroup>
<BlazorItemOutput Include="@(_IntermediateResolvedRuntimeDependencies)"> <BlazorItemOutput Include="@(_IntermediateResolvedRuntimeDependencies->WithMetadataValue('Extension','.dll'))">
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath> <TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<Type>Assembly</Type> <Type>Assembly</Type>
<PrimaryOutput Condition="'%(FileName)' == @(IntermediateAssembly->'%(FileName)')">true</PrimaryOutput> <PrimaryOutput Condition="'%(FileName)' == @(IntermediateAssembly->'%(FileName)')">true</PrimaryOutput>
</BlazorItemOutput> </BlazorItemOutput>
<BlazorItemOutput Include="@(_IntermediateResolvedRuntimeDependencies->WithMetadataValue('Extension','.pdb'))">
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<Type>Pdb</Type>
</BlazorItemOutput>
<FileWrites Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(TargetOutputPath)')" /> <FileWrites Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(TargetOutputPath)')" />
</ItemGroup> </ItemGroup>
</Target> </Target>
<Target <Target
@ -583,8 +592,11 @@
<Target Name="_ResolveBlazorIndexHtmlInputs"> <Target Name="_ResolveBlazorIndexHtmlInputs">
<ItemGroup> <ItemGroup>
<BlazorIndexHtmlInput Include="$(BlazorIndexHtml)" /> <BlazorIndexHtmlInput Include="$(BlazorIndexHtml)" />
<BlazorIndexHtmlInput Include="$(Configuration)" />
<BlazorIndexHtmlInput Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(FullPath)')" /> <BlazorIndexHtmlInput Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(FullPath)')" />
<BlazorIndexHtmlInput Include="@(BlazorItemOutput->WithMetadataValue('Type','Pdb')->'%(FullPath)')" />
<BlazorIndexHtmlInput Include="@(_BlazorLinkingOption)" /> <BlazorIndexHtmlInput Include="@(_BlazorLinkingOption)" />
<BlazorIndexHtmlInput Include="$(BlazorEnableDebugging)" />
</ItemGroup> </ItemGroup>
<WriteLinesToFile <WriteLinesToFile
@ -607,6 +619,7 @@
<ItemGroup> <ItemGroup>
<_UnlinkedAppReferencesPaths Include="@(_BlazorDependencyInput)" /> <_UnlinkedAppReferencesPaths Include="@(_BlazorDependencyInput)" />
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->WithMetadataValue('PrimaryOutput','')->'%(FileName)%(Extension)')" /> <_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->WithMetadataValue('PrimaryOutput','')->'%(FileName)%(Extension)')" />
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Pdb')->'%(FileName)%(Extension)')" Condition="'$(BlazorEnableDebugging)' == 'true'" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<_LinkerEnabledFlag Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''">--linker-enabled</_LinkerEnabledFlag> <_LinkerEnabledFlag Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''">--linker-enabled</_LinkerEnabledFlag>

View File

@ -8,20 +8,6 @@
<!-- Preserve all methods on WasmRuntime, because these are called by JS-side code <!-- Preserve all methods on WasmRuntime, because these are called by JS-side code
to implement timers. Fixes https://github.com/aspnet/Blazor/issues/239 --> to implement timers. Fixes https://github.com/aspnet/Blazor/issues/239 -->
<type fullname="System.Threading.WasmRuntime" /> <type fullname="System.Threading.WasmRuntime" />
<!-- Mono redirects certain string constructor calls to CreateString internally, so preserve them -->
<type fullname="System.String">
<method signature="System.String CreateString(System.Char,System.Int32)" />
<method signature="System.String CreateString(System.Char[])" />
<method signature="System.String CreateString(System.Char[],System.Int32,System.Int32)" />
<method signature="System.String CreateString(System.Char*)" />
<method signature="System.String CreateString(System.Char*,System.Int32,System.Int32)" />
<method signature="System.String CreateString(System.Byte*)" />
<method signature="System.String CreateString(System.Byte*,System.Int32,System.Int32)" />
<method signature="System.String CreateString(System.Byte*,System.Int32,System.Int32,System.Text.Encoding)" />
<method signature="System.String CreateString(System.SByte*)" />
<method signature="System.String CreateString(System.SByte*,System.Int32,System.Int32,System.Text.Encoding)" />
</type>
</assembly> </assembly>
</linker> </linker>

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Blazor.Server; using Microsoft.AspNetCore.Blazor.Server;
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Builder
// way to remove middleware once it's registered. // way to remove middleware once it's registered.
private static List<object> _uncollectableWatchers = new List<object>(); private static List<object> _uncollectableWatchers = new List<object>();
public static void UseHostedAutoRebuild(this IApplicationBuilder appBuilder, BlazorConfig config, string hostAppContentRootPath) public static void UseHostedAutoRebuild(this IApplicationBuilder app, BlazorConfig config, string hostAppContentRootPath)
{ {
var isFirstFileWrite = true; var isFirstFileWrite = true;
WatchFileSystem(config, () => WatchFileSystem(config, () =>
@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Builder
catch (Exception ex) catch (Exception ex)
{ {
// If we don't have permission to write these files, autorebuild will not be enabled // If we don't have permission to write these files, autorebuild will not be enabled
var loggerFactory = appBuilder.ApplicationServices.GetRequiredService<ILoggerFactory>(); var loggerFactory = app.ApplicationServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger(typeof (AutoRebuildExtensions)); var logger = loggerFactory.CreateLogger(typeof (AutoRebuildExtensions));
logger?.LogWarning(ex, logger?.LogWarning(ex,
"Cannot autorebuild because there was an error when writing to a file in '{0}'.", "Cannot autorebuild because there was an error when writing to a file in '{0}'.",
@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.Builder
}); });
} }
public static void UseDevServerAutoRebuild(this IApplicationBuilder appBuilder, BlazorConfig config) public static void UseDevServerAutoRebuild(this IApplicationBuilder app, BlazorConfig config)
{ {
// Currently this only supports VS for Windows. Later on we can add // Currently this only supports VS for Windows. Later on we can add
// an IRebuildService implementation for VS for Mac, etc. // an IRebuildService implementation for VS for Mac, etc.
@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Builder
buildToken = new RebuildToken(DateTime.Now); buildToken = new RebuildToken(DateTime.Now);
}); });
appBuilder.Use(async (context, next) => app.Use(async (context, next) =>
{ {
try try
{ {

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -23,12 +23,12 @@ namespace Microsoft.AspNetCore.Builder
/// Configures the middleware pipeline to work with Blazor. /// Configures the middleware pipeline to work with Blazor.
/// </summary> /// </summary>
/// <typeparam name="TProgram">Any type from the client app project. This is used to identify the client app assembly.</typeparam> /// <typeparam name="TProgram">Any type from the client app project. This is used to identify the client app assembly.</typeparam>
/// <param name="applicationBuilder"></param> /// <param name="app"></param>
public static void UseBlazor<TProgram>( public static void UseBlazor<TProgram>(
this IApplicationBuilder applicationBuilder) this IApplicationBuilder app)
{ {
var clientAssemblyInServerBinDir = typeof(TProgram).Assembly; var clientAssemblyInServerBinDir = typeof(TProgram).Assembly;
applicationBuilder.UseBlazor(new BlazorOptions app.UseBlazor(new BlazorOptions
{ {
ClientAssemblyPath = clientAssemblyInServerBinDir.Location, ClientAssemblyPath = clientAssemblyInServerBinDir.Location,
}); });
@ -37,21 +37,21 @@ namespace Microsoft.AspNetCore.Builder
/// <summary> /// <summary>
/// Configures the middleware pipeline to work with Blazor. /// Configures the middleware pipeline to work with Blazor.
/// </summary> /// </summary>
/// <param name="applicationBuilder"></param> /// <param name="app"></param>
/// <param name="options"></param> /// <param name="options"></param>
public static void UseBlazor( public static void UseBlazor(
this IApplicationBuilder applicationBuilder, this IApplicationBuilder app,
BlazorOptions options) BlazorOptions options)
{ {
// TODO: Make the .blazor.config file contents sane // TODO: Make the .blazor.config file contents sane
// Currently the items in it are bizarre and don't relate to their purpose, // Currently the items in it are bizarre and don't relate to their purpose,
// hence all the path manipulation here. We shouldn't be hardcoding 'dist' here either. // hence all the path manipulation here. We shouldn't be hardcoding 'dist' here either.
var env = (IHostingEnvironment)applicationBuilder.ApplicationServices.GetService(typeof(IHostingEnvironment)); var env = (IHostingEnvironment)app.ApplicationServices.GetService(typeof(IHostingEnvironment));
var config = BlazorConfig.Read(options.ClientAssemblyPath); var config = BlazorConfig.Read(options.ClientAssemblyPath);
var distDirStaticFiles = new StaticFileOptions var distDirStaticFiles = new StaticFileOptions
{ {
FileProvider = new PhysicalFileProvider(config.DistPath), FileProvider = new PhysicalFileProvider(config.DistPath),
ContentTypeProvider = CreateContentTypeProvider(), ContentTypeProvider = CreateContentTypeProvider(config.EnableDebugging),
OnPrepareResponse = SetCacheHeaders OnPrepareResponse = SetCacheHeaders
}; };
@ -59,16 +59,16 @@ namespace Microsoft.AspNetCore.Builder
{ {
if (env.ApplicationName.Equals(DevServerApplicationName, StringComparison.OrdinalIgnoreCase)) if (env.ApplicationName.Equals(DevServerApplicationName, StringComparison.OrdinalIgnoreCase))
{ {
applicationBuilder.UseDevServerAutoRebuild(config); app.UseDevServerAutoRebuild(config);
} }
else else
{ {
applicationBuilder.UseHostedAutoRebuild(config, env.ContentRootPath); app.UseHostedAutoRebuild(config, env.ContentRootPath);
} }
} }
// First, match the request against files in the client app dist directory // First, match the request against files in the client app dist directory
applicationBuilder.UseStaticFiles(distDirStaticFiles); app.UseStaticFiles(distDirStaticFiles);
// Next, match the request against static files in wwwroot // Next, match the request against static files in wwwroot
if (!string.IsNullOrEmpty(config.WebRootPath)) if (!string.IsNullOrEmpty(config.WebRootPath))
@ -77,16 +77,22 @@ namespace Microsoft.AspNetCore.Builder
// (and don't require them to be copied into dist). // (and don't require them to be copied into dist).
// TODO: When publishing is implemented, have config.WebRootPath set // TODO: When publishing is implemented, have config.WebRootPath set
// to null so that it only serves files that were copied to dist // to null so that it only serves files that were copied to dist
applicationBuilder.UseStaticFiles(new StaticFileOptions app.UseStaticFiles(new StaticFileOptions
{ {
FileProvider = new PhysicalFileProvider(config.WebRootPath), FileProvider = new PhysicalFileProvider(config.WebRootPath),
OnPrepareResponse = SetCacheHeaders OnPrepareResponse = SetCacheHeaders
}); });
} }
// Accept debugger connections
if (config.EnableDebugging)
{
app.UseMonoDebugProxy();
}
// Finally, use SPA fallback routing (serve default page for anything else, // Finally, use SPA fallback routing (serve default page for anything else,
// excluding /_framework/*) // excluding /_framework/*)
applicationBuilder.MapWhen(IsNotFrameworkDir, childAppBuilder => app.MapWhen(IsNotFrameworkDir, childAppBuilder =>
{ {
childAppBuilder.UseSpa(spa => childAppBuilder.UseSpa(spa =>
{ {
@ -116,12 +122,18 @@ namespace Microsoft.AspNetCore.Builder
private static bool IsNotFrameworkDir(HttpContext context) private static bool IsNotFrameworkDir(HttpContext context)
=> !context.Request.Path.StartsWithSegments("/_framework"); => !context.Request.Path.StartsWithSegments("/_framework");
private static IContentTypeProvider CreateContentTypeProvider() private static IContentTypeProvider CreateContentTypeProvider(bool enableDebugging)
{ {
var result = new FileExtensionContentTypeProvider(); var result = new FileExtensionContentTypeProvider();
result.Mappings.Add(".dll", MediaTypeNames.Application.Octet); result.Mappings.Add(".dll", MediaTypeNames.Application.Octet);
result.Mappings.Add(".mem", MediaTypeNames.Application.Octet); result.Mappings.Add(".mem", MediaTypeNames.Application.Octet);
result.Mappings.Add(".wasm", WasmMediaTypeNames.Application.Wasm); result.Mappings.Add(".wasm", WasmMediaTypeNames.Application.Wasm);
if (enableDebugging)
{
result.Mappings.Add(".pdb", MediaTypeNames.Application.Octet);
}
return result; return result;
} }
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -15,6 +15,7 @@ namespace Microsoft.AspNetCore.Blazor.Server
public string DistPath public string DistPath
=> Path.Combine(Path.GetDirectoryName(SourceOutputAssemblyPath), "dist"); => Path.Combine(Path.GetDirectoryName(SourceOutputAssemblyPath), "dist");
public bool EnableAutoRebuilding { get; } public bool EnableAutoRebuilding { get; }
public bool EnableDebugging { get; }
public static BlazorConfig Read(string assemblyPath) public static BlazorConfig Read(string assemblyPath)
=> new BlazorConfig(assemblyPath); => new BlazorConfig(assemblyPath);
@ -44,6 +45,7 @@ namespace Microsoft.AspNetCore.Blazor.Server
} }
EnableAutoRebuilding = configLines.Contains("autorebuild:true", StringComparer.Ordinal); EnableAutoRebuilding = configLines.Contains("autorebuild:true", StringComparer.Ordinal);
EnableDebugging = configLines.Contains("debug:true", StringComparer.Ordinal);
} }
} }
} }

View File

@ -0,0 +1,178 @@
// 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.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using WsProxy;
namespace Microsoft.AspNetCore.Builder
{
internal static class MonoDebugProxyAppBuilderExtensions
{
public static void UseMonoDebugProxy(this IApplicationBuilder app)
{
app.UseWebSockets();
app.Use((context, next) =>
{
var requestPath = context.Request.Path;
if (!requestPath.StartsWithSegments("/_framework/debug"))
{
return next();
}
if (requestPath.Equals("/_framework/debug/ws-proxy", StringComparison.OrdinalIgnoreCase))
{
return DebugWebSocketProxyRequest(context);
}
if (requestPath.Equals("/_framework/debug", StringComparison.OrdinalIgnoreCase))
{
return DebugHome(context);
}
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
return Task.CompletedTask;
});
}
private static async Task DebugWebSocketProxyRequest(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
context.Response.StatusCode = 400;
return;
}
var browserUri = new Uri(context.Request.Query["browser"]);
await new MonoProxy().Run(context, browserUri);
}
private static async Task DebugHome(HttpContext context)
{
context.Response.ContentType = "text/html";
var request = context.Request;
var appRootUrl = $"{request.Scheme}://{request.Host}{request.PathBase}/";
var targetTabUrl = request.Query["url"];
if (string.IsNullOrEmpty(targetTabUrl))
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("No value specified for 'url'");
return;
}
// TODO: Allow overriding port (but not hostname, as we're connecting to the
// local browser, not to the webserver serving the app)
var debuggerHost = "http://localhost:9222";
var debuggerTabsListUrl = $"{debuggerHost}/json";
IEnumerable<BrowserTab> availableTabs;
try
{
availableTabs = await GetOpenedBrowserTabs(debuggerHost);
}
catch (Exception ex)
{
await context.Response.WriteAsync($@"
<h1>Unable to find debuggable browser tab</h1>
<p>
Could not get a list of browser tabs from <code>{debuggerTabsListUrl}</code>.
Ensure Chrome is running with debugging enabled.
</p>
<h2>Resolution</h2>
{GetLaunchChromeInstructions(appRootUrl)}
<p>... then use that new tab for debugging.</p>
<h2>Underlying exception:</h2>
<pre>{ex}</pre>
");
return;
}
var matchingTabs = availableTabs
.Where(t => t.Url.Equals(targetTabUrl, StringComparison.Ordinal))
.ToList();
if (matchingTabs.Count == 0)
{
await context.Response.WriteAsync($@"
<h1>Unable to find debuggable browser tab</h1>
<p>
The response from <code>{debuggerTabsListUrl}</code> does not include
any entry for <code>{targetTabUrl}</code>.
</p>");
return;
}
else if (matchingTabs.Count > 1)
{
// TODO: Automatically disambiguate by adding a GUID to the page title
// when you press the debugger hotkey, include it in the querystring passed
// here, then remove it once the debugger connects.
await context.Response.WriteAsync($@"
<h1>Multiple matching tabs are open</h1>
<p>
There is more than one browser tab at <code>{targetTabUrl}</code>.
Close the ones you do not wish to debug, then refresh this page.
</p>");
return;
}
// Now we know uniquely which tab to debug, construct the URL to the debug
// page and redirect there
var tabToDebug = matchingTabs.Single();
var underlyingV8Endpoint = tabToDebug.WebSocketDebuggerUrl;
var proxyEndpoint = $"{request.Host}{request.PathBase}/_framework/debug/ws-proxy?browser={WebUtility.UrlEncode(underlyingV8Endpoint)}";
var devToolsUrlAbsolute = new Uri(debuggerHost + tabToDebug.DevtoolsFrontendUrl);
var devToolsUrlWithProxy = $"{devToolsUrlAbsolute.Scheme}://{devToolsUrlAbsolute.Authority}{devToolsUrlAbsolute.AbsolutePath}?ws={proxyEndpoint}";
context.Response.Redirect(devToolsUrlWithProxy);
}
private static string GetLaunchChromeInstructions(string appRootUrl)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return $@"<p>Close this browser, then press Win+R and enter the following:</p>
<p><strong><code>""%programfiles(x86)%\Google\Chrome\Application\chrome.exe"" --remote-debugging-port=9222 {appRootUrl}</code></strong></p>";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return $@"<p>Close this browser, then in a terminal window execute the following:</p>
<p><strong><code>google-chrome --remote-debugging-port=9222 {appRootUrl}</code></strong></p>";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return $@"<p>Close this browser, then in a terminal window execute the following:</p>
<p><strong><code>open /Applications/Google\ Chrome.app --remote-debugging-port=9222 {appRootUrl}</code></strong></p>";
}
else
{
throw new InvalidOperationException("Unknown OS platform");
}
}
private static async Task<IEnumerable<BrowserTab>> GetOpenedBrowserTabs(string debuggerHost)
{
using (var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) })
{
var jsonResponse = await httpClient.GetStringAsync($"{debuggerHost}/json");
return JsonConvert.DeserializeObject<BrowserTab[]>(jsonResponse);
}
}
class BrowserTab
{
public string Type { get; set; }
public string Url { get; set; }
public string Title { get; set; }
public string DevtoolsFrontendUrl { get; set; }
public string WebSocketDebuggerUrl { get; set; }
}
}
}

View File

@ -44,11 +44,12 @@ namespace WsProxy
} }
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/*
public void Configure (IApplicationBuilder app, IHostingEnvironment env) public void Configure (IApplicationBuilder app, IHostingEnvironment env)
{ {
//loggerFactory.AddConsole(); //loggerFactory.AddConsole();
//loggerFactory.AddDebug(); //loggerFactory.AddDebug();
//app.UseDeveloperExceptionPage (); app.UseDeveloperExceptionPage ();
app.UseWebSockets (); app.UseRouter (router => { app.UseWebSockets (); app.UseRouter (router => {
router.MapGet ("devtools/page/{pageId}", async context => { router.MapGet ("devtools/page/{pageId}", async context => {
@ -66,5 +67,6 @@ namespace WsProxy
}); });
}); });
} }
*/
} }
} }

View File

@ -263,9 +263,9 @@ namespace WsProxy {
Send (this.ide, o, token); Send (this.ide, o, token);
} }
public async Task Run (HttpContext context) public async Task Run (HttpContext context, Uri browserUri)
{ {
var browserUri = GetBrowserUri (context.Request.Path.ToString ()); //var browserUri = GetBrowserUri (context.Request.Path.ToString ());
Debug ("wsproxy start"); Debug ("wsproxy start");
using (this.ide = await context.WebSockets.AcceptWebSocketAsync ()) { using (this.ide = await context.WebSockets.AcceptWebSocketAsync ()) {
Debug ("ide connected"); Debug ("ide connected");

View File

@ -58,15 +58,20 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
uncalled implementation code from mscorlib.dll anyway. uncalled implementation code from mscorlib.dll anyway.
*/ */
"Microsoft.AspNetCore.Blazor.Browser.dll", "Microsoft.AspNetCore.Blazor.Browser.dll",
"Microsoft.AspNetCore.Blazor.Browser.pdb",
"Microsoft.AspNetCore.Blazor.dll", "Microsoft.AspNetCore.Blazor.dll",
"Microsoft.AspNetCore.Blazor.pdb",
"Microsoft.Extensions.DependencyInjection.Abstractions.dll", "Microsoft.Extensions.DependencyInjection.Abstractions.dll",
"Microsoft.Extensions.DependencyInjection.dll", "Microsoft.Extensions.DependencyInjection.dll",
"Microsoft.JSInterop.dll", "Microsoft.JSInterop.dll",
"Microsoft.JSInterop.pdb",
"Mono.Security.dll", "Mono.Security.dll",
"Mono.WebAssembly.Interop.dll", "Mono.WebAssembly.Interop.dll",
"Mono.WebAssembly.Interop.pdb",
"mscorlib.dll", "mscorlib.dll",
"netstandard.dll", "netstandard.dll",
"StandaloneApp.dll", "StandaloneApp.dll",
"StandaloneApp.pdb",
"System.dll", "System.dll",
"System.Collections.Concurrent.dll", "System.Collections.Concurrent.dll",
"System.Collections.dll", "System.Collections.dll",