Initial debugger support
This commit is contained in:
parent
c881a63a78
commit
cafb56569d
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -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" /> -->
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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$/, '');
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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$/, '');
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue