65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (27投票s)

2024年5月13日

MIT

14分钟阅读

viewsIcon

44949

在本文中,我将介绍如何在浏览器中编写 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# 在浏览器中功能的所有或大部分内容。

文章大纲

本文涵盖的主题如下

  1. 创建 Wasm 项目并将其嵌入到浏览器代码中。
  2. 从 Wasm C# 调用浏览器 JavaScript 方法,反之亦然——从浏览器内 JavaScript 调用 Wasm C# 方法。
  3. 调用 C# Program.Main 方法。
  4. 在浏览器中运行 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 解决方案包含两个项目

  1. JSCallingDotNetSample - 一个 ASP.NET 8.0 项目。请确保这是您的启动项目。
  2. 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# 库项目相比的高亮显示的区别

  1. Sdk 设置为 Microsoft.NET.Sdk.WebAssembly
  2. TargetFramework 设置为 net8.0-browser
  3. <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;  

该文件在代码中得到了很好的注释。以下是该文件中最重要的几行:

  1. 我们从 ./_framework/dotnet.js 文件加载 dotnet 对象。
    import { dotnet } from './_framework/dotnet.js'  
    
  2. 我们从 dotnet 创建了两个方法——一个返回包含所有 C# 导出到 JavaScript 的对象的对象,另一个返回 WebAssembly 项目和网站的配置。
    const { getAssemblyExports, getConfig } = await dotnet.create();        
    
  3. 我们调用 getConfig() 方法获取配置对象。
    const config = getConfig();  
    
  4. 我们使用 getAssemblyExport(...) 方法获取包含所有 C# 导出的 export 对象。
    const exports = await getAssemblyExports(config.mainAssemblyName);
    
  5. 我们调用导出的 C# Greeter.JSInteropCallsContainer.Greet(...) 方法并将结果保存在变量 text 中。
    const text = exports.Greeter.JSInteropCallsContainer.Greet(['Nick', 'Joe', 'Bob']);        
    
  6. 最后,我们将获得的文本设置为我们 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 &quot;$(SolutionDir)Greeter\bin\$(Configuration)\net8.0-browser\wwwroot\_framework&quot; &quot;$(ProjectDir)wwwroot\_framework&quot; /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 项目 的说明。

  1. 我通过运行以下命令安装 wasm-tools(或确保它们已安装并更新):
    dotnet workload install wasm-tools        
    
    从命令行。
  2. 我通过运行以下命令将 Avalonia .NET 模板更新到最新版本:
    dotnet new install avalonia.templates        
    
  3. 我创建了 AvaCode 文件夹用于 Avalonia 项目,并使用命令行 cd 进入该文件夹。
  4. 在该文件夹内,我从命令行运行:
    dotnet new avalonia.xplat        
    
  5. 这将创建共享项目 AvaCode(位于同名文件夹中)以及多个平台特定的项目。
  6. 我删除了大多数平台特定的项目,只保留 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 代码,并提供了一些易于理解的示例。

© . All rights reserved.