MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C#与Blazor框架构建WebAssembly应用

2024-07-054.1k 阅读

C# 与 Blazor 框架构建 WebAssembly 应用

Blazor 框架简介

Blazor 是一个使用 .NET 构建交互式客户端 Web UI 的框架。它允许开发者使用 C# 代替 JavaScript 来编写客户端应用逻辑。Blazor 应用可以在浏览器中直接运行,这得益于 WebAssembly 技术,它为 Blazor 提供了在浏览器环境中高效执行 .NET 代码的能力。

Blazor 框架有两种主要的托管模型:Server - Side Blazor 和 WebAssembly Blazor。Server - Side Blazor 应用在服务器上运行,并通过 SignalR 与客户端浏览器建立实时连接,将 UI 更改推送至客户端。而 WebAssembly Blazor 应用则将 .NET 运行时、应用代码及其依赖项编译为 WebAssembly 并下载到客户端浏览器中运行,完全在客户端执行,减少了服务器负载。

WebAssembly 基础

WebAssembly(简称 Wasm)是一种新的二进制指令格式,专为在现代网络浏览器中运行而设计。它提供了一种高效的方式来在浏览器中运行多种编程语言编写的代码,而不仅仅是 JavaScript。WebAssembly 代码以紧凑的二进制格式存储,加载速度快,并且可以接近原生性能运行。

WebAssembly 有自己的文本格式(.wat),可以通过文本编辑器编写,然后编译为二进制格式(.wasm)。然而,大多数开发者会使用像 C# 这样的高级语言,并借助工具链将其编译为 WebAssembly。在 Blazor 应用中,.NET 代码会被编译为 WebAssembly,使得应用能够在浏览器环境中独立运行,与 JavaScript 应用相比,具有更好的性能和安全性。

搭建开发环境

要开始使用 C# 和 Blazor 构建 WebAssembly 应用,首先需要安装以下工具:

  1. .NET SDK:可以从微软官方网站下载并安装最新版本的 .NET SDK。它包含了编译器、运行时以及各种开发工具,是构建 .NET 应用的基础。
  2. IDE(Integrated Development Environment):推荐使用 Visual Studio 或 Visual Studio Code。Visual Studio 是一款功能强大的全功能 IDE,专为 .NET 开发而设计,提供了丰富的代码编辑、调试和项目管理功能。Visual Studio Code 则是一款轻量级的跨平台代码编辑器,通过安装相关的扩展,也能很好地支持 .NET 和 Blazor 开发。

创建 Blazor WebAssembly 项目

在安装好上述工具后,可以通过以下步骤创建一个新的 Blazor WebAssembly 项目:

  1. 使用 Visual Studio:打开 Visual Studio,点击“创建新项目”,在项目模板中搜索“Blazor WebAssembly App”,选择该模板并点击“下一步”。在后续步骤中,设置项目名称、位置和解决方案名称,然后点击“创建”。
  2. 使用 Visual Studio Code 和 .NET CLI:打开终端,导航到希望创建项目的目录,然后运行以下命令:
dotnet new blazorwasm -o MyBlazorApp
cd MyBlazorApp

这将使用 .NET 命令行界面(CLI)创建一个名为“MyBlazorApp”的新 Blazor WebAssembly 项目,并进入该项目目录。

Blazor 组件基础

Blazor 应用是由组件组成的。组件是可重用的 UI 片段,它们可以包含标记(HTML)、C# 代码和样式。

组件结构

Blazor 组件通常以 .razor 文件的形式存在。例如,创建一个名为“Counter.razor”的组件:

@page "/counter"
<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

在这个组件中:

  • @page "/counter" 定义了该组件的路由,使得在浏览器中访问“/counter”路径时会渲染此组件。
  • <h1>Counter</h1><p>Current count: @currentCount</p> 是 HTML 标记部分,其中 @currentCount 是一个 Razor 表达式,将 C# 变量 currentCount 的值嵌入到 HTML 中。
  • <button> 元素绑定了一个点击事件 @onclick="IncrementCount",当按钮被点击时,会调用 C# 方法 IncrementCount
  • @code 块中定义了 C# 代码,包括变量 currentCount 和方法 IncrementCount

组件参数

组件可以接受参数,使其更加通用和可重用。例如,创建一个名为“Greeting.razor”的组件:

@page "/greeting"
@using System

<h1>@GreetingText, @(DateTime.Now.ToShortTimeString())</h1>

@code {
    [Parameter]
    public string GreetingText { get; set; } = "Hello";
}

在其他组件中使用这个 Greeting 组件时,可以传递不同的参数值:

<Greeting GreetingText="Good morning" />

这里,通过 GreetingText 参数为 Greeting 组件传递了不同的问候语。

数据绑定

数据绑定是 Blazor 中一个重要的特性,它允许在组件的 UI 和底层数据之间建立双向连接。

单向数据绑定

单向数据绑定是将数据从组件的 C# 代码绑定到 UI。例如:

@page "/databind"
<p>@message</p>

@code {
    private string message = "Initial message";

    protected override void OnInitialized()
    {
        message = "Updated message";
    }
}

在这个例子中,message 变量的值从 C# 代码绑定到了 <p> 标签中的 UI。当 OnInitialized 方法被调用时,message 的值更新,UI 也会相应更新。

双向数据绑定

双向数据绑定允许在 UI 和 C# 代码之间双向同步数据。通常用于表单元素。例如:

@page "/databindform"
<input type="text" @bind="userInput" />
<p>You entered: @userInput</p>

@code {
    private string userInput;
}

这里,<input> 元素使用 @bind 指令与 userInput 变量建立双向绑定。当用户在输入框中输入内容时,userInput 变量的值会更新;同时,如果在 C# 代码中更新 userInput 的值,输入框中的内容也会相应改变。

依赖注入

依赖注入(Dependency Injection,简称 DI)是一种软件设计模式,它允许将对象的创建和依赖关系管理从对象本身中分离出来。在 Blazor 应用中,依赖注入非常有用,它使得组件之间的耦合度降低,提高了代码的可测试性和可维护性。

注册服务

在 Blazor 应用中,可以在 Program.cs 文件中注册服务。例如,假设有一个简单的服务接口 IGreetingService 和实现类 GreetingService

public interface IGreetingService
{
    string GetGreeting();
}

public class GreetingService : IGreetingService
{
    public string GetGreeting()
    {
        return "Hello from service";
    }
}

Program.cs 中注册这个服务:

using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading.Tasks;

namespace MyBlazorApp
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("#app");

            builder.Services.AddScoped<IGreetingService, GreetingService>();

            await builder.Build().RunAsync();
        }
    }
}

使用服务

在组件中使用这个服务:

@page "/useservice"
@inject IGreetingService greetingService
<h1>@greetingService.GetGreeting()</h1>

通过 @inject 指令,组件可以获取并使用已注册的服务实例。

与 JavaScript 交互

虽然 Blazor 允许使用 C# 编写客户端逻辑,但有时可能仍需要与 JavaScript 进行交互,例如访问浏览器的一些特定 API 或者使用现有的 JavaScript 库。

调用 JavaScript 函数

Blazor 提供了 IJSRuntime 接口来调用 JavaScript 函数。首先,创建一个 JavaScript 文件,例如“interop.js”:

export function showAlert(message) {
    alert(message);
}

在 Blazor 组件中调用这个函数:

@page "/jsinterop"
@inject IJSRuntime jsRuntime
<button @onclick="ShowJsAlert">Call JavaScript alert</button>

@code {
    private async Task ShowJsAlert()
    {
        await jsRuntime.InvokeVoidAsync("interop.showAlert", "Hello from Blazor!");
    }
}

这里,通过 jsRuntime.InvokeVoidAsync 方法调用了 JavaScript 中的 showAlert 函数,并传递了一个消息。

JavaScript 调用 C# 方法

也可以从 JavaScript 调用 Blazor 组件中的 C# 方法。首先,在 Blazor 组件中注册一个可调用的方法:

@page "/jsinvoke"
@inject IJSRuntime jsRuntime
<button @onclick="RegisterJsCallback">Register callback</button>

@code {
    private async Task RegisterJsCallback()
    {
        await jsRuntime.InvokeVoidAsync("interop.registerCallback", DotNetObjectReference.Create(this), nameof(OnJsCallback));
    }

    [JSInvokable]
    public void OnJsCallback()
    {
        Console.WriteLine("Callback from JavaScript");
    }
}

在 JavaScript 文件“interop.js”中:

export function registerCallback(dotNetObject, callbackName) {
    document.addEventListener('click', function () {
        dotNetObject.invokeMethodAsync(callbackName);
    });
}

这里,当用户点击页面时,JavaScript 会调用 Blazor 组件中的 OnJsCallback 方法。

路由与导航

Blazor 提供了内置的路由和导航功能,使得创建单页应用(SPA)变得容易。

定义路由

在组件中通过 @page 指令定义路由,如前面的 Counter.razor 组件:

@page "/counter"
<h1>Counter</h1>
...

这里,“/counter”就是该组件的路由。

导航

可以使用 <NavLink> 组件进行导航。例如,在 Index.razor 组件中添加一个导航链接到 Counter 组件:

@page "/"
<NavLink href="/counter">Go to Counter</NavLink>

<NavLink> 组件会自动添加激活样式,当导航到对应的路由时,链接会有特殊的样式表示当前处于激活状态。

路由参数

路由可以包含参数。例如,创建一个 UserDetails.razor 组件,用于显示特定用户的详细信息:

@page "/user/{UserId}"
<h1>User Details for @UserId</h1>

@code {
    [Parameter]
    public string UserId { get; set; }
}

在其他组件中导航到这个组件并传递参数:

<NavLink href="/user/123">User 123 Details</NavLink>

这里,“123”就是传递给 UserDetails 组件的 UserId 参数值。

状态管理

在 Blazor 应用中,状态管理是一个重要的方面,尤其是在多个组件之间共享数据时。

简单状态管理

对于简单的状态管理,可以在一个组件中定义共享数据,并将该组件作为其他组件的父组件。例如:

@page "/statemanage"
<ChildComponent Message="Shared message" />

@code {
    public string SharedMessage { get; set; } = "Initial shared message";
}

@code {
    public partial class ChildComponent : ComponentBase
    {
        [Parameter]
        public string Message { get; set; }
    }
}

这里,SharedMessage 数据通过参数传递给了 ChildComponent

使用状态管理库

对于更复杂的状态管理场景,可以使用一些状态管理库,如 Redux - like 库。例如,使用 Fluxor 库。首先,安装 Fluxor NuGet 包。

定义一个状态类:

public class CounterState
{
    public int Count { get; set; }
}

定义一个动作(Action):

public record IncrementCounterAction();

定义一个 reducer 来处理动作并更新状态:

public class CounterReducer
{
    [ReducerMethod]
    public static CounterState Reduce(CounterState state, IncrementCounterAction action)
    {
        return new CounterState { Count = state.Count + 1 };
    }
}

在组件中使用状态和调度动作:

@page "/fluxorcounter"
@using Fluxor
@inject IDispatcher dispatcher
@inject IState<CounterState> counterState

<p>Current count: @counterState.Value.Count</p>
<button @onclick="Increment">Increment</button>

@code {
    private void Increment()
    {
        dispatcher.Dispatch(new IncrementCounterAction());
    }
}

通过这种方式,可以实现更复杂的状态管理逻辑,并且在多个组件之间共享状态。

部署 Blazor WebAssembly 应用

完成 Blazor WebAssembly 应用的开发后,需要将其部署到服务器上供用户访问。

发布应用

可以使用 Visual Studio 或 .NET CLI 发布应用。使用 .NET CLI,在项目目录中运行以下命令:

dotnet publish -c Release -o publish

这将在“publish”目录中生成发布版本的应用,包括编译后的 WebAssembly 文件、静态资源等。

部署到 Web 服务器

将“publish”目录中的内容上传到 Web 服务器。常见的 Web 服务器如 IIS(Internet Information Services)、Apache 等都可以用于部署 Blazor WebAssembly 应用。

  • IIS 部署:在 IIS 中创建一个新的网站或应用程序,将发布目录中的内容复制到相应的物理路径。确保 IIS 配置了正确的 MIME 类型,特别是对于 .wasm 文件(可以添加 MIME 类型 application/wasm)。
  • Apache 部署:将发布目录中的内容复制到 Apache 的文档根目录或相应的虚拟主机目录。同样,需要配置正确的 MIME 类型,在 Apache 的配置文件中添加以下内容:
AddType application/wasm .wasm

通过以上步骤,就可以成功部署 Blazor WebAssembly 应用,让用户通过浏览器访问。

性能优化

在构建 Blazor WebAssembly 应用时,性能优化至关重要,以确保应用在各种网络环境和设备上都能快速响应。

代码优化

  1. 减少不必要的渲染:Blazor 默认会在状态变化时重新渲染组件。可以通过 ShouldRender 方法来控制组件是否需要重新渲染。例如:
@page "/optimize"
@using System

<p>@message</p>
<button @onclick="UpdateMessage">Update</button>

@code {
    private string message = "Initial message";
    private DateTime lastUpdate = DateTime.Now;

    protected override bool ShouldRender()
    {
        var currentTime = DateTime.Now;
        if ((currentTime - lastUpdate).TotalSeconds < 1)
        {
            return false;
        }
        lastUpdate = currentTime;
        return true;
    }

    private void UpdateMessage()
    {
        message = "Updated message";
    }
}

在这个例子中,ShouldRender 方法会在每次状态变化时被调用,通过判断时间间隔来决定是否进行渲染,避免了过于频繁的渲染。

  1. 优化数据绑定:避免在绑定表达式中执行复杂的计算。如果需要进行复杂计算,可以在 C# 代码中预先计算好,然后绑定结果。例如:
@page "/databindopt"
<p>@formattedMessage</p>

@code {
    private string originalMessage = "Some long text here";
    private string formattedMessage;

    protected override void OnInitialized()
    {
        formattedMessage = FormatMessage(originalMessage);
    }

    private string FormatMessage(string message)
    {
        // 复杂的格式化逻辑
        return message.ToUpper().Trim();
    }
}

这里在 OnInitialized 方法中预先计算好 formattedMessage,而不是在绑定表达式中每次都执行 FormatMessage 方法。

资源优化

  1. 代码拆分:Blazor WebAssembly 应用在初始加载时会下载所有的代码和依赖项。可以使用代码拆分来延迟加载部分代码。例如,对于一些不常用的功能模块,可以将其拆分为单独的组件,并使用 NavigationManager 来按需加载。
@page "/lazyload"
@inject NavigationManager navigationManager
<button @onclick="LoadLazyComponent">Load Lazy Component</button>

@code {
    private void LoadLazyComponent()
    {
        navigationManager.NavigateTo("/lazycomponent");
    }
}

LazyComponent.razor 组件中,它只会在用户导航到该组件时才会加载:

@page "/lazycomponent"
<h1>Lazy - Loaded Component</h1>
  1. 优化资源大小:减少不必要的依赖项,确保只引入应用真正需要的库和文件。对于静态资源,如图片、样式表等,可以进行压缩和优化。例如,使用工具对图片进行压缩,减少文件大小,同时不影响图片质量。

安全性

在构建 Blazor WebAssembly 应用时,安全性是不容忽视的方面。

身份验证与授权

  1. 身份验证:可以使用 ASP.NET Core Identity 来实现身份验证。首先,在项目中安装相关的 NuGet 包,如 Microsoft.AspNetCore.Components.WebAssembly.Authentication。然后,在 Program.cs 中配置身份验证:
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading.Tasks;

namespace MyBlazorApp
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("#app");

            builder.Services.AddApiAuthorization();

            await builder.Build().RunAsync();
        }
    }
}

在组件中,可以使用 AuthorizeView 组件来根据用户的身份验证状态显示不同的内容:

@page "/auth"
<AuthorizeView>
    <Authorized>
        <p>Welcome, @context.User.Identity.Name!</p>
    </Authorized>
    <NotAuthorized>
        <p>Please log in.</p>
    </NotAuthorized>
</AuthorizeView>
  1. 授权:基于角色或策略的授权可以通过在控制器或组件中使用 Authorize 属性来实现。例如,只有具有“Admin”角色的用户才能访问某个组件:
@page "/admin"
@attribute [Authorize(Roles = "Admin")]
<h1>Admin Page</h1>

防止跨站脚本攻击(XSS)

Blazor 会自动对绑定到 UI 的数据进行 HTML 编码,以防止 XSS 攻击。例如:

@page "/xss"
@using System.Text

<p>@htmlEncodedText</p>

@code {
    private string maliciousText = "<script>alert('XSS')</script>";
    private string htmlEncodedText;

    protected override void OnInitialized()
    {
        htmlEncodedText = System.Net.WebUtility.HtmlEncode(maliciousText);
    }
}

这里,htmlEncodedText 会被正确编码并显示为文本,而不是执行恶意脚本。

防止跨站请求伪造(CSRF)

Blazor 应用在提交表单或进行 HTTP 请求时,会自动包含 CSRF 令牌。例如,在一个表单组件中:

@page "/csrf"
<form method="post">
    <input type="hidden" name="__RequestVerificationToken" value="@(context.RequestVerificationToken)" />
    <input type="submit" value="Submit" />
</form>

@code {
    [CascadingParameter]
    private Microsoft.AspNetCore.Components.Forms.EditContext context { get; set; }
}

服务器端会验证这个令牌,以防止 CSRF 攻击。

通过以上对 C# 与 Blazor 框架构建 WebAssembly 应用的各个方面的介绍,开发者可以全面了解如何使用这些技术构建高效、安全且功能丰富的 Web 应用。从基础的组件开发到复杂的状态管理、性能优化和安全性保障,Blazor 提供了一套完整的解决方案,使得 C# 开发者能够在 Web 前端领域发挥其优势。