使用 Razor 类库 (RCL) 在 Web 和本机客户端之间共享资产

注意

此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

对于当前版本,请参阅此文的 .NET 8 版本

使用 Razor 类库 (RCL) 在 Web 和本机客户端项目之间共享 Razor 组件、C# 代码和静态资产。

本文基于以下文章中的一般概念:

本文中的示例在同一解决方案中服务器端 Blazor 应用和 .NET MAUIBlazor Hybrid 应用之间共享资产:

  • 尽管使用了服务器端 Blazor 应用,但该指南同样适用于与 Blazor Hybrid 应用共享资产的 Blazor WebAssembly 应用。
  • 项目在同一个解决方案中,但 RCL 可以为解决方案之外的项目提供共享资产。
  • RCL 作为项目添加到解决方案中,但任何 RCL 都可以作为 NuGet 包发布。 NuGet 包可以为 Web 和本机客户端项目提供共享资产。
  • 创建项目的顺序并不重要。 但是,依赖 RCL 获取资产的项目必须在创建 RCL 之后创建对 RCL 的项目引用。

有关创建 RCL 的指导,请参阅使用 Razor 类库 (RCL) 中的 ASP.NET Core Razor 组件。 (可选)在具有 ASP.NET Core 的类库中的可重用Razor UI 中访问有关广泛适用于 ASP.NET Core 应用的 RCL 的其他指南。

ClickOnce 部署的目标框架

要使用 ClickOnce 在 .NET 6 中发布带有 Razor 类库 (RCL) 的 WPF 或 Windows 窗体项目,除 net6.0 之外,RCL 还必须以 net6.0-windows 为目标。

示例:

<TargetFrameworks>net6.0;net6.0-windows</TargetFrameworks>

有关详细信息,请参阅以下文章:

共享 Web UI Razor 组件、代码和静态资产

RCL 中的组件可以同时由使用 Blazor 生成的 Web 和本机客户端应用共享。 使用 Razor 类库 (RCL) 中的 ASP.NET Core Razor 组件中的指南阐述了如何使用 Razor 类库 (RCL) 共享 Razor 组件。 同样的指南适用于在 Blazor Hybrid 应用中重用来自 RCL 的 Razor 组件。

组件命名空间派生自 RCL 的包 ID 或程序集名称以及 RCL 中组件的文件夹路径。 有关详细信息,请参阅 ASP.NET Core Razor 组件@using 指令可以放置在组件和代码的 _Imports.razor 文件中,如以下名为 SharedLibrary 的 RCL 示例所示,其中包含共享 Razor 组件的 Shared 文件夹和共享数据类的 Data 文件夹:

@using SharedLibrary
@using SharedLibrary.Shared
@using SharedLibrary.Data

将共享静态资源放置在 RCL 的 wwwroot 文件夹中,并更新应用中的静态资源路径以使用以下路径格式:

_content/{PACKAGE ID/ASSEMBLY NAME}/{PATH}/{FILE NAME}

占位符:

  • {PACKAGE ID/ASSEMBLY NAME}:RCL 的包 ID 或程序集名称。
  • {PATH}:RCL 的 wwwroot 文件夹中的可选路径。
  • {FILE NAME}:静态资产的文件名。

上述路径格式也用于应用中由添加到 RCL 的 NuGet 包提供的静态资产。

对于名为 SharedLibrary 并使用缩小的 Bootstrap 样式表作为示例的 RCL:

_content/SharedLibrary/css/bootstrap/bootstrap.min.css

有关如何跨项目共享静态资产的更多信息,请参阅以下文章:

index.html 文件通常特定于应用,应保留在 Blazor Hybrid 应用或 Blazor WebAssembly 应用中。 index.html 文件通常不共享。

根 Razor 组件(App.razorMain.razor)可以共享,但通常可能需要特定于托管应用。 例如,启用身份验证后,服务器端 Blazor 和 Blazor WebAssembly 项目模板中的 App.razor 是不同的。 可以添加 AdditionalAssemblies 参数来指定任何共享可路由组件的位置,并且可以按类型名称指定路由器的共享默认布局组件

提供独立于托管模型的代码和服务

如果托管模型或目标平台的代码必须不同,可将代码抽象为接口并在每个项目中注入服务实现。

以下天气数据示例抽象了不同的天气预报服务实现:

  • 对 Blazor Hybrid 和 Blazor WebAssembly 使用 HTTP 请求。
  • 直接为服务器端 Blazor 应用请求数据。

该示例使用以下规范和约定:

  • RCL 名为 SharedLibrary 并包含以下文件夹和命名空间:
    • Data:包含 WeatherForecast 类,用作天气数据的模型。
    • Interfaces:包含服务实现的服务接口,名为 IWeatherForecastService
  • FetchData 组件保存在 RCL 的 Pages 文件夹中,任何使用 RCL 的应用都可以路由该组件。
  • 每个 Blazor 应用都维护一个实现 IWeatherForecastService 接口的服务实现。

RCL 中的 Data/WeatherForecast.cs

namespace SharedLibrary.Data;

public class WeatherForecast
{
    public DateTime Date { get; set; }
    public int TemperatureC { get; set; }
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    public string? Summary { get; set; }
}

RCL 中的 Interfaces/IWeatherForecastService.cs

using SharedLibrary.Data;

namespace SharedLibrary.Interfaces;

public interface IWeatherForecastService
{
    Task<WeatherForecast[]?> GetForecastAsync(DateTime startDate);
}

RCL 中的 _Imports.razor 文件包括以下添加的命名空间:

@using SharedLibrary.Data
@using SharedLibrary.Interfaces

Blazor Hybrid 和 Blazor WebAssembly 应用中的 Services/WeatherForecastService.cs

using System.Net.Http.Json;
using SharedLibrary.Data;
using SharedLibrary.Interfaces;

namespace {APP NAMESPACE}.Services;

public class WeatherForecastService : IWeatherForecastService
{
    private readonly HttpClient http;

    public WeatherForecastService(HttpClient http)
    {
        this.http = http;
    }

    public async Task<WeatherForecast[]?> GetForecastAsync(DateTime startDate) =>
        await http.GetFromJsonAsync<WeatherForecast[]?>("WeatherForecast");
}

在上述示例中,{APP NAMESPACE} 占位符是应用的命名空间。

在服务器端 Blazor 应用中的 Services/WeatherForecastService.cs

using SharedLibrary.Data;
using SharedLibrary.Interfaces;

namespace {APP NAMESPACE}.Services;

public class WeatherForecastService : IWeatherForecastService
{
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot"
    };

    public async Task<WeatherForecast[]?> GetForecastAsync(DateTime startDate) =>
        await Task.FromResult(Enumerable.Range(1, 5)
            .Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            }).ToArray());
}

在上述示例中,{APP NAMESPACE} 占位符是应用的命名空间。

Blazor Hybrid、Blazor WebAssembly 和服务器端 Blazor 应用为 IWeatherForecastService 注册其天气预报服务实现(Services.WeatherForecastService)。

Blazor WebAssembly 项目还注册了一个 HttpClient。 在基于 Blazor WebAssembly 项目模板创建的应用中默认注册的 HttpClient 足以满足此目的。 有关详细信息,请参阅在 ASP.NET Core Blazor 应用中调用 Web API

RCL 中的 Pages/FetchData.razor

@page "/fetchdata"
@inject IWeatherForecastService ForecastService

<PageTitle>Weather forecast</PageTitle>

<h1>Weather forecast</h1>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }
}

其他资源