创建和使用 ASP.NET Core Razor 组件Create and use ASP.NET Core Razor components

本文内容

作者:Luke LathamDaniel Roth

查看或下载示例代码如何下载

Blazor 应用是使用组件构建的 。组件是自包含的用户界面 (UI) 块,例如页、对话框或窗体。组件包含插入数据或响应 UI 事件所需的 HTML 标记和处理逻辑。组件非常灵活且轻量。可在项目之间嵌套、重复使用和共享。

组件类Component classes

组件是使用 C# 和 HTML 标记的组合在 Razor 组件文件 (.razor) 中实现的 。Blazor 中的组件正式称为 Razor 组件 。

组件的名称必须以大写字符开头。例如,MyCoolComponent.razor 有效,myCoolComponent.razor 无效 。

使用 HTML 定义组件的 UI。动态呈现逻辑(例如,循环、条件、表达式)是使用名为 Razor 的嵌入式 C# 语法添加的。在编译应用时,HTML 标记和 C# 呈现逻辑转换为组件类。生成的类的名称与文件名匹配。

组件类的成员在 @code 块中定义。@code 块中,通过用于处理事件或定义其他组件逻辑的方法指定组件状态(属性、字段)。允许多个 @code 块。

可以使用以 @ 开头的 C# 表达式将组件成员作为组件的呈现逻辑的一部分。例如,通过为字段名称添加 @ 前缀来呈现 C# 字段。下面的示例计算并呈现:

  • font-style 的 CSS 属性值的 _headingFontStyle
  • <h1> 元素内容的 _headingText
  1. <h1 style="font-style:@_headingFontStyle">@_headingText</h1>
  2. @code {
  3. private string _headingFontStyle = "italic";
  4. private string _headingText = "Put on your new Blazor!";
  5. }

最初呈现组件后,组件会为响应事件而重新生成其呈现树。然后 Blazor 将新呈现树与前一个呈现树进行比较,并对浏览器文档对象模型 (DOM) 应用任何修改。

组件是普通 C# 类,可以放置在项目中的任何位置。生成网页的组件通常位于 Pages 文件夹中 。非页面组件通常放置在 Shared 文件夹或添加到项目的自定义文件夹中 。

通常,组件的命名空间是从应用的根命名空间和该组件在应用内的位置(文件夹)派生而来的。如果应用的根命名空间是 BlazorApp,并且 Counter 组件位于 Pages 文件夹中 :

  • Counter 组件的命名空间为 BlazorApp.Pages
  • 组件的完全限定类型名称为 BlazorApp.Pages.Counter

有关详细信息,请参阅导入组件部分。

若要使用自定义文件夹,请将自定义文件夹的命名空间添加到父组件或应用的 _Imports.razor 文件中 。例如,当应用的根命名空间为 BlazorApp 时,以下命名空间使 Components 文件夹中的组件可用 :

  1. @using BlazorApp.Components

静态资产Static assets

Blazor 遵循 ASP.NET Core 应用的约定,将静态资产放在项目的 Web 根 (wwwroot) 文件夹下

使用基相对路径 (/) 来引用静态资产的 Web 根。在下面的示例中,logo.png 物理上位置位于 {PROJECT ROOT}/wwwroot/images 文件夹中 :

  1. <img alt="Company logo" src="/images/logo.png" />

Razor 组件不支持波浪符斜杠表示法 (~/) 。

有关设置应用基路径的信息,请参阅 托管和部署 ASP.NET Core Blazor

组件中不支持标记帮助程序Tag Helpers aren't supported in components

Razor 组件(.razor 文件)不支持标记帮助程序若要在 Blazor 中提供类似标记帮助程序的功能,请创建一个具有与标记帮助程序相同功能的组件,并改为使用该组件。

使用组件Use components

通过使用 HTML 元素语法声明组件,组件可以包含其他组件。使用组件的标记类似于 HTML 标记,其中标记的名称是组件类型。

Index.razor 中的以下标记呈现了 HeadingComponent 实例 :

  1. <HeadingComponent />

Components/HeadingComponent.razor :

  1. @using System.Globalization
  2. @*
  3. The 'using' directive makes System.Globalization available to
  4. the component. System.Globalization provides a method for
  5. converting a string into title case (capitalizes the first
  6. letter of every word in a string), which is used to convert a
  7. a string into title case for a heading.
  8. *@
  9. @*
  10. Heading text is rendered by evaluating the _headingText field.
  11. The font-style of the heading is rendered by evaluating the
  12. _headingFontStyle field.
  13. *@
  14. <h1 style="font-style:@_headingFontStyle">@_headingText</h1>
  15. <form>
  16. <div>
  17. @*
  18. A check box sets the font style and is bound to the
  19. _italicsCheck field.
  20. *@
  21. <input type="checkbox" id="italicsCheck"
  22. @bind="_italicsCheck" />
  23. <label class="form-check-label"
  24. for="italicsCheck">Use italics</label>
  25. </div>
  26. @*
  27. When the form is submitted, the onclick event executes
  28. the UpdateHeading method.
  29. *@
  30. <button type="button" class="btn btn-primary" @onclick="UpdateHeading">
  31. Update heading
  32. </button>
  33. </form>
  34. @code {
  35. private static TextInfo _tinfo = CultureInfo.CurrentCulture.TextInfo;
  36. private string _headingText =
  37. _tinfo.ToTitleCase("welcome to blazor!");
  38. private string _headingFontStyle = "normal";
  39. private bool _italicsCheck = false;
  40. // When UpdateHeading is executed, _italicsCheck determines
  41. // the value of _headingFontStyle to set the font style of the
  42. // heading.
  43. public void UpdateHeading()
  44. {
  45. _headingFontStyle = _italicsCheck ? "italic" : "normal";
  46. }
  47. }

如果某个组件包含一个 HTML 元素,该元素的大写首字母与组件名称不匹配,则会发出警告,指示该元素名称异常。为组件的命名空间添加 @using 指令使组件可用,这可解决此警告。

路由Routing

可以通过为应用中的每个可访问组件提供路由模板来实现 Blazor 中的路由。

编译具有 @page 指令的 Razor 文件时,将为生成的类提供 RouteAttribute 指定路由模板。在运行时,路由器将使用 RouteAttribute 查找组件类,并呈现具有与请求的 URL 匹配的路由模板的任何组件。

  1. @page "/ParentComponent"
  2. ...

有关详细信息,请参阅 ASP.NET Core Blazor 路由

参数Parameters

路由参数Route parameters

组件可以接收 @page 指令中提供的路由模板中的路由参数。路由器使用路由参数来填充相应的组件参数。

Pages/RouteParameter.razor :

  1. @page "/RouteParameter"
  2. @page "/RouteParameter/{text}"
  3. <h1>Blazor is @Text!</h1>
  4. @code {
  5. [Parameter]
  6. public string Text { get; set; }
  7. protected override void OnInitialized()
  8. {
  9. Text = Text ?? "fantastic";
  10. }
  11. }

不支持可选参数,因此在前面的示例中应用了两个 @page 指令。第一个指令允许导航到没有参数的组件。第二个 @page 指令接收 {text} 路由参数,并将值分配给 Text 属性。

Razor 组件 (.razor) 不支持 Catch-all 参数语法 (/*),该语法捕获跨多个文件夹边界的路径 。

组件参数Component parameters

组件可以具有组件参数,这些参数由具有 [Parameter] 属性的组件类上的公共属性定义 。使用这些属性在标记中为组件指定参数。

Components/ChildComponent.razor :

  1. <div class="panel panel-default">
  2. <div class="panel-heading">@Title</div>
  3. <div class="panel-body">@ChildContent</div>
  4. <button class="btn btn-primary" @onclick="OnClickCallback">
  5. Trigger a Parent component method
  6. </button>
  7. </div>
  8. @code {
  9. [Parameter]
  10. public string Title { get; set; }
  11. [Parameter]
  12. public RenderFragment ChildContent { get; set; }
  13. [Parameter]
  14. public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
  15. }

在示例应用的以下示例中,ParentComponent 设置 ChildComponentTitle 属性的值。

Pages/ParentComponent.razor :

  1. @page "/ParentComponent"
  2. <h1>Parent-child example</h1>
  3. <ChildComponent Title="Panel Title from Parent"
  4. OnClickCallback="@ShowMessage">
  5. Content of the child component is supplied
  6. by the parent component.
  7. </ChildComponent>

子内容Child content

组件可以设置另一个组件的内容。分配组件提供指定接收组件的标记之间的内容。

在下面的示例中,ChildComponent 具有一个表示 RenderFragment(表示要呈现的 UI 段)的 ChildContent 属性。ChildContent 的值放置在应呈现内容的组件标记中。ChildContent 的值是从父组件接收的,并呈现在启动面板的 panel-body 中。

Components/ChildComponent.razor :

  1. <div class="panel panel-default">
  2. <div class="panel-heading">@Title</div>
  3. <div class="panel-body">@ChildContent</div>
  4. <button class="btn btn-primary" @onclick="OnClickCallback">
  5. Trigger a Parent component method
  6. </button>
  7. </div>
  8. @code {
  9. [Parameter]
  10. public string Title { get; set; }
  11. [Parameter]
  12. public RenderFragment ChildContent { get; set; }
  13. [Parameter]
  14. public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
  15. }

备注

必须按约定将接收 RenderFragment 内容的属性命名为 ChildContent

示例应用中的 ParentComponent 可以通过将内容置于 <ChildComponent> 标记中,提供用于呈现 ChildComponent 的内容。

Pages/ParentComponent.razor :

  1. @page "/ParentComponent"
  2. <h1>Parent-child example</h1>
  3. <ChildComponent Title="Panel Title from Parent"
  4. OnClickCallback="@ShowMessage">
  5. Content of the child component is supplied
  6. by the parent component.
  7. </ChildComponent>

属性展开和任意参数Attribute splatting and arbitrary parameters

除了组件的声明参数外,组件还可以捕获和呈现其他属性。其他属性可以在字典中捕获,然后在使用 @attributes Razor 指令呈现组件时,将其展开到元素上 。此方案在定义生成支持各种自定义项的标记元素的组件时非常有用。例如,为支持多个参数的 <input> 单独定义属性可能比较繁琐。

在下面的示例中,第一个 <input> 元素 (id="useIndividualParams") 使用单个组件参数,第二个 <input> 元素 (id="useAttributesDict") 使用属性展开:

  1. <input id="useIndividualParams"
  2. maxlength="@Maxlength"
  3. placeholder="@Placeholder"
  4. required="@Required"
  5. size="@Size" />
  6. <input id="useAttributesDict"
  7. @attributes="InputAttributes" />
  8. @code {
  9. [Parameter]
  10. public string Maxlength { get; set; } = "10";
  11. [Parameter]
  12. public string Placeholder { get; set; } = "Input placeholder text";
  13. [Parameter]
  14. public string Required { get; set; } = "required";
  15. [Parameter]
  16. public string Size { get; set; } = "50";
  17. [Parameter]
  18. public Dictionary<string, object> InputAttributes { get; set; } =
  19. new Dictionary<string, object>()
  20. {
  21. { "maxlength", "10" },
  22. { "placeholder", "Input placeholder text" },
  23. { "required", "required" },
  24. { "size", "50" }
  25. };
  26. }

参数的类型必须使用字符串键实现 IEnumerable<KeyValuePair<string, object>>在此方案中,也可以选择使用 IReadOnlyDictionary<string, object>

使用这两种方法呈现的 <input> 元素相同:

  1. <input id="useIndividualParams"
  2. maxlength="10"
  3. placeholder="Input placeholder text"
  4. required="required"
  5. size="50">
  6. <input id="useAttributesDict"
  7. maxlength="10"
  8. placeholder="Input placeholder text"
  9. required="required"
  10. size="50">

若要接受任意属性,请使用 [Parameter] 特性定义组件参数,并将 CaptureUnmatchedValues 属性设置为 true

  1. @code {
  2. [Parameter(CaptureUnmatchedValues = true)]
  3. public Dictionary<string, object> InputAttributes { get; set; }
  4. }

[Parameter] 上的 CaptureUnmatchedValues 属性允许参数匹配所有不匹配任何其他参数的特性。组件只能使用 CaptureUnmatchedValues 定义单个参数。CaptureUnmatchedValues 一起使用的属性类型必须可以使用字符串键从 Dictionary<string, object> 中分配。IEnumerable<KeyValuePair<string, object>>IReadOnlyDictionary<string, object> 也是此方案中的选项。

相对于元素特性位置的 @attributes 位置很重要。在元素上展开 @attributes 时,将从右到左(从最后一个到第一个)处理特性。请考虑以下使用 Child 组件的组件示例:

ParentComponent.razor :

  1. <ChildComponent extra="10" />

ChildComponent.razor :

  1. <div @attributes="AdditionalAttributes" extra="5" />
  2. [Parameter(CaptureUnmatchedValues = true)]
  3. public IDictionary<string, object> AdditionalAttributes { get; set; }

Child 组件的 extra 属性设置为 @attributes 右侧。通过附加特性传递时,Parent 组件的呈现的 <div> 包含 extra="5",因为特性是从右到左(从最后一个到第一个)处理的:

  1. <div extra="5" />

在下面的示例中,extra@attributes 的顺序在 Child 组件的 <div> 中反转:

ParentComponent.razor :

  1. <ChildComponent extra="10" />

ChildComponent.razor :

  1. <div extra="5" @attributes="AdditionalAttributes" />
  2. [Parameter(CaptureUnmatchedValues = true)]
  3. public IDictionary<string, object> AdditionalAttributes { get; set; }

通过附加属性传递时,Parent 组件中呈现的 <div> 包含 extra="10"

  1. <div extra="10" />

捕获对组件的引用Capture references to components

组件引用提供了一种引用组件实例的方法,以便可以向该实例发出命令,例如 ShowReset若要捕获组件引用,请执行以下操作:

  • 向子组件添加 @ref 特性。
  • 定义与子组件类型相同的字段。
  1. <MyLoginDialog @ref="_loginDialog" ... />
  2. @code {
  3. private MyLoginDialog _loginDialog;
  4. private void OnSomething()
  5. {
  6. _loginDialog.Show();
  7. }
  8. }

呈现组件时,将用 MyLoginDialog 子组件实例填充 _loginDialog 字段。然后,可以在组件实例上调用 .NET 方法。

重要

仅在呈现组件后填充 _loginDialog 变量,其输出包含 MyLoginDialog 元素。在这之前,没有任何内容可引用。若要在组件完成呈现后操作组件引用,请使用 OnAfterRenderAsync 或 OnAfterRender 方法

尽管捕获组件引用使用与捕获元素引用类似的语法,但它不是 JavaScript 互操作功能。组件引用不会传递给 JavaScript 代码—它们只在 .NET 代码中使用。

备注

不要使用组件引用来改变子组件的状态 。请改用常规声明性参数将数据传递给子组件。使用常规声明性参数使子组件在正确的时间自动重新呈现。

在外部调用组件方法以更新状态Invoke component methods externally to update state

Blazor 使用 SynchronizationContext 来强制执行单个逻辑线程。组件的生命周期方法和 Blazor 引发的任何事件回调都在此 SynchronizationContext 上执行。如果组件必须根据外部事件(如计时器或其他通知)进行更新,请使用 InvokeAsync 方法,该方法将调度到 Blazor 的 SynchronizationContext

例如,假设有一个可通知任何侦听组件更新状态的通告程序服务 :

  1. public class NotifierService
  2. {
  3. // Can be called from anywhere
  4. public async Task Update(string key, int value)
  5. {
  6. if (Notify != null)
  7. {
  8. await Notify.Invoke(key, value);
  9. }
  10. }
  11. public event Func<string, int, Task> Notify;
  12. }

NotifierService 注册为单一实例:

  • 在 Blazor WebAssembly 中,在 Program.Main 中注册服务:
  1. builder.Services.AddSingleton<NotifierService>();
  • 在 Blazor 服务器中,在 Startup.ConfigureServices 中注册服务:
  1. services.AddSingleton<NotifierService>();

使用 NotifierService 更新组件:

  1. @page "/"
  2. @inject NotifierService Notifier
  3. @implements IDisposable
  4. <p>Last update: @_lastNotification.key = @_lastNotification.value</p>
  5. @code {
  6. private (string key, int value) _lastNotification;
  7. protected override void OnInitialized()
  8. {
  9. Notifier.Notify += OnNotify;
  10. }
  11. public async Task OnNotify(string key, int value)
  12. {
  13. await InvokeAsync(() =>
  14. {
  15. _lastNotification = (key, value);
  16. StateHasChanged();
  17. });
  18. }
  19. public void Dispose()
  20. {
  21. Notifier.Notify -= OnNotify;
  22. }
  23. }

在前面的示例中,NotifierService 在 Blazor 的 SynchronizationContext 之外调用组件的 OnNotify 方法。InvokeAsync 用于切换到正确的上下文,并将呈现排入队列。

使用 @ 键控制是否保留元素和组件Use @key to control the preservation of elements and components

在呈现元素或组件的列表并且元素或组件随后发生更改时,Blazor 的比较算法必须决定之前的元素或组件中有哪些可以保留,以及模型对象应如何映射到这些元素或组件。通常,此过程是自动的,可以忽略,但在某些情况下,可能需要控制该过程。

请看下面的示例:

  1. @foreach (var person in People)
  2. {
  3. <DetailsEditor Details="@person.Details" />
  4. }
  5. @code {
  6. [Parameter]
  7. public IEnumerable<Person> People { get; set; }
  8. }

People 集合的内容可能会随插入、删除或重新排序的条目而更改。当组件重新呈现时,<DetailsEditor> 组件可能会更改以接收不同的 Details 参数值。这可能导致重新呈现比预期更复杂。在某些情况下,重新呈现可能会导致可见行为差异,如失去元素焦点。

可以通过 @key 指令属性来控制映射过程。@key 使比较算法保证基于键的值保留元素或组件:

  1. @foreach (var person in People)
  2. {
  3. <DetailsEditor @key="person" Details="@person.Details" />
  4. }
  5. @code {
  6. [Parameter]
  7. public IEnumerable<Person> People { get; set; }
  8. }

People 集合发生更改时,比较算法会保留 <DetailsEditor> 实例与 person 实例之间的关联:

  • 如果从 People 列表中删除 Person,则仅从 UI 中删除相应的 <DetailsEditor> 实例。其他实例保持不变。
  • 如果在列表中的某个位置插入 Person,则会在相应位置插入一个新的 <DetailsEditor> 实例。其他实例保持不变。
  • 如果对 Person 项进行重新排序,则保留相应的 <DetailsEditor> 实例,并在 UI 中重新排序。

在某些场景中,使用 @key 可最大程度降低重新呈现的复杂性,并避免在 DOM 的有状态部分发生变化时出现潜在问题,如焦点位置。

重要

对于每个容器元素或组件而言,键是本地的。不会在整个文档中全局地比较键。

何时使用 @keyWhen to use @key

通常,每当呈现列表(例如,在 @foreach 块中)以及存在适当的值定义 @key 时,都可以使用 @key

还可以使用 @key 来防止 Blazor 在对象发生更改时保留元素或组件子树:

  1. <div @key="currentPerson">
  2. ... content that depends on currentPerson ...
  3. </div>

如果 @currentPerson 更改,则 @key 属性指令强制 Blazor 放弃整个 <div> 及其子代,并利用新的元素和组件在 UI 中重新生成子树。如果需要确保在 @currentPerson 更改时不保留 UI 状态,这会很有用。

何时不使用 @keyWhen not to use @key

@key 进行比较时,会产生性能费用。性能费用不是很大,但仅在控制元素或组件保留规则对应用有利的情况下才指定 @key

即使未使用 @key,Blazor 也会尽可能地保留子元素和组件实例。使用 @key 的唯一优点是控制如何将模型实例映射到保留的组件实例,而不是选择映射的比较算法 。

@key 使用哪些值What values to use for @key

通常,为 @key 提供以下类型之一的值:

  • 模型对象实例(例如,先前示例中的 Person 实例)。这可确保基于对象引用相等性的保留。
  • 唯一标识符(例如,intstringGuid 类型的主键值)。

确保用于 @key 的值不冲突。如果在同一父元素内检测到冲突值,则 Blazor 引发异常,因为它无法明确地将旧元素或组件映射到新元素或组件。仅使用非重复值,例如对象实例或主键值。

分部类支持Partial class support

Razor 组件作为分部类生成。使用以下方法之一创建 Razor 组件:

  • @code 块中使用单个文件中的 HTML 标记和 Razor 代码定义 C# 代码。 Blazor 模板使用此方法来定义其 Razor 组件。
  • C# 代码位于定义为分部类的代码隐藏文件中。

下面的示例显示了从 Blazor 模板生成的应用中具有 @code 块的默认 Counter 组件。HTML 标记、Razor 代码和 C# 代码位于同一个文件中:

Counter.razor :

  1. @page "/counter"
  2. <h1>Counter</h1>
  3. <p>Current count: @_currentCount</p>
  4. <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
  5. @code {
  6. private int _currentCount = 0;
  7. void IncrementCount()
  8. {
  9. _currentCount++;
  10. }
  11. }

还可以使用具有分部类的代码隐藏文件创建 Counter 组件:

Counter.razor :

  1. @page "/counter"
  2. <h1>Counter</h1>
  3. <p>Current count: @_currentCount</p>
  4. <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

Counter.razor.cs :

  1. namespace BlazorApp.Pages
  2. {
  3. public partial class Counter
  4. {
  5. private int _currentCount = 0;
  6. void IncrementCount()
  7. {
  8. _currentCount++;
  9. }
  10. }
  11. }

根据需要将任何所需的命名空间添加到分部类文件中。Razor 组件使用的典型命名空间包括:

  1. using Microsoft.AspNetCore.Authorization;
  2. using Microsoft.AspNetCore.Components;
  3. using Microsoft.AspNetCore.Components.Authorization;
  4. using Microsoft.AspNetCore.Components.Forms;
  5. using Microsoft.AspNetCore.Components.Routing;
  6. using Microsoft.AspNetCore.Components.Web;

指定基类Specify a base class

@inherits 指令可用于指定组件的基类。下面的示例演示组件如何继承基类 BlazorRocksBase 以提供组件的属性和方法。基类应派生自 ComponentBase

Pages/BlazorRocks.razor :

  1. @page "/BlazorRocks"
  2. @inherits BlazorRocksBase
  3. <h1>@BlazorRocksText</h1>

BlazorRocksBase.cs :

  1. using Microsoft.AspNetCore.Components;
  2. namespace BlazorSample
  3. {
  4. public class BlazorRocksBase : ComponentBase
  5. {
  6. public string BlazorRocksText { get; set; } =
  7. "Blazor rocks the browser!";
  8. }
  9. }

指定属性Specify an attribute

可以通过 @attribute 指令在 Razor 组件中指定属性。下面的示例将 [Authorize] 属性应用于组件类:

  1. @page "/"
  2. @attribute [Authorize]

导入组件Import components

使用 Razor 创建的组件的命名空间基于(按优先级顺序):

  • Razor 文件 (.razor) 标记 (@namespace BlazorSample.MyNamespace) 中的 @namespace 指定内容 。
  • 项目文件 (<RootNamespace>BlazorSample</RootNamespace>) 中项目的 RootNamespace
  • 项目名称,取自项目文件的文件名 (.csproj),以及从项目根到组件的路径 。例如,框架将 {PROJECT ROOT}/Pages/Index.razor (BlazorSample.csproj) 解析为命名空间 BlazorSample.Pages 。组件遵循 C# 名称绑定规则。对于本示例中的 Index 组件,范围内的组件是所有组件:
    • 在同一文件夹 Pages 中 。
    • 未显式指定其他命名空间的项目根中的组件。

使用 Razor 的 @using 指令将不同命名空间中定义的组件引入范围中。

如果 BlazorSample/Shared/ 文件夹中存在另一个组件 NavMenu.razor,则可以通过以下 @using 语句在 Index.razor 中使用该组件 :

  1. @using BlazorSample.Shared
  2. This is the Index page.
  3. <NavMenu></NavMenu>

还可以使用其完全限定的名称来引用组件,而不需要 @using 指令:

  1. This is the Index page.
  2. <BlazorSample.Shared.NavMenu></BlazorSample.Shared.NavMenu>

备注

不支持 global:: 限定。

不支持导入具有别名 using 语句的组件(例如,@using Foo = Bar)。

不支持部分限定名称。例如,不支持使用 <Shared.NavMenu></Shared.NavMenu> 添加 @using BlazorSample 和引用 NavMenu.razor

条件 HTML 元素属性Conditional HTML element attributes

HTML 元素属性基于 .NET 值有条件地呈现。如果值为 falsenull,则不会呈现属性。如果值为 true,则以最小化呈现属性。

在下面的示例中,IsCompleted 确定是否在元素的标记中呈现 checked

  1. <input type="checkbox" checked="@IsCompleted" />
  2. @code {
  3. [Parameter]
  4. public bool IsCompleted { get; set; }
  5. }

如果 IsCompletedtrue,则复选框呈现为:

  1. <input type="checkbox" checked />

如果 IsCompletedfalse,则复选框呈现为:

  1. <input type="checkbox" />

有关详细信息,请参阅 ASP.NET Core 的 Razor 语法参考

警告

.NET 类型为 bool 时,某些 HTML 属性(如 aria-pressed)无法正常运行。在这些情况下,请使用 string 类型,而不是 bool

原始 HTMLRaw HTML

通常使用 DOM 文本节点呈现字符串,这意味着将忽略它们可能包含的任何标记,并将其视为文字文本。若要呈现原始 HTML,请将 HTML 内容包装在 MarkupString 值中。将该值分析为 HTML 或 SVG,并插入到 DOM 中。

警告

呈现从任何不受信任的源构造的原始 HTML 存在安全风险,应避免 !

下面的示例演示如何使用 MarkupString 类型向组件的呈现输出添加静态 HTML 内容块:

  1. @((MarkupString)_myMarkup)
  2. @code {
  3. private string _myMarkup =
  4. "<p class='markup'>This is a <em>markup string</em>.</p>";
  5. }

级联值和参数Cascading values and parameters

在某些情况下,使用组件参数将数据从祖先组件流向子代组件不太方便,尤其是在有多个组件层时。级联值和参数提供了一种方便的方法,使祖先组件为其所有子代组件提供值,从而解决了此问题。级联值和参数还提供了一种协调组件的方法。

主题示例Theme example

在示例应用的以下示例中,ThemeInfo 类指定了要沿组件层次结构向下传递的主题信息,以便应用中给定部分内的所有按钮共享相同样式。

UIThemeClasses/ThemeInfo.cs :

  1. public class ThemeInfo
  2. {
  3. public string ButtonClass { get; set; }
  4. }

祖先组件可以使用级联值组件提供级联值。CascadingValue 组件包装组件层次结构的子树,并向该子树内的所有组件提供单个值。

例如,示例应用将应用布局之一中的主题信息 (ThemeInfo) 指定为组成 @Body 属性布局正文的所有组件的级联参数。在布局组件中为 ButtonClass 分配了 btn-success 的值。任何子代组件都可以通过 ThemeInfo 级联对象使用此属性。

CascadingValuesParametersLayout 组件:

  1. @inherits LayoutComponentBase
  2. @using BlazorSample.UIThemeClasses
  3. <div class="container-fluid">
  4. <div class="row">
  5. <div class="col-sm-3">
  6. <NavMenu />
  7. </div>
  8. <div class="col-sm-9">
  9. <CascadingValue Value="_theme">
  10. <div class="content px-4">
  11. @Body
  12. </div>
  13. </CascadingValue>
  14. </div>
  15. </div>
  16. </div>
  17. @code {
  18. private ThemeInfo _theme = new ThemeInfo { ButtonClass = "btn-success" };
  19. }

为了使用级联值,组件使用 [CascadingParameter] 属性声明级联参数。级联值按类型绑定到级联参数。

在示例应用中,CascadingValuesParametersTheme 组件将 ThemeInfo 级联值绑定到级联参数。该参数用于为组件显示的按钮之一设置 CSS 类。

CascadingValuesParametersTheme 组件:

  1. @page "/cascadingvaluesparameterstheme"
  2. @layout CascadingValuesParametersLayout
  3. @using BlazorSample.UIThemeClasses
  4. <h1>Cascading Values & Parameters</h1>
  5. <p>Current count: @_currentCount</p>
  6. <p>
  7. <button class="btn" @onclick="IncrementCount">
  8. Increment Counter (Unthemed)
  9. </button>
  10. </p>
  11. <p>
  12. <button class="btn @ThemeInfo.ButtonClass" @onclick="IncrementCount">
  13. Increment Counter (Themed)
  14. </button>
  15. </p>
  16. @code {
  17. private int _currentCount = 0;
  18. [CascadingParameter]
  19. protected ThemeInfo ThemeInfo { get; set; }
  20. private void IncrementCount()
  21. {
  22. _currentCount++;
  23. }
  24. }

若要在同一子树内级联多个相同类型的值,请为每个 CascadingValue 组件及其相应的 CascadingParameter 提供唯一的 Name 字符串。在下面的示例中,两个 CascadingValue 组件按名称级联 MyCascadingType 的不同实例:

  1. <CascadingValue Value=@_parentCascadeParameter1 Name="CascadeParam1">
  2. <CascadingValue Value=@ParentCascadeParameter2 Name="CascadeParam2">
  3. ...
  4. </CascadingValue>
  5. </CascadingValue>
  6. @code {
  7. private MyCascadingType _parentCascadeParameter1;
  8. [Parameter]
  9. public MyCascadingType ParentCascadeParameter2 { get; set; }
  10. ...
  11. }

在子代组件中,级联参数按名称从祖先组件中相应的级联值接收值:

...

@code {
    [CascadingParameter(Name = "CascadeParam1")]
    protected MyCascadingType ChildCascadeParameter1 { get; set; }

    [CascadingParameter(Name = "CascadeParam2")]
    protected MyCascadingType ChildCascadeParameter2 { get; set; }
}

TabSet 示例TabSet example

级联参数还允许组件跨组件层次结构进行协作。例如,请考虑示例应用中的以下 TabSet 示例 。

该示例应用包含一个选项卡实现的 ITab 接口:

using Microsoft.AspNetCore.Components;

namespace BlazorSample.UIInterfaces
{
    public interface ITab
    {
        RenderFragment ChildContent { get; }
    }
}

CascadingValuesParametersTabSet 组件使用 TabSet 组件,其中包含多个 Tab 组件:

<TabSet>
    <Tab Title="First tab">
        <h4>Greetings from the first tab!</h4>

        <label>
            <input type="checkbox" @bind="showThirdTab" />
            Toggle third tab
        </label>
    </Tab>
    <Tab Title="Second tab">
        <h4>The second tab says Hello World!</h4>
    </Tab>

    @if (showThirdTab)
    {
        <Tab Title="Third tab">
            <h4>Welcome to the disappearing third tab!</h4>
            <p>Toggle this tab from the first tab.</p>
        </Tab>
    }
</TabSet>

Tab 组件不会作为参数显式传递给 TabSetTab 组件是 TabSet 的子内容的一部分。TabSet 仍需要了解每个 Tab 组件,以便它可以呈现标头和活动选项卡。若要在不需要额外代码的情况下启用此协调,TabSet 组件可以将自身作为级联值提供,然后由子代 Tab 组件选取 。

TabSet 组件:

@using BlazorSample.UIInterfaces

<!-- Display the tab headers -->
<CascadingValue Value=this>
    <ul class="nav nav-tabs">
        @ChildContent
    </ul>
</CascadingValue>

<!-- Display body for only the active tab -->
<div class="nav-tabs-body p-4">
    @ActiveTab?.ChildContent
</div>

@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    public ITab ActiveTab { get; private set; }

    public void AddTab(ITab tab)
    {
        if (ActiveTab == null)
        {
            SetActivateTab(tab);
        }
    }

    public void RemoveTab(ITab tab)
    {
        if (ActiveTab == tab)
        {
            SetActivateTab(null);
        }
    }

    public void SetActivateTab(ITab tab)
    {
        if (ActiveTab != tab)
        {
            ActiveTab = tab;
            StateHasChanged();
        }
    }
}

子代 Tab 组件将包含的 TabSet 作为级联参数捕获,因此 Tab 组件会将其自身添加到 TabSet 并在处于活动状态的选项卡上进行协调。

Tab 组件:

@using BlazorSample.UIInterfaces
@implements ITab

<li>
    <a @onclick="Activate" class="nav-link @TitleCssClass" role="button">
        @Title
    </a>
</li>

@code {
    [CascadingParameter]
    public TabSet ContainerTabSet { get; set; }

    [Parameter]
    public string Title { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private string TitleCssClass => ContainerTabSet.ActiveTab == this ? "active" : null;

    protected override void OnInitialized()
    {
        ContainerTabSet.AddTab(this);
    }

    private void Activate()
    {
        ContainerTabSet.SetActivateTab(this);
    }
}

Razor 模板Razor templates

可以使用 Razor 模板语法来定义呈现片段。Razor 模板是一种定义 UI 代码片段的方法,请使用以下格式:

@<{HTML tag}>...</{HTML tag}>

下面的示例演示如何在组件中指定 RenderFragmentRenderFragment<T> 值并直接呈现模板。还可以将呈现片段作为参数传递给模板化组件

@_timeTemplate

@_petTemplate(new Pet { Name = "Rex" })

@code {
    private RenderFragment _timeTemplate = @<p>The time is @DateTime.Now.</p>;
    private RenderFragment<Pet> _petTemplate = (pet) => @<p>Pet: @pet.Name</p>;

    private class Pet
    {
        public string Name { get; set; }
    }
}

以上代码的呈现输出:

<p>The time is 10/04/2018 01:26:52.</p>

<p>Pet: Rex</p>

可缩放的向量图形 (SVG) 图像Scalable Vector Graphics (SVG) images

由于 Blazor 呈现 HTML,因此通过 <img> 标记支持浏览器支持的图像,包括可缩放的矢量图形 (SVG) 图像(.svg) :

<img alt="Example image" src="some-image.svg" />

同样,样式表文件 (.css) 的 CSS 规则支持 SVG 图像 :

.my-element {
    background-image: url("some-image.svg");
}

但是,并非在所有情况下都支持内联的 SVG 标记。如果将 <svg> 标记直接放入组件文件 (.razor),则支持基本图像呈现,但很多高级场景尚不受支持 。例如,当前未遵循 <use> 标记,并且 @bind 不能与某些 SVG 标记一起使用。我们预计会在将来的版本中解决这些限制。

其他资源Additional resources