通过WebAssembly在浏览器中使用C#(不使用Blazor)

目录

介绍

Avalonia

为什么“不使用Blazor”

WebAssembly的主要问题——缺乏好的样本和好的文档

文章大纲

将ASP.NET Core用于示例

示例源代码位置

JavaScript调用C#方法示例

重要提示

示例位置

示例代码概述

解决方案和项目的创建方式

运行项目

仅限C#的Greeter项目

JSCallingDotNet示例代码

小改动

对Program.cs文件的修改

添加wasmRunner.js文件

修改Index.cshtml文件

提高浏览器内C#的性能

创建生成依赖项并使用生成后事件复制_framework文件夹

C#调用JavaScript程序的示例

在Web Assembly示例中运行C# Main方法

通过WebAssembly在浏览器中运行Avalonia

浏览器中Avalonia的小介绍

运行项目

创建客户端C# Avalonia项目

对主项目的更改

结论


介绍

C#程序(以及用许多其他软件语言编写的程序)现在可以通过WebAssembly(简称Wasm)在几乎任何浏览器中运行。

除了用更好的语言编写之外,WebAssembly程序通常比JavaScript/TypeScript程序运行得更快,因为它们是编译的(至少是中级语言,有些甚至编译为本机汇编语言)。此外,作为强类型语言,它们具有更小、更快的内置类型。

Avalonia

Avalonia是一个出色的多平台框架,用于构建UI应用程序。Avalonia可用于构建适用于WindowsMacOSLinux的桌面应用程序、浏览器WebAssembly应用程序以及适用于iOSAndroidTizen的移动应用程序。

最重要的是,Avalonia允许在不同平台之间重用大部分代码。

这里有一个网站,演示了作为Avalonia浏览器内应用程序构建的Avalonia控件: Avalonia演示

在本文中,我们将仅讨论通过Wasm的浏览器内Avalonia

为什么不使用Blazor”

Initial Microsoft’s WebAssembly支持产品称为Blazor(客户端版本),并作为ASP.NET的一部分发布。Blazor可用于执行非可视化操作,也可用于修改C#中的HTML树。

但是,在与HTML/JavaScript交互时,我看到了一些关于Blazor稳定性和性能的抱怨。这是网络上提到 Blazor稳定性和性能问题的一个地方。我记得在其他网站上也看到过类似的投诉。

由于这些问题,本文重点介绍如何使用System.Runtime.InteropServices.JavaScript包,该包已成为.NETWebAssembly SDK。据报道,该软件包提供了更好的性能和更稳定的与JavaScript的交互。

最新版本的 Avalonia 也使用此库。

WebAssembly的主要问题——缺乏好的样本和好的文档

虽然通过WasmC#已经准备好迎接黄金时期,但主要问题是它是一项相对较新的技术,它几乎没有好的样本和良好的在线文档。

本文的主要目的是提供易于理解的示例和良好的文档,涵盖所有或大部分C#浏览器功能。

文章大纲

以下是本文涵盖的主题:

  1. 创建Wasm项目并将其嵌入到浏览器代码中。
  2. 从Wasm C#调用浏览器JavaScript方法,反之亦然——从浏览器内JavaScript调用Wasm C#方法。
  3. 从JavaScript调用C# Program.Main方法。
  4. 在浏览器中运行Avalonia可视化应用程序。

ASP.NET Core用于示例

基于浏览器的编程总是意味着一个服务器——在这里的所有示例中,我都使用ASP.NET,因为ASP.NETMicrosoft的一项功能强大、经过充分测试、经过验证的技术,可以与我最喜欢的Visual Studio 2022很好地配合使用,并允许将HTML/JavaScript客户端代码与服务器代码放在同一个项目中。

为了速度和清晰起见,我尽量避免ASP.NET代码生成;相反,我使用ASP.NET作为Web和数据服务器以及中间层。

虽然ASP.NET是我选择的服务器,但部署和运行WebAssembly的完全相同的方法可以应用于任何其他服务器技术。

示例源代码位置

示例的源代码位于Web Assembly示例中。

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 APPRazor 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文件中(这是文件夹前面的红色小STOP图标的意思)。

生成并运行主JSCallingDotNetSample项目——首先,在一两秒钟内,您将看到一条消息请等待Web Assembly正在加载!,然后它将被C#代码生成的消息覆盖:

仅限C#Greeter项目

C# Greeter项目包含由JavaScript调用的C#代码(来自ASP.NET项目)。它只有一个静态类JSInteropCallsContainerGreet,并且该类具有一个方法,该方法采用字符串(名称)数组并返回问候语字符串“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>

这三项更改允许项目生成(作为构建的结果)包含.wasm和部署所需的其他文件_framework文件夹。

JSCallingDotNet示例代码

在这里,我将介绍使用“ASP.NET Core Web AppRazor Pages模板创建项目后对JSCallingDotNetSample ASP.NET项目中的文件所做的更改。

小改动

为了简化起见,我删除了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()方法来获取config配置对象:

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元素的内部文本,并带有:

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#代码的浏览器内性能。最重要的是编译C# AOT(提前)。这可能会增加.wasm文件的大小,但会大大提高性能。

我们的示例演示了如何在发布配置中使用AOT编译。

您可以通过转到命令行上的Greeter项目文件夹并执行以下命令来创建Greeter的发布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指令包含在以发布配置为条件的PropertyGroup中:

<PropertyGroup Condition="'$(Configuration)'=='Release'">
	<RunAOTCompilation>true</RunAOTCompilation>
</PropertyGroup>  

创建生成依赖项并使用生成后事件复制_framework文件夹

请注意,在Debug配置中,我们可以将项目Greeter设置为在主JSCallingDotNetSample项目之前生成,而无需设置项目依赖项。

为了正确执行此操作,请在解决方案资源管理器中单击解决方案JSCallingDotNetSample,然后选择项目依赖项...”菜单项:

在打开的对话框中,选择项目下的“JSColldingDotNetSample”,然后在“Depends on”面板中确保选中该复选框。然后按确定按钮。

这将确保Greeter项目在主ASP.NET JSCallingDotNetSample项目之前生成。

现在,若要在调试模式下自动复制_framework文件夹,我们将以下行添加到JSColldingDotNetSample.csproj文件的末尾:

<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中提供的。

Greeter项目中的JSInteropCallsContainer类有两个方法,而不是一个。

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文件夹)并运行它。我们将看到嗨,尼克,乔,鲍勃!!”——问候语是而不是你好

Web Assembly示例中运行C# Main方法

接下来,我将展示如何从JavaScript运行C# Program.Main方法。相应的示例位于JSCallingCSharpMainMethodSample/JSCallingCSharpMainMethodSample.sln解决方案下。

首先,重新生成并尝试运行主项目JSCallingCSharpMainMethodSample。按F12在浏览器中打开devtools。单击控制台选项卡。您将看到打印到控制台的任何内容:

“Welcome to WebAssembly Program.Mainstring[] 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.Mainstring[] 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

JSCallingCSharpMainMethodSamplemain)项目中,唯一更改的文件是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(...)的第二个参数应为字符串数组。这些是将传递给Program.Main(string[] args)作为args的字符串。这就是程序将“arg1”“arg2”“arg3”打印到控制台的原因。如果更改该数组中的参数,则会在程序输出中看到相应的更改。

通过WebAssembly在浏览器中运行Avalonia

浏览器中Avalonia的小介绍

Avalonia可以在许多平台上运行,包括通过WebAssembly在浏览器内运行。

浏览器示例项目中的Avalonia位于AvaInBrowserSample/AvaInBrowserSample.sln解决方案下。

打开解决方案,使AvaInBrowserSample ASP.NET项目成为启动项目。

运行项目

尝试重新生成AvaInBrowserSample项目,然后在调试器中运行它。几秒钟后,Avalonia应用程序将出现在浏览器中:

当您按下更改文本按钮时,上述短语的第一个单词在您好之间切换,而文本的其余部分保持不变。

Avalonia代码非常简单——自定义代码仅位于MainView.xamlMainView.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 dotnet模板:

dotnet new install avalonia.templates        

3、我为Avalonia项目创建文件夹AvaCode,并使用命令行将其cd到它。

4、在该文件夹中,我从命令行运行:

dotnet new avalonia.xplat        

5、这将创建共享项目AvaCode(在同名文件夹中)和许多特定于平台的项目。

6、我删除了大多数特定于平台的项目,只留下AvaCode.Browser(用于构建Avalonia WebAssembly包)和AvaCode.Display(用于调试和更快的原型设计,如果需要)。

然后,我使用Visual Studio将这三个项目添加到我的AvaInBrowserSample解决方案中。我将这些项目放在一个单独的解决方案文件夹AvaCode中:

请注意,3Avalonia项目位于AvaCode Solution文件夹中图像的顶部。

现在,我可以构建我的Avalonia功能(在AvaCode项目中),并通过从AvaCode.Desktop项目运行它来测试它。

我还可以通过将目录更改为AvaCode.Browser项目并在命令行上执行命令“dotnet run”来在浏览器中对其进行测试。

对主项目的更改

为了使我的主ASP.NET项目显示AvaloniaMainView,我将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代码嵌入到浏览器中,并提供一些易于理解的示例。

https://www.codeproject.com/Articles/5382292/Csharp-in-Browser-via-WebAssembly-without-Blazor

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值