diff --git a/Blazor.sln b/Blazor.sln index f1caf90170..9e90f27bfe 100644 --- a/Blazor.sln +++ b/Blazor.sln @@ -45,6 +45,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Blazor.Mono.Test" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Blazor.E2ETest", "test\Microsoft.Blazor.E2ETest\Microsoft.Blazor.E2ETest.csproj", "{5BC2A10D-B6CA-43AE-B73C-2A41AE1039F9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoSanityClient", "samples\MonoSanityClient\MonoSanityClient.csproj", "{06AAAE9E-96DE-4574-97DA-9C4C7D9FE990}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -75,6 +77,10 @@ Global {5BC2A10D-B6CA-43AE-B73C-2A41AE1039F9}.Debug|Any CPU.Build.0 = Debug|Any CPU {5BC2A10D-B6CA-43AE-B73C-2A41AE1039F9}.Release|Any CPU.ActiveCfg = Release|Any CPU {5BC2A10D-B6CA-43AE-B73C-2A41AE1039F9}.Release|Any CPU.Build.0 = Release|Any CPU + {06AAAE9E-96DE-4574-97DA-9C4C7D9FE990}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {06AAAE9E-96DE-4574-97DA-9C4C7D9FE990}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06AAAE9E-96DE-4574-97DA-9C4C7D9FE990}.Release|Any CPU.ActiveCfg = Release|Any CPU + {06AAAE9E-96DE-4574-97DA-9C4C7D9FE990}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -87,6 +93,7 @@ Global {5A694793-3257-4D37-BB74-4A41B3894685} = {B867E038-B3CE-43E3-9292-61568C46CDEB} {118484D3-3993-45CE-97C1-6F28A517529B} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E} {5BC2A10D-B6CA-43AE-B73C-2A41AE1039F9} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E} + {06AAAE9E-96DE-4574-97DA-9C4C7D9FE990} = {F5FDD4E5-6A52-4A86-BE5E-5E42CB1DC8DA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {504DA352-6788-4DC0-8705-82167E72A4D3} diff --git a/runtime/Microsoft.Blazor.Server/BlazorAppBuilderExtensions.cs b/runtime/Microsoft.Blazor.Server/BlazorAppBuilderExtensions.cs index 9c23044bcf..2dff0e53df 100644 --- a/runtime/Microsoft.Blazor.Server/BlazorAppBuilderExtensions.cs +++ b/runtime/Microsoft.Blazor.Server/BlazorAppBuilderExtensions.cs @@ -26,7 +26,8 @@ namespace Microsoft.AspNetCore.Builder { { ".dll", MediaTypeNames.Application.Octet }, { ".js", "application/javascript" }, - { ".wasm", MediaTypeNames.Application.Octet } + { ".mem", MediaTypeNames.Application.Octet }, + { ".wasm", MediaTypeNames.Application.Octet }, }); } } diff --git a/samples/MonoSanity/MonoSanity.csproj b/samples/MonoSanity/MonoSanity.csproj index c800d91356..6cf5b5d1c1 100644 --- a/samples/MonoSanity/MonoSanity.csproj +++ b/samples/MonoSanity/MonoSanity.csproj @@ -10,6 +10,7 @@ + diff --git a/samples/MonoSanity/Startup.cs b/samples/MonoSanity/Startup.cs index b478e25de8..aa6d8d6245 100644 --- a/samples/MonoSanity/Startup.cs +++ b/samples/MonoSanity/Startup.cs @@ -3,6 +3,9 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using MonoSanityClient; +using System.IO; +using System.Net.Mime; namespace MonoSanity { @@ -13,6 +16,28 @@ namespace MonoSanity app.UseDeveloperExceptionPage(); app.UseFileServer(); app.UseBlazor(); + + ServeSingleStaticFile(app, + "/clientBin/MonoSanityClient.dll", + typeof(Examples).Assembly.Location); + } + + private void ServeSingleStaticFile(IApplicationBuilder app, string url, string physicalPath) + { + // This is not implemented efficiently (e.g., doesn't support cache control headers) + // so don't use this in real applications. Use 'UseStaticFiles' or similar instead. + app.Use((context, next) => + { + if (context.Request.Path == url) + { + context.Response.ContentType = MediaTypeNames.Application.Octet; + return File.OpenRead(physicalPath).CopyToAsync(context.Response.Body); + } + else + { + return next(); + } + }); } } } diff --git a/samples/MonoSanity/wwwroot/index.html b/samples/MonoSanity/wwwroot/index.html index 0600c266b8..846b0655ec 100644 --- a/samples/MonoSanity/wwwroot/index.html +++ b/samples/MonoSanity/wwwroot/index.html @@ -1,8 +1,83 @@ + Mono sanity check + -

Hello, world!

+

Simple sanity check to ensure the Mono runtime works in basic cases.

+ +
+ Add numbers +
+ + + = + + +
+
+ +
+ Repeat string +
+ * + = + + +
+
+ +
+ Trigger exception +
+ + +
+
+
+ +

Loading...

+ + + diff --git a/samples/MonoSanity/wwwroot/loader.js b/samples/MonoSanity/wwwroot/loader.js new file mode 100644 index 0000000000..d391c30bbb --- /dev/null +++ b/samples/MonoSanity/wwwroot/loader.js @@ -0,0 +1,164 @@ +(function () { + window.initMono = function initMono(loadAssemblyUrls, onReadyCallback) { + window.Browser = { + init: function () { }, + asyncLoad: asyncLoad + }; + + window.Module = { + print: function (line) { console.log(line); }, + printEr: function (line) { console.error(line); }, + wasmBinaryFile: '/_framework/wasm/mono.wasm', + asmjsCodeFile: '/_framework/asmjs/mono.asm.js', + preloadPlugins: [], + preRun: [function () { + preloadAssemblies(loadAssemblyUrls); + }], + postRun: [function () { + var load_runtime = Module.cwrap('mono_wasm_load_runtime', null, ['string']); + load_runtime('appBinDir'); + onReadyCallback(); + }] + }; + + addScriptTagsToDocument(); + }; + + window.invokeMonoMethod = function invokeMonoMethod(assemblyName, namespace, typeName, methodName, args) { + var assembly_load = Module.cwrap('mono_wasm_assembly_load', 'number', ['string']); + var find_class = Module.cwrap('mono_wasm_assembly_find_class', 'number', ['number', 'string', 'string']); + var find_method = Module.cwrap('mono_wasm_assembly_find_method', 'number', ['number', 'string', 'number']); + + var assembly = assembly_load(assemblyName); + var type = find_class(assembly, namespace, typeName); + var method = find_method(type, methodName, -1); + + var stack = Module.Runtime.stackSave(); + try { + var resultPtr = callMethod(method, null, args); + return dotnetStringToJavaScriptString(resultPtr); + } + finally { + Module.Runtime.stackRestore(stack); + } + }; + + function preloadAssemblies(loadAssemblyUrls) { + var loadBclAssemblies = [ + 'mscorlib', + 'System', + 'System.Core', + 'Facades/netstandard', + 'Facades/System.Console', + 'Facades/System.Collections', + 'Facades/System.Diagnostics.Debug', + 'Facades/System.IO', + 'Facades/System.Linq', + 'Facades/System.Reflection', + 'Facades/System.Reflection.Extensions', + 'Facades/System.Runtime', + 'Facades/System.Runtime.Extensions', + 'Facades/System.Runtime.InteropServices', + 'Facades/System.Threading', + 'Facades/System.Threading.Tasks' + ]; + + var allAssemblyUrls = loadAssemblyUrls + .concat(loadBclAssemblies.map(function (name) { return '_framework/bcl/' + name + '.dll'; })); + + Module.FS_createPath('/', 'appBinDir', true, true); + allAssemblyUrls.forEach(function (url) { + FS.createPreloadedFile('appBinDir', getAssemblyNameFromUrl(url) + '.dll', url, true, false, null, function onError(err) { + throw err; + }); + }); + } + + function asyncLoad(url, onload, onerror) { + var xhr = new XMLHttpRequest; + xhr.open('GET', url, /* async: */ true); + xhr.responseType = 'arraybuffer'; + xhr.onload = function xhr_onload() { + if (xhr.status === 200 || xhr.status === 0 && xhr.response) { + var asm = new Uint8Array(xhr.response); + onload(asm); + } else { + onerror(xhr); + } + }; + xhr.onerror = onerror; + xhr.send(null); + } + + function dotnetStringToJavaScriptString(mono_obj) { + if (mono_obj === 0) + return null; + var mono_string_get_utf8 = Module.cwrap('mono_wasm_string_get_utf8', 'number', ['number']); + var raw = mono_string_get_utf8(mono_obj); + var res = Module.UTF8ToString(raw); + Module._free(raw); + return res; + } + + function callMethod(method, target, args) { + var stack = Module.Runtime.stackSave(); + var invoke_method = Module.cwrap('mono_wasm_invoke_method', 'number', ['number', 'number', 'number']); + var mono_string = Module.cwrap('mono_wasm_string_from_js', 'number', ['string']); + + try { + var argsBuffer = Module.Runtime.stackAlloc(args.length); + var exceptionFlagManagedInt = Module.Runtime.stackAlloc(4); + for (var i = 0; i < args.length; ++i) { + var argVal = args[i]; + if (typeof argVal === 'number') { + var managedInt = Module.Runtime.stackAlloc(4); + Module.setValue(managedInt, argVal, 'i32'); + Module.setValue(argsBuffer + i * 4, managedInt, 'i32'); + } else if (typeof argVal === 'string') { + var managedString = mono_string(argVal); + Module.setValue(argsBuffer + i * 4, managedString, 'i32'); + } else { + throw new Error('Unsupported arg type: ' + typeof argVal); + } + } + Module.setValue(exceptionFlagManagedInt, 0, 'i32'); + + var res = invoke_method(method, target, argsBuffer, exceptionFlagManagedInt); + + if (Module.getValue(exceptionFlagManagedInt, 'i32') !== 0) { + throw new Error(dotnetStringToJavaScriptString(res)); + } + + return res; + } finally { + Module.Runtime.stackRestore(stack); + } + } + + function addScriptTagsToDocument() { + // Load either the wasm or asm.js version of the Mono runtime + var browserSupportsNativeWebAssembly = typeof WebAssembly !== 'undefined' && WebAssembly.validate; + var monoRuntimeUrlBase = '/_framework/' + (browserSupportsNativeWebAssembly ? 'wasm' : 'asmjs'); + var monoRuntimeScriptUrl = monoRuntimeUrlBase + '/mono.js'; + + if (!browserSupportsNativeWebAssembly) { + // In the asmjs case, the initial memory structure is in a separate file we need to download + var meminitXHR = Module['memoryInitializerRequest'] = new XMLHttpRequest(); + meminitXHR.open('GET', monoRuntimeUrlBase + '/mono.js.mem'); + meminitXHR.responseType = 'arraybuffer'; + meminitXHR.send(null); + } + + var scriptElem = document.createElement('script'); + scriptElem.src = monoRuntimeScriptUrl; + document.body.appendChild(scriptElem); + } + + function getAssemblyNameFromUrl(url) { + var lastSegment = url.substring(url.lastIndexOf('/') + 1); + var queryStringStartPos = lastSegment.indexOf('?'); + var filename = queryStringStartPos < 0 ? lastSegment : lastSegment.substring(0, queryStringStartPos); + return filename.replace(/\.dll$/, ''); + } + +})(); \ No newline at end of file diff --git a/samples/MonoSanityClient/Examples.cs b/samples/MonoSanityClient/Examples.cs new file mode 100644 index 0000000000..24be61d54e --- /dev/null +++ b/samples/MonoSanityClient/Examples.cs @@ -0,0 +1,31 @@ +// 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.Text; + +namespace MonoSanityClient +{ + public static class Examples + { + public static string AddNumbers(int a, int b) + => (a + b).ToString(); + + public static string RepeatString(string str, int count) + { + var result = new StringBuilder(); + + for (var i = 0; i < count; i++) + { + result.Append(str); + } + + return result.ToString(); + } + + public static void TriggerException(string message) + { + throw new InvalidOperationException(message); + } + } +} diff --git a/samples/MonoSanityClient/MonoSanityClient.csproj b/samples/MonoSanityClient/MonoSanityClient.csproj new file mode 100644 index 0000000000..5766db614c --- /dev/null +++ b/samples/MonoSanityClient/MonoSanityClient.csproj @@ -0,0 +1,7 @@ + + + + netcoreapp2.0 + + + diff --git a/test/Microsoft.Blazor.Mono.Test/MonoStaticFileProviderTest.cs b/test/Microsoft.Blazor.Mono.Test/MonoStaticFileProviderTest.cs index 40631e6350..fe3e175817 100644 --- a/test/Microsoft.Blazor.Mono.Test/MonoStaticFileProviderTest.cs +++ b/test/Microsoft.Blazor.Mono.Test/MonoStaticFileProviderTest.cs @@ -18,6 +18,7 @@ namespace Microsoft.Blazor.Mono.Test var expectedFiles = new[] { "/asmjs/mono.asm.js", + "/asmjs/mono.js.mem", "/wasm/mono.wasm", "/bcl/mscorlib.dll", "/bcl/Facades/System.Collections.dll",