通过 WebAssembly 在浏览器中运行 C# (无需 Blazor)





5.00/5 (27投票s)
在本文中,我将介绍如何在浏览器中编写 C# 代码。
引言
C# 程序(以及用许多其他软件语言编写的程序)现在可以通过 WebAssembly(简称 Wasm)在几乎任何浏览器中运行。
除了用更好的语言编写外,WebAssembly 程序通常运行速度比 JavaScript/TypeScript 程序快,因为它们经过编译(至少编译到中间语言,有些甚至编译到本地机器汇编语言)。而且,作为强类型语言,它们拥有显著更小、更快的内置类型。
Avalonia
Avalonia 是一个用于构建 UI 应用程序的出色跨平台框架。Avalonia 可用于构建 Windows、MacOS、Linux 的桌面应用程序,以及浏览器 WebAssembly 应用程序和 iOS、Android 和 Tizen 的移动应用程序。
最重要的是,Avalonia 允许在不同平台之间重用大部分代码。
这是一个演示 Avalonia 控件构建为 Avalonia 浏览器内应用程序的网站:Avalonia Demo
在本文中,我们将只讨论通过 Wasm 在浏览器中运行的 Avalonia。
为何“不含 Blazor”
微软最初的 WebAssembly 支持产品称为 Blazor(客户端版本),作为 ASP.NET 的一部分发布。Blazor 可用于执行非可视化操作,也可用于在 C# 中修改 HTML 树。
然而,我看到了一些关于 Blazor 在与 HTML/JavaScript 交互时的稳定性与性能的抱怨。这里有一个关于 Blazor 稳定性和性能问题的网页。我记得在其他网站上看到过类似的抱怨。
由于这些问题,本文将重点介绍使用 System.Runtime.InteropServices.JavaScript 包,该包已成为 .NET WebAssembly SDK 的一部分。据报道,该包提供了更好的性能和更稳定的与 JavaScript 的交互。
最新版本的 Avalonia 也使用了这个库。
WebAssembly 的主要问题——缺乏好的示例和文档
尽管 C# 通过 Wasm 已经准备就绪,但主要问题是它是一项相对较新的技术,网上很少有好的示例和文档。
本文的主要目的是提供易于理解的示例和良好的文档,涵盖 C# 在浏览器中功能的所有或大部分内容。
文章大纲
本文涵盖的主题如下
- 创建 Wasm 项目并将其嵌入到浏览器代码中。
- 从 Wasm C# 调用浏览器 JavaScript 方法,反之亦然——从浏览器内 JavaScript 调用 Wasm C# 方法。
- 调用 C# Program.Main 方法。
- 在浏览器中运行 Avalonia 可视化应用程序。
使用 ASP.NET Core 作为示例
基于浏览器的编程总是意味着需要一个服务器——在所有示例中,我使用 ASP.NET,因为 ASP.NET 是微软一款强大、经过良好测试、久经考验的技术,它与我喜欢的 Visual Studio 2022 配合良好,并且允许将 HTML/JavaScript 客户端代码与服务器代码保存在同一个项目中。
为了速度和清晰起见,我尽量避免 ASP.NET 代码生成;相反,我将 ASP.NET 用作 Web 和数据服务器以及中间层。
虽然 ASP.NET 是我服务器的选择,但部署和运行 WebAssembly 的方法可以应用于任何其他服务器技术。
示例源代码位置
示例的源代码位于 Web Assembly Samples。
JavaScript 调用 C# 方法示例
重要提示
我将详细介绍第一个示例,并解释有关 WebAssembly 的几乎所有内容。其余示例的详细程度不会保持(因为那时您已经理解了 WebAssembly 的工作原理)。因此,阅读本节很重要,而其余示例相关部分您可以根据需要选择性阅读。
示例位置
此示例位于 JSCallingDotNetSample 文件夹中(其解决方案文件名称相同)。
示例代码概述
JSCallingDotNetSample 解决方案包含两个项目
- JSCallingDotNetSample - 一个 ASP.NET 8.0 项目。请确保这是您的启动项目。
- Greeter - 一个 C# .NET 8.0 库。
解决方案和项目是如何创建的
为了创建解决方案和主要的 ASP.NET 项目,我启动了 Visual Studio 2022,点击了“创建新项目”选项,并选择了“ASP.NET Core Web APP (Razor Pages)”
然后我输入了解决方案的名称(“JSCallingDotNetSample”),并通过取消选择“将解决方案和项目放在同一目录”复选框,确保解决方案创建在项目之上一个目录中(而不是在同一目录中)。
然后,为了创建包含 C# 代码的项目,我右键单击解决方案资源管理器中的解决方案,选择“添加”->“新建项目”,然后选择“类库”模板。
请注意,虽然在下面的所有示例中,ASP.NET 项目的创建方式相同,但一些纯 C# 项目的创建方式会不同。有时我们会选择 C# 控制台项目(而不是类库)模板,对于 Avalonia 示例,当我们将要介绍该主题时,创建 Avalonia Wasm 项目会更有趣,届时我将提供详细信息。
请注意,没有项目依赖项——ASP.NET 主项目不依赖于 C# 项目。不过稍后我将展示如何引入项目之间的构建依赖项。
运行项目
为了成功运行项目,请先生成 Greeter 项目。在项目的 bin/Debug/net8.0-browser/wwwroot 文件夹下会创建一个 _framework 子文件夹。
将此 _framework 文件夹复制到 JSCollingDotNetSample ASP.NET 项目的 wwwroot 文件夹下。
请注意,该文件夹不应成为源代码的一部分(即使它已被移动到源代码文件夹下)。如果您正在使用 git,您必须将此文件夹及其内容添加到 git .ignore 文件中(这是文件夹前的小红色停止图标的含义)。
生成并运行主要的 JSCallingDotNetSample 项目——首先,您会看到一条“请稍候,WebAssembly 正在加载!”的消息,一两秒钟后,它将被 C# 代码生成的消息覆盖。
纯 C# Greeter 项目
C# Greeter 项目包含由 JavaScript(来自 ASP.NET 项目)调用的 C# 代码。它只有一个静态类 JSInteropCallsContainer
,该类有一个名为 Greet
的方法,该方法接受一个字符串数组(姓名)并返回一个问候字符串“Hello <name1>, <name2>, ... <name_N>!!!”
public static partial class JSInteropCallsContainer { // this simple static method is exported to JavaScript // via WebAssembly [JSExport] public static string Greet(params string[] names) { var resultStr = string.Join(", ", names); // return a string greeting comma separated names passed to it // e.g. if the array of names contains two names "Joe" and "Jack" // then the resulting string will be "Hello Joe, Jack!!!". return $"Hello {resultStr}!!!"; } }
请注意,该类是静态的、部分类,并且 Greet(...)
方法具有 JSExport
属性——这允许从 JavaScript 调用该方法。
看看项目文件——Greeter.csproj
<Project Sdk="Microsoft.NET.Sdk.WebAssembly">
<PropertyGroup>
<TargetFramework>net8.0-browser</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>Library</OutputType>
</PropertyGroup>
...
</Project>
注意与普通 C# 库项目相比的高亮显示的区别
- Sdk 设置为 Microsoft.NET.Sdk.WebAssembly。
- TargetFramework 设置为 net8.0-browser。
- <AllowUnsafeBlocks>true</AllowUnsafeBlocks>.
这三个更改允许项目在生成(生成结果)时创建 _framework 文件夹,其中包含部署所需的 .wasm 和其他文件。
JSCallingDotNetSample 代码
在这里,我将解释在创建 JSCallingDotNetSample ASP.NET 项目(使用“ASP.NET Core Web App (Razor Pages)”模板)后对项目中的文件所做的更改。
少量修改
为了简化,我删除了 wwwroot/lib 文件夹——因为我计划不使用引导程序或 jQuery。
我还大大简化了 Pages/Shared 文件夹下的 _Layout.cshtml 文件,删除了其页脚、页眉和 CSS 类。
对 Program.cs 文件的修改
我从 Progam.cs 文件中删除了一些不必要的行,并添加了 WebAssembly 所需的 MIME 类型。
using Microsoft.AspNetCore.StaticFiles;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
// create a dictionary of mime types to add
var provider = new FileExtensionContentTypeProvider();
var dict = new Dictionary<string, string>
{
{".pdb" , "application/octet-stream" },
{".blat", "application/octet-stream" },
{".dll" , "application/octet-stream" },
{".dat" , "application/octet-stream" },
{".json", "application/json" },
{".wasm", "application/wasm" },
{".symbols", "application/octet-stream" }
};
// add the dictionary entries to the provider
foreach (var kvp in dict)
{
provider.Mappings[kvp.Key] = kvp.Value;
}
// set the provider to contain the added
// mime types
app.UseStaticFiles(new StaticFileOptions
{
ContentTypeProvider = provider
});
app.MapRazorPages();
app.Run();
MIME 类型被添加到 FileExtensionContentTypeProvider
对象中,然后该对象被指定为静态文件的提供程序。
var provider = new FileExtensionContentTypeProvider();
// create a dictionary of mime types to add
var dict = new Dictionary<string, string>
{
{".pdb" , "application/octet-stream" },
{".blat", "application/octet-stream" },
{".dll" , "application/octet-stream" },
{".dat" , "application/octet-stream" },
{".json", "application/json" },
{".wasm", "application/wasm" },
{".symbols", "application/octet-stream" }
};
// add the dictionary entries to the provider
foreach (var kvp in dict)
{
provider.Mappings[kvp.Key] = kvp.Value;
}
// set the provider to contain the added
// mime types
app.UseStaticFiles(new StaticFileOptions
{
ContentTypeProvider = provider
});
添加 wasmRunner.js 文件
我将 wasmRunner.js 文件添加到了 wwwroot 文件夹(与复制的 _framework 文件夹所在的文件夹相同)。
这是文件内容
// note that it expects to load dotnet.js
// (and wasm files) from _framework folder
import { dotnet } from './_framework/dotnet.js'
const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);
// get the objects needed to run exported C# code
const { getAssemblyExports, getConfig } =
await dotnet.create();
// config contains the web-site configurations
const config = getConfig();
// exports contain the methods exported by C#
const exports = await getAssemblyExports(config.mainAssemblyName);
// we call the exported C# method Greeter.JSInteropCallsContainer.Greet
// passing to it an array of names
const text = exports.Greeter.JSInteropCallsContainer.Greet(['Nick', 'Joe', 'Bob']);
// logging the result of Greet method call
console.log(text);
// adding the result of Greet method call to the inner text of
// an element out id "out"
document.getElementById("out").innerText = text;
该文件在代码中得到了很好的注释。以下是该文件中最重要的几行:
- 我们从 ./_framework/dotnet.js 文件加载
dotnet
对象。import { dotnet } from './_framework/dotnet.js'
- 我们从
dotnet
创建了两个方法——一个返回包含所有 C# 导出到 JavaScript 的对象的对象,另一个返回 WebAssembly 项目和网站的配置。const { getAssemblyExports, getConfig } = await dotnet.create();
- 我们调用
getConfig()
方法获取配置对象。const config = getConfig();
- 我们使用
getAssemblyExport(...)
方法获取包含所有 C# 导出的export
对象。const exports = await getAssemblyExports(config.mainAssemblyName);
- 我们调用导出的 C#
Greeter.JSInteropCallsContainer.Greet(...)
方法并将结果保存在变量text
中。const text = exports.Greeter.JSInteropCallsContainer.Greet(['Nick', 'Joe', 'Bob']);
- 最后,我们将获得的文本设置为我们 id 为 "out" 的
div
元素的 inner text。document.getElementById("out").innerText = text;
修改 Index.cshtml 文件
我修改的最后一个文件是 Pages/Index.cshtml 文件。我将其内容修改为:
<div id="out"
style="font-size:50px">
<div style="font-size:30px">
Please wait while Web Assembly is Loading!
</div>
</div>
<script type="module" src="~/wasmRunner.js"></script>
请注意,它有一个 id 为 "out" 的 div
元素,其内容将被文本替换。
还请注意,它从 wwwroot 文件夹加载了 wamsRunner.js 模块(这就是 ~/ 的意思)。
提高浏览器内 C# 的性能
许多方法可以提高浏览器内 C# 代码的性能。最重要的是 AOT(Ahead-Of-Time)编译 C#。这可能会增加 .wasm 文件的大小,但会大大提高性能。
我们的示例演示了如何在 Release 配置中使用 AOT 编译。
您可以通过在命令行中进入 Greeter 项目文件夹并执行以下命令来创建 Greeter 的 Release AOT 版本
dotnet publish -c Release
这将在 bin\Release\net8.0-browser\publish\wwwroot 文件夹下创建一个 _framework 文件夹。
这个 _framework 文件夹将包含优化后的 .wasm 文件。以与之前相同的方式将此文件夹移动或复制到 JSCallingDotNetSample/wwwroot 文件夹下,现在运行项目时将加载优化后的 .wasm 文件。
重要提示:项目的 AOT 编译可能需要很长时间——如果项目足够大,甚至需要 10-15 分钟。然而,在我们的例子中,我们的项目非常小,AOT 构建应该只需要几秒钟。
再次查看 Greeter.csproj 项目文件。AOT 指令包含在一个以 Release 配置为条件的 PropertyGroup 中。
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<RunAOTCompilation>true</RunAOTCompilation>
</PropertyGroup>
创建构建依赖项并使用后置生成事件复制 _framework 文件夹
请注意,在 Debug 配置中,我们可以设置 Greeter 项目在主要的 JSCallingDotNetSample 项目之前生成,而无需设置项目依赖项。
为了正确执行此操作,请在解决方案资源管理器中右键单击 JSCallingDotNetSample 解决方案,然后选择“项目依赖项...”菜单项。
在打开的对话框中,在“项目”下选择 JSColldingDotNetSample,然后在“依赖项”面板中,确保选中了复选框。然后按“确定”按钮。
这将确保 Greeter 项目在主要的 ASP.NET JSCallingDotNetSample 项目之前生成。
现在,我们将以下行添加到 JSColldingDotNetSample.csproj 文件的末尾,以便在 Debug 模式下自动复制 _framework 文件夹。
<Project Sdk="Microsoft.NET.Sdk.Web">
...
<Target Condition="'$(Configuration)'=='Debug'"
Name="PostBuild"
AfterTargets="PostBuildEvent">
<Exec Command="xcopy "$(SolutionDir)Greeter\bin\$(Configuration)\net8.0-browser\wwwroot\_framework" "$(ProjectDir)wwwroot\_framework" /E /R /Y /I" />
</Target>
</Project>
请注意,这只能针对 Debug 选项完成,因为 Release 需要对 Greeter 项目执行发布步骤。当然,也应该可以自动化它,但此时我不想花时间去弄清楚。
C# 调用 JavaScript 程序的示例
此示例位于 DotNetCallingJSSample 文件夹中(其解决方案名称与文件夹名称相同)。它建立在先前的示例之上。
它修改了我们导出的方法
[JSExport]
public static string Greet(params string[] names)
{
...
}
以依赖于另一个方法
[JSImport("getGreetingWord", "CSharpMethodsJSImplementationsModule")]
public static partial string GetGreetingWord();
其实现是在 JavaScript 中提供的。
JSInteropCallsContainer
类在 Greeter 项目中包含两个方法,而不是一个。
Greeter.GetGreetingWord()
是一个未实现的方法。它被标记为 partial
并且具有 JSImport("getGreetingWord", "CSharpMethodsJSImplementationsModule")
属性。
public static partial class JSInteropCallsContainer
{
[JSImport("getGreetingWord", "CSharpMethodsJSImplementationsModule")]
public static partial string GetGreetingWord();
...
}
属性参数表示程序期望 GetGreetingWord()
方法由名为“CSharpMethodsJSImplementationsModule”的 JavaScript 模块中的 JavaScript getGreetingWord()
方法实现。这有点前向引用,但并非世界末日。
Greet(params string[] names)
方法已被稍微修改,以便从 GetGreetingWord()
方法获取问候语,而不是将其硬编码为“Hello”。
public static partial class JSInteropCallsContainer
{
[JSImport("getGreetingWord", "CSharpMethodsJSImplementationsModule")]
public static partial string GetGreetingWord();
// this simple static method is exported to JavaScript
// via WebAssembly
[JSExport]
public static string Greet(params string[] names)
{
var resultStr = string.Join(", ", names);
// return a string greeting comma separated names passed to it
// e.g. if the array of names contains two names "Joe" and "Jack"
// then the resulting string will be "Hello Joe, Jack!!!".
return $"{GetGreetingWord()} {resultStr}!!!";
}
}
在主要的 DotNetCallingJSSample 项目中唯一更改的文件是 wwwroot/wasmRunner.js。它有一个修改的行和一个插入的方法调用。
// get the objects needed to run exported C# code
const { getAssemblyExports, getConfig, setModuleImports } =
await dotnet.create();
// we set the module import
setModuleImports("CSharpMethodsJSImplementationsModule", {
getGreetingWord: () => { return "Hi"; }
});
请注意,除了我们已经在上一个示例中使用的 getAssemblyExport(...)
和 getConfig(...)
方法之外,我们还从 await dotnet.create()
调用中获取了 setModuleImports(...)
方法。
然后我们使用 setModuleImports(...)
方法将“CSharpMethodsJSImplementationsModule”模块中的 getGreetingWord()
方法设置为始终返回“Hi”。
现在,重新生成主要的 DotNetCallingJSSample 项目(以强制重新生成 Greeter 项目并复制 _framework 文件夹)并运行它。我们将看到“Hi Nick, Joe, Bob!!!”——问候语是“Hi”,而不是“Hello”。
运行 WebAssembly 中的 C# Main 方法示例
接下来,我将展示如何从 JavaScript 运行 C# Program.Main 方法。相应的示例位于 JSCallingCSharpMainMethodSample/JSCallingCSharpMainMethodSample.sln 解决方案下。
首先,重新生成并尝试运行主要的 JSCallingCSharpMainMethodSample 项目。按 F12 打开浏览器开发者工具。点击 Console 选项卡。您将看到所有输出到控制台的内容。
行“Welcome to WebAssembly Program.Main(string[] args)!!!”,然后是行“Here are the arguments passed to Program.Main:”以及最后“arg1”、“arg2”和“arg3”分别显示在自己的行上。
该解决方案中的 Greeter 项目只有一个简单的 C# 文件 Program.cs。
namespace Greeter;
public static partial class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Welcome to WebAssembly Program.Main(string[] args)!!!");
if (args.Length > 0)
{
Console.WriteLine();
Console.WriteLine("Here are the arguments passed to Program.Main:");
foreach(string arg in args)
{
Console.WriteLine($"\t{arg}");
}
}
}
}
它将向控制台打印“Welcome to WebAssembly Program.Main(string[] args)!!!”,然后如果传递了一些参数给 main,它还将打印“Here are the arguments passed to Program.Main:”这行,然后打印每个参数在其自己的行上。
现在看看 Greeter.csproj 文件。
<Project Sdk="Microsoft.NET.Sdk.WebAssembly">
<PropertyGroup>
<TargetFramework>net8.0-browser</TargetFramework>
<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>Exe</OutputType>
<StartupObject>Greeter.Program</StartupObject>
</PropertyGroup>
...
</Project>
注意——我们添加了 <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
,将 OutputType 更改为“Exe”,并添加了 StartupObject 行。
在 JSCallingCSharpMainMethodSample(主)项目中,唯一更改的文件是 wasmRunner.js。
// note that it expects to load dotnet.js
// (and wasm files) from _framework folder
import { dotnet } from './_framework/dotnet.js'
const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);
// get the dotnetRuntime containing all the methods
// and objects for invoking C# from JavaScript and
// vice versa.
const dotnetRuntime = await dotnet.create();
// config contains the web-site configurations
const config = dotnetRuntime.getConfig();
// call Program.Main(string[] args) method from JavaScript
// passing to it an array of arguments "arg1", "arg2" and "arg3"
我们从 await dotnet.create()
获取 dotnetRuntime
,然后调用其 dotnetRuntime.runMain(...)
方法来调用 C# 的 Program.Main(...)
方法。
...
const dotnetRuntime = await dotnet.create();
...
await dotnetRuntime.runMain(config.mainAssemblyName, ["arg1", "arg2", "arg3"]);
请注意,传递给 dotnetRuntime.runMain(...)
的第二个参数应该是字符串数组。这些字符串将作为 args
传递给 Program.Main(string[] args)
。这就是为什么程序将“arg1”、“arg2”和“arg3”打印到控制台的原因。如果您更改该数组中的参数,您将看到程序输出中的相应更改。
运行 Avalonia in Browser via WebAssembly
Avalonia in Browser 简介
Avalonia 可以在包括浏览器(通过 WebAssembly)在内的许多平台上运行。
Avalonia in Browser 示例项目位于 AvaInBrowserSample/AvaInBrowserSample.sln 解决方案下。
打开解决方案,并将 AvaInBrowserSample ASP.NET 项目设为您的启动项目。
运行项目
尝试重新生成 AvaInBrowserSample 项目,然后使用调试器运行它。几秒钟后,Avalonia 应用程序将出现在浏览器中。
当您按下“更改文本”按钮时,上面短语的第一个单词在“Hello”和“Hi”之间切换,而其余文本保持不变。
Avalonia 代码非常简单——自定义代码仅位于 MainView.xaml 和 MainView.xaml.cs 文件中。
按钮的回调触发了 TextBox
中文本的更改。
private void ChangeTextButton_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
if (GreetingTextBlock.Text?.StartsWith("Hello") == true)
{
GreetingTextBlock.Text = "Hi from Avalonia in-Browser!!!";
}
else
{
GreetingTextBlock.Text = "Hello from Avalonia in-Browser!!!";
}
}
创建客户端 C# Avalonia 项目
在这里,我将展示如何使用 Avalonia 模板创建 Avalonia-in-Browser 项目。
请注意,我假设我们位于解决方案文件夹 AvaInBrowserSample 中。
要创建 Avalonia WebAssembly 项目,我使用了 创建 Avalonia Web Assembly 项目 的说明。
- 我通过运行以下命令安装 wasm-tools(或确保它们已安装并更新):
dotnet workload install wasm-tools
从命令行。 - 我通过运行以下命令将 Avalonia .NET 模板更新到最新版本:
dotnet new install avalonia.templates
- 我创建了 AvaCode 文件夹用于 Avalonia 项目,并使用命令行 cd 进入该文件夹。
- 在该文件夹内,我从命令行运行:
dotnet new avalonia.xplat
- 这将创建共享项目 AvaCode(位于同名文件夹中)以及多个平台特定的项目。
- 我删除了大多数平台特定的项目,只保留 AvaCode.Browser(用于构建 Avalonia WebAssembly 包)和 AvaCode.Display(如果需要进行调试和更快的原型设计)。
然后,我使用 Visual Studio 将这三个项目添加到我的 AvaInBrowserSample 解决方案中。我将这些项目放在一个单独的解决方案文件夹 AvaCode 中。
请注意,图片顶部的 3 个 Avalonia 项目位于 AvaCode 解决方案文件夹中。
现在我可以构建我的 Avalonia 功能(在 AvaCode 项目中),并通过运行 AvaCode.Desktop 项目来测试它。
我也可以在浏览器中进行测试,方法是将目录更改为 AvaCode.Browser 项目,然后在命令行执行“dotnet run”命令。
对主项目的更改
为了让我的主 ASP.NET 项目显示 Avalonia 的 MainView,我将 app.css 文件从 AvaCode.Browser/wwwroot 文件夹复制到 ASP.NET 项目的 AvaInBrowserSample/wwwroot/css/ 文件夹中。
然后我修改了 _Layout.cshtml 文件,使其包含指向 app.css 文件的链接,然后我将一些魔术词样式 style="margin: 0px; overflow: hidden"
添加到 <body>
标签,并简化了 <body>
标签内的区域,以确保 @RenderBody()
调用直接位于该标签下方。
<body style="margin: 0px; overflow: hidden"> @RenderBody() <script src="~/js/site.js" asp-append-version="true"></script> @await RenderSectionAsync("Scripts", required: false) </body>
现在我们需要修改 wasmRunner.js 文件。它看起来与上一节中的文件几乎相同,但在 dotnet.
和 .create()
方法之间会有一些额外的调用。
import { dotnet } from './_framework/dotnet.js'
const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);
const dotnetRuntime = await dotnet
.withDiagnosticTracing(false) // some extra methods
.withApplicationArgumentsFromQuery() // some extra methods
.create();
const config = dotnetRuntime.getConfig();
await dotnetRuntime.runMain(config.mainAssemblyName, [window.location.search]);
要更改的最后一个文件是 Index.cshtml。此文件使用从 AvaCode.Browser 项目复制过来的 app.css 文件中定义的某些 CSS 类。没有这些 CSS 类,Avalonia 就无法正确占据浏览器空间(全部空间)。
<div id="out">
<div id="out">
<div id="avalonia-splash">
<div class="center">
<h2 class="purple">
Please wait while the Avalonia Application Loads
</h2>
</div>
</div>
</div>
</div>
<script type="module" src="~/wasmRunner.js"></script>
结论
本文解释了如何在浏览器中嵌入 C# .NET 代码,并提供了一些易于理解的示例。