添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

ASP.NET Web Forms 的优势之一在于其能够将可重用的用户界面 (UI) 代码片段封装到可重用的 UI 控件中。 可以使用 .ascx 文件在标记中定义自定义用户控件。 还可以在代码中生成具有全面设计器支持的复杂服务器控件。

Blazor 还支持通过组件进行 UI 封装。 组件具有以下特点:

  • 是自包含的 UI 块。
  • 维护自己的状态和呈现逻辑。
  • 可以定义 UI 事件处理程序、绑定到输入数据以及管理其自己的生命周期。
  • 通常使用 Razor 语法在 .razor 文件中定义。
  • Razor 简介

    Razor 是基于 HTML 和 C# 的轻量级标记模板化语言。 借助 Razor,可以在标记和 C# 代码之间无缝转换,以定义组件呈现逻辑。 编译 .razor 文件时,将在 .NET 类中以结构化方式捕获呈现逻辑。 编译类的名称从 .razor 文件名中获取。 命名空间从项目的默认命名空间和文件夹路径中获取,也可以使用 @namespace 指令(在下面的 Razor 指令中详细介绍)显式指定命名空间。

    组件的呈现逻辑使用常规 HTML 标记创作,并使用 C# 添加动态逻辑。 @ 字符用于转换为 C#。 Razor 通常很聪明,可猜出你何时切换回 HTML。 例如,以下组件使用当前时间呈现 <p> 标记:

    <p>@DateTime.Now</p>
    

    若要显式指定 C# 表达式的开头和结尾,请使用括号:

    <p>@(DateTime.Now)</p>
    

    使用 Razor 还可以在呈现逻辑中轻松地使用 C# 控制流。 例如,可以有条件地呈现某些 HTML,如下所示:

    @if (value % 2 == 0)
        <p>The value was even.</p>
    

    或者,可以使用常规 C# foreach 循环生成项目列表,如下所示:

    @foreach (var item in items) <li>@item.Text</li>

    像 ASP.NET Web Forms 中的指令一样,Razor 指令可控制 Razor 组件编译的许多方面。 示例包括组件的以下项:

  • 实现的接口
  • 导入的命名空间
  • Razor 指令以 @ 字符开头,通常用于文件开头的新行的开头。 例如,@namespace 指令定义组件的命名空间:

    @namespace MyComponentNamespace
    

    下表总结 Blazor 中使用的各种 Razor 指令及其 ASP.NET Web Forms 等效项(如果存在)。

    Web Forms 等效项

    Razor 组件还广泛使用元素上的指令属性来控制组件编译的各个方面(事件处理、数据绑定、组件和元素引用等)。 指令属性均遵循常用泛型语法,其中括号中的值是可选的:

    @directive(-suffix(:name))(="value")
    

    下表总结 Blazor 中使用的 Razor 指令的各种属性。

    若要将成员添加到 Razor 组件类,请使用 @code 指令。 此方法类似于在 ASP.NET Web Forms 用户控件或页中使用 <script runat="server">...</script> 块。

    @code {
        int count = 0;
        void IncrementCount()
            count++;
    

    因为 Razor 基于 C#,所以必须从 C# 项目 (.csproj) 中进行编译。 无法从 Visual Basic 项目 (.vbproj) 编译 .razor 文件。 仍然可以从 Blazor 项目引用 Visual Basic 项目。 反之亦然。

    有关完整的 Razor 语法参考,请参阅 ASP.NET Core 的 Razor 语法参考

    除常规 HTML 外,组件还可以将其他组件用作其呈现逻辑的一部分。 用于在 Razor 中使用组件的语法类似于在 ASP.NET Web Forms 应用中使用用户控件。 使用与组件的类型名称匹配的元素标记指定组件。 例如,可以添加 Counter 组件,如下所示:

    <Counter />
    

    与 ASP.NET Web Forms 不同,Blazor 中的组件:

  • 不使用元素前缀(例如,asp:)。
  • 不要求在页上或在 web.config 中进行注册。
  • 将 Razor 组件视为 .NET 类型,因为这正是其本质。 如果引用了包含组件的程序集,则该组件可供使用。 若要将组件的命名空间纳入范围,请应用 @using 指令:

    @using MyComponentLib
    <Counter />
    

    如默认的 Blazor 项目所示,通常将 @using 指令放入 _Imports.razor 文件中,以便将它们导入同一目录中和子目录中的所有 .razor 文件。

    如果组件的命名空间不在范围内,则可以使用其完整类型名称来指定组件,就像在 C# 中一样:

    <MyComponentLib.Counter />
    

    修改组件的页面标题

    在生成 SPA 样式的应用时,通常可以在不重新加载整个页面的情况下重新加载页面的某些部分。 尽管如此,根据当前加载的组件,更改页面标题可能会很有用。 这可以通过在组件的 Razor 页面中包含 <PageTitle> 标记来实现:

    @page "/"
    <PageTitle>Home</PageTitle>
    

    此元素的内容可以是动态的,例如显示消息的当前计数:

    <PageTitle>@MessageCount messages</PageTitle>
    

    请注意,如果特定页面上的多个组件包含 <PageTitle> 标记,则只会显示最后一个(因为每个组件都将覆盖前一个)。

    在 ASP.NET Web Forms 中,可以使用公共属性将参数和数据传递到控件。 这些属性可以使用特性在标记中进行设置,也可以直接在代码中设置。 Razor 组件以类似的方式工作,尽管组件属性还必须使用 [Parameter] 特性进行标记才能被视为组件参数。

    以下 Counter 组件定义名为 IncrementAmount 的组件参数,该参数可用于指定每次单击按钮时 Counter 应该递增的数量。

    <h1>Counter</h1>
    <p>Current count: @currentCount</p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
    @code {
        int currentCount = 0;
        [Parameter]
        public int IncrementAmount { get; set; } = 1;
        void IncrementCount()
            currentCount+=IncrementAmount;
    

    若要在 Blazor 中指定组件参数,请像在 ASP.NET Web Forms 中一样使用特性:

    <Counter IncrementAmount="10" />
    

    查询字符串参数

    Razor 组件也可以利用它们所呈现的页面的查询字符串的值作为参数源。 若要启用此参数,请将 [SupplyParameterFromQuery] 属性添加到参数中。 例如,下面的参数定义将以 ?IncBy=2 格式从请求中获取其值:

    [Parameter]
    [SupplyParameterFromQuery(Name = "IncBy")]
    public int IncrementAmount { get; set; } = 1;
    

    如果未在 [SupplyParameterFromQuery] 属性中提供自定义 Name,则默认情况下它将匹配属性名称(在这种情况下为 IncrementAmount)。

    组件和错误边界

    默认情况下,Blazor 应用将检测未处理的异常,并在页面底部显示错误消息,无其他详细信息。 若要限制受未处理错误影响的应用的某些部分,例如将影响限制在单个组件,可以将 <ErrorBoundary> 标记包装在组件声明周围。

    例如,若要防止从 Counter 组件引发的可能异常,请在 <ErrorBoundary> 中声明它,并且可以选择指定在出现异常时显示的消息:

    <ErrorBoundary>
        <ChildContent>
            <Counter />
        </ChildContent>
        <ErrorContent>
            Oops! The counter isn't working right now; please try again later.
        </ErrorContent>
    </ErrorBoundary>
    

    如果不需要指定自定义错误内容,则只需直接包装组件:

    <ErrorBoundary>
      <Counter />
    </ErrorBoundary>
    

    如果包装组件中发生未处理的异常,将显示指示“出现错误”的默认消息。

    事件处理程序

    ASP.NET Web Forms 和 Blazor 均提供用于处理 UI 事件的基于事件的编程模型。 此类事件的示例包括按钮单击和文本输入。 在 ASP.NET Web Forms 中,可以使用 HTML 服务器控件处理 DOM 公开的 UI 事件,也可以处理 Web 服务器控件公开的事件。 这些事件通过表单回发请求在服务器上显示。 考虑以下 Web Forms 按钮单击示例:

    Counter.ascx

    <asp:Button ID="ClickMeButton" runat="server" Text="Click me!" OnClick="ClickMeButton_Click" />
    

    Counter.ascx.cs

    public partial class Counter : System.Web.UI.UserControl
        protected void ClickMeButton_Click(object sender, EventArgs e)
            Console.WriteLine("The button was clicked!");
    

    在 Blazor 中,可以直接使用 @on{event} 形式的指令属性为 DOM UI 事件注册处理程序。 {event} 占位符表示事件的名称。 例如,可侦听按钮单击,如下所示:

    <button @onclick="OnClick">Click me!</button>
    @code {
        void OnClick()
            Console.WriteLine("The button was clicked!");
    

    事件处理程序可以接受可选的事件特定参数,以提供有关事件的详细信息。 例如,鼠标事件可以接受 MouseEventArgs 参数,但这不是必需的。

    <button @onclick="OnClick">Click me!</button>
    @code {
        void OnClick(MouseEventArgs e)
            Console.WriteLine($"Mouse clicked at {e.ScreenX}, {e.ScreenY}.");
    

    可以使用 lambda 表达式来代替引用事件处理程序的方法组。 借助 lambda 表达式,可以覆盖其他范围内的值。

    @foreach (var buttonLabel in buttonLabels)
        <button @onclick="() => Console.WriteLine($"The {buttonLabel} button was clicked!")">@buttonLabel</button>
    

    事件处理程序可以同步或异步执行。 例如,以下 OnClick 事件处理程序异步执行:

    <button @onclick="OnClick">Click me!</button>
    @code {
        async Task OnClick()
            var result = await Http.GetAsync("api/values");
    

    处理事件后,将呈现组件以解释任何组件状态更改。 使用异步事件处理程序时,组件将在处理程序执行完成后立即呈现。 异步 Task 完成后,将再次呈现组件。 在异步 Task 仍在执行时,此异步执行模式提供呈现部分相应 UI 的机会。

    <button @onclick="ShowMessage">Get message</button>
    @if (showMessage)
        @if (message == null)
            <p><em>Loading...</em></p>
            <p>The message is: @message</p>
    @code
        bool showMessage = false;
        string message;
        public async Task ShowMessage()
            showMessage = true;
            message = await MessageService.GetMessageAsync();
    

    组件还可以通过定义类型为 EventCallback<TValue> 的组件参数来定义自己的事件。 事件回叫支持 DOM UI 事件处理程序的所有变体:可选参数、同步或异步、方法组或 lambda 表达式。

    <button class="btn btn-primary" @onclick="OnClick">Click me!</button>
    @code {
        [Parameter]
        public EventCallback<MouseEventArgs> OnClick { get; set; }
    

    Blazor 提供将 UI 组件中的数据绑定到组件状态的简单机制。 此方法与 ASP.NET Web Forms 中用于将数据源中的数据绑定到 UI 控件的功能不同。 我们将在处理数据部分中介绍如何处理来自不同数据源的数据。

    若要创建从 UI 组件到组件状态的双向数据绑定,请使用 @bind 指令属性。 在下面的示例中,复选框的值绑定到 isChecked 字段。

    <input type="checkbox" @bind="isChecked" />
    @code {
        bool isChecked;
    

    呈现组件时,复选框的值将设置为 isChecked 字段的值。 当用户切换复选框时,将触发 onchange 事件,且 isChecked 字段将设置为新值。 在这种情况下,@bind 语法等效于以下标记:

    <input value="@isChecked" @onchange="(UIChangeEventArgs e) => isChecked = e.Value" />
    

    若要更改用于绑定的事件,请使用 @bind:event 属性。

    <input @bind="text" @bind:event="oninput" />
    <p>@text</p>
    @code {
        string text;
    

    组件还可以支持将数据绑定到其参数。 若要进行数据绑定,请定义与可绑定参数同名的事件回叫参数。 名称会添加“Changed”后缀。

    PasswordBox.razor

    Password: <input
        value="@Password"
        @oninput="OnPasswordChanged"
        type="@(showPassword ? "text" : "password")" />
    <label><input type="checkbox" @bind="showPassword" />Show password</label>
    @code {
        private bool showPassword;
        [Parameter]
        public string Password { get; set; }
        [Parameter]
        public EventCallback<string> PasswordChanged { get; set; }
        private Task OnPasswordChanged(ChangeEventArgs e)
            Password = e.Value.ToString();
            return PasswordChanged.InvokeAsync(Password);
    

    若要将数据绑定链接到基础 UI 元素,请设置值并直接在 UI 元素上处理事件,而不要使用 @bind 属性。

    若要绑定到组件参数,请使用 @bind-{Parameter} 属性指定要绑定到的参数。

    <PasswordBox @bind-Password="password" />
    @code {
        string password;
    

    如果组件的状态已在常规 UI 事件或事件回叫之外更改,则组件必须手动发出需要再次呈现的信号。 若要发出组件状态已更改的信号,请在组件上调用 StateHasChanged 方法。

    在下面的示例中,组件显示来自 AppState 服务的消息(可由应用的其他部分更新)。 组件向 AppState.OnChange 事件注册其 StateHasChanged 方法,以便每次消息更新时呈现该组件。

    public class AppState
        public string Message { get; }
        // Lets components receive change notifications
        public event Action OnChange;
        public void UpdateMessage(string message)
            Message = message;
            NotifyStateChanged();
        private void NotifyStateChanged() => OnChange?.Invoke();
    
    @inject AppState AppState
    <p>App message: @AppState.Message</p>
    @code {
        protected override void OnInitialized()
            AppState.OnChange += StateHasChanged
    

    组件生命周期

    ASP.NET Web Forms 框架为模块、页和控件提供定义完善的生命周期方法。 例如,以下控件为 InitLoadUnLoad 生命周期事件实现事件处理程序:

    Counter.ascx.cs

    public partial class Counter : System.Web.UI.UserControl
        protected void Page_Init(object sender, EventArgs e) { ... }
        protected void Page_Load(object sender, EventArgs e) { ... }
        protected void Page_UnLoad(object sender, EventArgs e) { ... }
    

    Razor 组件也具有定义完善的生命周期。 组件的生命周期可用于初始化组件状态及实现高级组件行为。

    Blazor 的所有组件生命周期方法都具有同步和异步版本。 组件呈现是同步的。 不能在组件呈现期间运行异步逻辑。 所有异步逻辑都必须作为 async 生命周期方法的一部分执行。

    OnInitialized

    OnInitializedOnInitializedAsync 方法用于初始化组件。 组件通常在首次呈现后初始化。 组件初始化后,可能会在最终释放前呈现多次。 OnInitialized 方法类似于 ASP.NET Web Forms 页和控件中的 Page_Load 事件。

    protected override void OnInitialized() { ... }
    protected override async Task OnInitializedAsync() { await ... }
    

    OnParametersSet

    当组件已从其父级接收参数并将值分配给属性时,将调用 OnParametersSetOnParametersSetAsync 方法。 这些方法在组件初始化后以及每次呈现组件时执行。

    protected override void OnParametersSet() { ... }
    protected override async Task OnParametersSetAsync() { await ... }
    

    OnAfterRender

    OnAfterRenderOnAfterRenderAsync 方法在组件完成呈现后调用。 此时,将填充元素和组件引用(在下文中详细介绍这些概念)。 此时已启用与浏览器的交互性功能。 与 DOM 和 JavaScript 执行的交互可以安全地进行。

    protected override void OnAfterRender(bool firstRender)
        if (firstRender)
    protected override async Task OnAfterRenderAsync(bool firstRender)
        if (firstRender)
            await ...
    

    在服务器上进行预呈现时,不调用 OnAfterRenderOnAfterRenderAsync

    firstRender 参数在首次呈现组件时为 true;否则,其值为 false

    IDisposable

    从 UI 中移除 Razor 组件时,该组件可以实现 IDisposable 来释放资源。 Razor 组件可以通过使用 @implements 指令来实现 IDispose

    @using System
    @implements IDisposable
    @code {
        public void Dispose()
    

    捕获组件引用

    在 ASP.NET Web Forms 中,通常通过引用控件实例 ID 直接在代码中操作控件实例。 在 Blazor 中,也可以捕获和操作对组件的引用,尽管这种做法不太常见。

    若要在 Blazor 中捕获组件引用,请使用 @ref 指令属性。 属性的值应该匹配与引用的组件具有相同类型的可设置字段的名称。

    <MyLoginDialog @ref="loginDialog" ... />
    @code {
        MyLoginDialog loginDialog = default!;
        void OnSomething()
            loginDialog.Show();
    

    呈现父组件时,将使用子组件实例填充该字段。 然后,可以在组件实例上调用方法,或以其他方式操作组件实例。

    不建议直接使用组件引用来操作组件状态。 这样做会阻止组件在正确的时间自动呈现。

    捕获元素引用

    Razor 组件可以捕获对元素的引用。 与 ASP.NET Web Forms 中的 HTML 服务器控件不同,不能在 Blazor 中直接使用元素引用来操作 DOM。 Blazor 使用其 DOM 比较算法处理大部分 DOM 交互。 Blazor 中捕获的元素引用是不透明的。 但是,它们用于在 JavaScript 互操作调用中传递特定元素引用。 有关 JavaScript 互操作的详细信息,请参阅 ASP.NET Core Blazor JavaScript 互操作

    模板化组件

    在 ASP.NET Web Forms 中,可以创建模板化控件。 模板化控件使开发人员可以指定用于呈现容器控件的一部分 HTML。 生成模板化服务器控件的机制很复杂,但它们提供了功能强大的方案,可用于以用户可自定义的方式呈现数据。 模板化控件的示例包括 RepeaterDataList

    还可以通过定义类型为 RenderFragmentRenderFragment<T> 的组件参数来模板化 Razor 组件。 RenderFragment 表示可随后由组件呈现的 Razor 标记块。 RenderFragment<T> 是 Razor 标记块,其接受可在呈现片段时指定的参数。

    Razor 组件可以将其子内容作为 RenderFragment 进行捕获,并将该内容作为组件呈现的一部分呈现。 若要捕获子内容,请定义类型为 RenderFragment 的组件参数并将其命名为 ChildContent

    ChildContentComponent.razor

    <h1>Component with child content</h1>
    <div>@ChildContent</div>
    @code {
        [Parameter]
        public RenderFragment ChildContent { get; set; }
    

    然后,父组件可以使用常规 Razor 语法提供子内容。

    <ChildContentComponent>
        <ChildContent>
            <p>The time is @DateTime.Now</p>
        </ChildContent>
    </ChildContentComponent>
    

    模板化 Razor 组件还可以定义类型为 RenderFragmentRenderFragment<T> 的多个组件参数。 RenderFragment<T> 的参数可以在调用时指定。 若要为组件指定泛型类型参数,请使用 @typeparam Razor 指令。

    SimpleListView.razor

    @typeparam TItem
    @Heading
    @foreach (var item in Items)
        <li>@ItemTemplate(item)</li>
    @code {
        [Parameter]
        public RenderFragment Heading { get; set; }
        [Parameter]
        public RenderFragment<TItem> ItemTemplate { get; set; }
        [Parameter]
        public IEnumerable<TItem> Items { get; set; }
    

    在使用模板化组件时,可以使用与参数名称匹配的子元素来指定模板参数。 作为元素传递的类型为 RenderFragment<T> 的组件参数有一个名为 context 的隐式参数。 可以使用子元素上的 Context 属性来更改此实现参数的名称。 可以使用与类型参数名称匹配的属性来指定任何泛型类型参数。 如果可能,将推断类型参数:

    <SimpleListView Items="messages" TItem="string">
        <Heading>
            <h1>My list</h1>
        </Heading>
        <ItemTemplate Context="message">
            <p>The message is: @message</p>
        </ItemTemplate>
    </SimpleListView>
    

    此组件的输出如下所示:

    <h1>My list</h1>
        <li><p>The message is: message1</p></li>
        <li><p>The message is: message2</p></li>
    

    Razor 组件通常是在单个 .razor 文件中创作的。 但也可以使用代码隐藏文件来分隔代码和标记。 若要使用组件文件,请添加与组件文件的文件名匹配,但添加了 .cs 扩展名的 C# 文件 (Counter.razor.cs)。 使用 C# 文件定义组件的基类。 可以根据需要命名基类,但通常将基类命名为与组件类相同的名称,并添加 Base 扩展名 (CounterBase)。 基于组件的类也必须从 ComponentBase 中派生。 然后,在 Razor 组件文件中,添加 @inherits 指令以指定组件的基类 (@inherits CounterBase)。

    Counter.razor

    @inherits CounterBase
    <h1>Counter</h1>
    <p>Current count: @currentCount</p>
    <button @onclick="IncrementCount">Click me</button>
    

    Counter.razor.cs

    public class CounterBase : ComponentBase
        protected int currentCount = 0;
        protected void IncrementCount()
            currentCount++;
    

    组件成员在基类中的可见性必须为 protectedpublic 才能对组件类可见。

    前面的内容并不是对 Razor 组件的所有方面的详尽介绍。 有关如何创建和使用 ASP.NET Core Razor 组件的详细信息,请参阅 Blazor 文档。

    上一页下一页