ASP.NET Core Blazor 4:高级Blazor特性

  本章解释 Blazor 如何支持 URL 路由,以便通过一个请求显示多个组件。展示如何设置路由系统、如何定义路由以及如何在布局中创建公共内容。
  本章还介绍了组件的生命周期,它允许组件积极地参与 Blazor 环境,这在开始使用 URL 路由特性时尤为重要。最后,本章解释了组件在前面章节描述的父!子关系之外的不同交互方式。
  路由特性允许组件响应 URL 中的更改,而不需要新的 HTTP 连接。生命周期特性允许组件定义在执行应用程序时调用的方法,交互特性提供了在组件之间以及与其他JavaScript 代码进行通信的有用方法。

1 准备工作

  继续使用上一章项目。

2 使用组件的路由

  Blazor 支持根据 ASPNET Core路由系统选择要显示给用户的组件,使应用程序通过显示不同的 Razor 组件来响应 URL中的变化。首先,给 Blazor 文件夹添加一个名为 Routed.razor 的 Razor 组件,内容如代码清单所示。

<Router AppAssembly="typeof(Startup).Assembly">
    <Found>
        <RouteView RouteData="@context" />
    </Found>
    <NotFound>
        <h4 class="bg-danger text-white text-center p-2">
            Not Matching Route Found
        </h4>
    </NotFound>
</Router>

  Router 组件包含在 ASP.NET Core 中,提供了 Blazor 和 ASP.NET Core 路由特性之间的链接 Router 是一个通用模板组件,它定义了 Found 和 NotFound 部分。
  Router 组件需要 AppAssembly 属性,该属性指定要使用的.NET 程序集。对于大多数项目这是当前的程序集,它的指定如:AppAssembly="typeof(Startup).Assembly"。Router 组件的 Found 属性类型是 RenderFragment<RouteData>,它通过 RouteData 属性传递给RouteView 组件,如下所示:<RouteView RouteData="@context" />。RouteView 组件负责显示与当前路由匹配的组件,以及通过布局显示公共内容。NotFound 属性的类型是 RenderFragment,没有泛型类型参数,在当前路由找不到组件时显示内容部分。

2.1 准备 Blazor 页

  单个组件可以显示在现有的控制器视图和 Blazor 页面中。但是在使用组件路由时,最好创建一组与 Blazor 不同的 URL,因为 URL 被支持的方式是有限的,并且会导致复杂的解决方案。将一个名为 _Host.cshtml 的 Razor Pages 添加到 Pages 文件夹,并添加如代码清单所示的内容。

@page "/"
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <link href="/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
    <base href="~/" />
</head>
<body>
    <div class="m-2">
        <component type="typeof(MyAdvanced.Blazor.Routed)" render-mode="Server" />
    </div>
    <script src="_framework/blazor.server.js"></script>
</body>
</html>

  修改配置,以当请求与现有 URL 路由不匹配时使用 _Host.cshtml 文件作为回退,如代码清单所示。

            app.UseEndpoints(endpoints =>
            {
                ......
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });

  MapFallbackToPage 方法将路由系统配置为使用 Host页面,作为处理不匹配请求的最后手段。

2.2 向组件添加路由

  组件声明应该使用 @page 指令显示的 URL。给 PeopleList 组件添加了@page 指令@page "/people"。指令意味着为 http://localhost:5000/people URL 显示 PeopleList 组件。组件可使用多个@page指令声明对多个路由的支持。将 @page 指令添加到 DepartmentLis 组件以支持两个 URL @page "/departments"@page "/depts"
  要查看基本的 Razor 组件路由特性的工作情况,请求 http://localhost:5000/peoplehttp://localhost:5000/depts。每个 URL, 显示应用程序中的一个组件。
设置默认组件路由
  Starup 类中的请求设置了回退路由,应用程序需要默认 URL http:/localhost:5000 显示的组件,不然会报找不到路由。在 Blazor 文件夹的 PeopleListrazor 文件中定义默认路由 @page "/"就可以了。

2.3 在路由组件之间导航

  基本的路由配置已经就绪,但使用路由比独立组件的优势还不明显。下面使用 NavLink 组件改进,它呈现连接到路由系统的锚元素。代码清单将 NavLink 添加到 PeopleList 组件。

<NavLink class="btn btn-primary" href="/depts">Departments</NavLink>

  NavLink 组件是使用 URL 配置的,而不是组件、页面或动作名称。本例中的 NavLink 导航到 Departmentist 组件的 @page 指令所支持的 URL。
  导航也可以通过编程方式执行,这在组件响应事件然后需要导航到不同的URL时非常有用,如下在 Blazor 文件夹的 DepartmentList.razor 文件中以编程方式导航。

......
    
<button class="btn btn-primary" @onclick="HandleClick">People</button>

@code
{
    ......
        
    [Inject]
    public NavigationManager NavManager { get; set; }
    public void HandleClick() => NavManager.NavigateTo("/people");
}

  NavigationManager 类提供对导航的编程访问。以下描述了 NavigationManager 类提供的最重要成员。

名称 描述
NavigateTo(url) 此方法导航到指定的 URL,而不发送新的 HTTP 请求
ToAbsoluteUri(path) 此方法将一个相对路径转换为一个完整的 URL
ToBaseRelativePath(url) 此方法从完整的 URL 中获取相对路径
LocationChanged 位置更改时触发此事件
Uri 此属性返回当前 URL

  NavieadionMangst 类作为服务提供,并通过 Inject 属性被 Razor 组件接收,NavManager.NavigateTo 方法导航到一个 URL,该 URL 由 PeopleList 组件处理。
  请求 http://localhost:5000/people。单击 Departments 链接,该链接样式化为一个按钮。Departmenlist 组件将息示出来,单击 People 链接,将返回到 PeopleList 组件。

2.4 接收路由数据

  组件可以通过使用 Parameter 属性修饰属性来接收段变量。为了便于演示,为 Blazor 文件添加一个名为 PersonDisplay.razor 的 Razor 组件。

@page "/person"
@page "/person/{id:long}"

<h5>Editor for Person: @Id</h5>
<NavLink class="btn btn-primary" href="/people">Return</NavLink>

@code
{
    [Parameter]
    public long Id { get; set; } = 0;
}

  使用 NavLink 组件为 PeopleList 组件显示的每个 Person 对象创建导航链接。

@page "/"
@page "/people"

<TableTemplate RowType="Person" RowData="People"
               Highlight="@(p=>p.Location.City)" SortDirection="@(p=>p.Surname)">
    <Header>
    <tr>
        <th>ID</th>
        <th>Name</th>
        <th>Dept</th>
        <th>Location</th>
        <td></td>
    </tr>
    </Header>
    <RowTemplate Context="p">
        <td>@p.PersonId</td>
        <td>@p.Surname, @p.Firstname</td>
        <td>@p.Department.Name</td>
        <td>@p.Location.City, @p.Location.State</td>
        <td>
            <NavLink class="btn btn-sm btn-info" href="@GetEditUrl(p.PersonId)">
                Edit
            </NavLink>
        </td>
    </RowTemplate>
</TableTemplate>

<NavLink class="btn btn-primary" href="/depts">Departments</NavLink>

@code
{
    [Inject]
    public DataContext Context { get; set; }

    public IEnumerable<Person> People => Context.People
            .Include(p => p.Department)
            .Include(p => p.Location);

    [Inject]
    public NavigationManager NavManager { get; set; }
    public void HandleClick() => NavManager.NavigateTo("/people");

    public string GetEditUrl(long id) => $"/person/{id}";
}

  Razor 组件不支持在属性值中混合静态内容和 Razor 表达式。相反,前面定义了 GetEditUrl 方法来为每个 Person 对象生成导航 URL,调用该方法来生成 NavLink href 属性的值。
  请求 http://localhost:5000/people,然后单击其中一个 Edit 按钮。浏览器导航到新的 URL 而不重新加载 HTML 文档,并显示 PersonDisplay 组件生成的占位符内容,显示了组件如何从路由系统中接收数据。

2.5 使用布局定义公共内容

  布局是为 Razor 组件提供通用内容的模板组件。要创建布局,给 Blazor 文件夹添加一个名为NavLayout.razor 的 Razor 组件。

@inherits LayoutComponentBase

<div class="container-fluid">
    <div class="row">
        <div class="col-3">
            @foreach (string key in NavLinks.Keys)
            {
                <NavLink class="btn btn-outline-primary btn-block"
                         href="@NavLinks[key]"
                         ActiveClass="btn-primary text-white"
                         Match="NavLinkMatch.Prefix">
                    @key
                </NavLink>
            }
        </div>
        <div class="col">
            @Body
        </div>
    </div>
</div>

@code
{
    public Dictionary<string, string> NavLinks = new Dictionary<string, string>
        {{"People","/people"}, {"Departments", "/depts"},{"Details", "/person"}};
}

  布局使用 @inherits 表达式指定 LayoutComponentBase 类作为从Razor 组件中生成的类的基类。LayoutComponentBase 类定义了一个名为 Body 的 RenderFragment 类,用于指定布局中显示的公共内容中的组件内容。
  在本例中,布局组件创建了一个网格布局,该布局为应用程序中的每个组件显示一组 NavLink 组件。NavLink 组件配置了两个新属性。

  • ActiveClass:此属性指定一个或多个 CSS 类,当前 URL与 href 属性值匹配时,由 NavLink 组件呈现的锚元素将添加到这些 CSS 类中
  • Match:此属性指定如何使用来自 NavLinkMatch 枚举的值将当前 URL 匹配到 href属性;这些值是Prefix(如果 href 匹配 URL 的开头,则认为是匹配的)和 All(要求整个 URL 相同)

  NavLink 组件配置为使用前缀匹配,并当有匹配时,添加它们呈现给引导程序 btn-primary 和 text-white 类的锚元素。
应用布局
  有三种方法可以应用布局。组件可以使用 @layout 表达式选择自己的布局。父组件可通过将子组件包装到内置的 LayoutView 组件中,来为其子组件使用布局。通过设置 RouteView 组件的 DefaultLayout 属性,可将布局应用于所有组件,如下在 Blazor 文件夹的 Routed.razor文件中应用布局。

<Found>
    <RouteView RouteData="@context" DefaultLayout="typeof(NavLayout)" />
</Found>

  请求 http://localhost:5000/people,该布局与 PeopleList 组件呈现的内容一起显示。布局左侧的导航按钮可用于在应用程序中导航。

3 理解组件生命周期方法

  Razor 组件具有定义良好的生命周期,用组件可以实现的方法来表示,以接收关键转换的通知。

名称 描述
OnInitialized()、OnInitializedAsync() 这些方法在组件第一次初始化时调用
OnParametersSet()、OnParametersSetAsync() 这些方法在应用通过 Parameter 属性修饰的属性值后调用
ShouldRender() 此方法在呈现组件的内容之前调用,以更新呈现给用户的内容。如果该方法返回false,则组件的内容将不呈现,更新将被抑制。此方法不会取消组件的初始呈现
OnAferRender(first)、OnAfterRenderAsync(first) 在呈现组件的内容之后调用此方法。当 Blazor 为组件执行初始渲染时,bool参数为 true

  使用 OnInitialized 或 OnParameterSet 方法都可用于设置组件的初始状态。上一节定义的布局没有处理默认的 URL,因为 NavLink 组件只匹配一个 URL。对于 DepartmentList 组件也存在同样的问题,可以使用 /departments 和 /depts 路径请求该组件。
  创建匹配多个 URL 的组件需要使用生命周期方法。要了解原因,请为 Blazor 文件夹添加一个名为 MultiNavLink.razor 的 Razor 组件。

<a class="@ComputedClass" @onclick="HandleClick" href="">
    @ChildContent
</a>

@code
{
    [Inject]
    public NavigationManager NavManager { get; set; }
    [Parameter]
    public IEnumerable<string> Href { get; set; }
    [Parameter]
    public string Class { get; set; }
    [Parameter]
    public string ActiveClass { get; set; }
    [Parameter]
    public NavLinkMatch? Match { get; set; }

    public NavLinkMatch ComputedMatch
    {
        get => Match ??
        (Href.Count() == 1 ? NavLinkMatch.Prefix : NavLinkMatch.All);
    }
    
    [Parameter]
    public RenderFragment ChildContent { get; set; }
    public string ComputedClass { get; set; }
    
    public void HandleClick()
    {
        NavManager.NavigateTo(Href.First());
    }

    private void CheckMatch(string currentUrl)
    {
        string path = NavManager.ToBaseRelativePath(currentUrl);
        path = path.EndsWith("/") ? path.Substring(0, path.Length - 1) : path;
        bool match = Href.Any(href => ComputedMatch == NavLinkMatch.All
                ? path == href : path.StartsWith(href));
        ComputedClass = match ? $"{Class} {ActiveClass}" : Class;
    }

    protected override void OnParametersSet()
    {
        ComputedClass = Class;
        NavManager.LocationChanged += (sender, arg) => CheckMatch(arg.Location);
        Href = Href.Select(h => h.StartsWith("/") ? h.Substring(1) : h);
        CheckMatch(NavManager.Uri);
    }
}

  该组件的工作方式与常规 NavLink 相同,但接收一组路径来匹配。组件依赖于 OnParametersSet 生命周期方法,因为需要进行一些初始设置,而只有在为用 Parameter 属性修饰的属性分配了信之后,才能执行这些初始设置,如提取单个路径。
  此组件通过侦听由 NavigationManager 类定义的 LocationChanged 事件来响应当前 URL 中的更改。事件的 Location 属性为组件提供当前 URL, 该 URL 用于更改锚元素的类。
  在 Blazor 文件夹的 NavLayout.razor 文件中应用新组件。

@foreach (string key in NavLinks.Keys)
{
    <MultiNavLink class="btn btn-outline-primary btn-block" href="@NavLinks[key]"
                  ActiveClass="btn-primary text-white">
        @key
    </MultiNavLink>
}
@code
{
    public Dictionary<string, string[]> NavLinks = new Dictionary<string, string[]>
    {
        {"People", new string[] {"/people", "/" } },
        {"Departments", new string[] {"/depts", "/departments" } },
        {"Details", new string[] { "/person" } }
     };
}

  并请求 http://localhost:5000http://localhost:5000/departments。这两个URL都可以识别,相应的导航按钮高亮显示。
对异步任务使用生命周期方法
  生命周期方法对于执行可能在呈现组件的初始内容之后完成的任务也很有用,例如查询数据库。在 Blazor 文件夹的 PersonDisplay.razor 文件中通过生命周期方法查询数据。

@page "/person"
@page "/person/{id:long}"

@if (Person == null)
{
    <h5 class="bg-info text-white text-center p-2">Loading...</h5>
}
else
{
    <table class="table table-striped table-bordered">
        <tbody>
            <tr><th>Id</th><td>@Person.PersonId</td></tr>
            <tr><th>Surname</th><td>@Person.Surname</td></tr>
            <tr><th>Firstname</th><td>@Person.Firstname</td></tr>
        </tbody>
    </table>
}

<button class="btn btn-outline-primary" @onclick="@(()=>HandleClick(false))">
    Previous
</button>
<button class="btn btn-outline-primary" @onclick="@(()=>HandleClick(true))">
    Next
</button>

@code
{
    [Inject]
    public DataContext Context { get; set; }
    [Inject]
    public NavigationManager NavManager { get; set; }
    [Parameter]
    public long Id { get; set; } = 0;
    public Person Person { get; set; }

    protected async override Task OnParametersSetAsync()
    {
        await Task.Delay(1000);
        Person = await Context.People.FirstOrDefaultAsync(p => p.PersonId == Id)
            ?? new Person();
    }
    public void HandleClick(bool increment)
    {
        Person = null;
        NavManager.NavigateTo($"/person/{(increment ? Id + 1 : Id - 1)}");
    }
}

  直到设置了参数值,组件才能查询数据库,因此在 OnParametersSetAsync 方法中获得 Person 属性的值。因为数据库是与 ASP.NET Core 服务器一起运行的,在查询数据库之前添加了一秒钟的延迟,以帮助强调组件的工作方式。
  在查询完成前,Person 属性的值为 null,这时它是表示查询结果的对象,或者如果查询没有生成结果,它就是一个新的 Person 对象。当 Person 对象为空时,将显示加载消息。
  重启ASP.NET Core,并请求 http://ocalhost:5000。单击表中显示的一个 Edit 按钮,PersonDisplay组件显示数据的摘要。单击 Previous 和 Next 按钮,以查询具有相邻主键值的对象。

4 管理组件的交互

  大多数组件通过参数和事件来工作,允许通过用户的交互来驱动应用程序中的更改。Blaz0还提供了用于管理与组件交互的高级选项。

4.1 使用子组件的引用

  父组件可以获得对子组件的引用,并可使用子组件定义的属性和方法。将禁用状态添加到 MultiNavLink 组件。

<a class="@ComputedClass" @onclick="HandleClick" href="">
    @if (Enabled)
    {
        @ChildContent
    }
    else
    {
        @("Disabled")
    }
</a>

@code
{
    [Inject]
    public NavigationManager NavManager { get; set; }

    [Parameter]
    public IEnumerable<string> Href { get; set; }

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

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

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

    [Parameter]
    public NavLinkMatch? Match { get; set; }

    public NavLinkMatch ComputedMatch
    {
        get => Match ??
        (Href.Count() == 1 ? NavLinkMatch.Prefix : NavLinkMatch.All);
    }

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

    public string ComputedClass { get; set; }


    public void HandleClick()
    {
        NavManager.NavigateTo(Href.First());
    }

    private void CheckMatch(string currentUrl)
    {
        string path = NavManager.ToBaseRelativePath(currentUrl);
        path = path.EndsWith("/") ? path.Substring(0, path.Length - 1) : path;
        bool match = Href.Any(href => ComputedMatch == NavLinkMatch.All
                ? path == href : path.StartsWith(href));
        if (!Enabled)
        {
            ComputedClass = DisabledClasses;
        }
        else
        {
            ComputedClass = match ? $"{Class} {ActiveClass}" : Class;
        }
    }

    protected override void OnParametersSet()
    {
        ComputedClass = Class;
        NavManager.LocationChanged += (sender, arg) => CheckMatch(arg.Location);
        Href = Href.Select(h => h.StartsWith("/") ? h.Substring(1) : h);
        CheckMatch(NavManager.Uri);
    }

    private bool Enabled { get; set; } = true;

    public void SetEnabled(bool enabled)
    {
        Enabled = enabled;
        CheckMatch(NavManager.Uri);
    }
}

  在 Blazor 文件夹的 NavLayout.razor 文件中保留引用,更新共享布局,以便保留对 MultiNavLink 组件和一个按钮的引用该按钮用于切换其 Enabled 属性值。

@inherits LayoutComponentBase

<div class="container-fluid">
    <div class="row">
        <div class="col-3">
            @foreach (string key in NavLinks.Keys)
            {
                <MultiNavLink class="btn btn-outline-primary btn-block"
                              href="@NavLinks[key]"
                              ActiveClass="btn-primary text-white"
                              DisabledClasses="btn btn-dark text-light btn-block disabled"
                @ref="Refs[key]">
                    @key
                </MultiNavLink>
            }
            <button class="btn btn-secondary btn-block mt-5 " @onclick="ToggleLinks">
                Toggle Links
            </button>
        </div>
        <div class="col">
            @Body
        </div>
    </div>
</div>

@code
{

    public Dictionary<string, string[]> NavLinks = new Dictionary<string, string[]>
    {
        {"People", new string[] {"/people", "/" } },
        {"Departments", new string[] {"/depts", "/departments" } },
        {"Details", new string[] { "/person" } }
     };

    public Dictionary<string, MultiNavLink> Refs
        = new Dictionary<string, MultiNavLink>();

    private bool LinksEnabled = true;

    public void ToggleLinks()
    {
        LinksEnabled = !LinksEnabled;
        foreach (MultiNavLink link in Refs.Values)
        {
            link.SetEnabled(LinksEnabled);
        }
    }
}

  通过添加 @ref 属性,并指定应该为组件分配的字段或属性的名称,可以创建对组件的引用。由于 MultiNavLink 组件是在由 Dictionary 驱动的 @foreach 循环中创建的,因此保留引用的最简单方法也是在 Dictionary 中,如下所示:@ref="Refs[key]"。在创建每个 MultiNavLink 组件时,它添加到 Refs 字典中。Razor 组件编译成标准的 C#类,这意味着 MultiNavink 组件的集合就是 MultiNavLink 对象的集合。
  重启ASP.NET Core,并请求 http://localhost:5000。然后单击 Toggle Links 按钮。事件处理程序调用 ToggleLinks 方法,它为每个 MultiNavLink 组件设置 Enabled 属性的值。

4.2 与来自其他代码的组件交互

  组件可被 ASP.NET Core 应用程序中的其他代码使用,允许复杂项目的各个部分之间进行更丰富的交互。如下修改了 MultiNavLink 组件中的方法,以便可以由 ASP.NET Core 应用程序的其他部分调用,来启用和禁用导航。

public void SetEnabled(bool enabled)
{
    InvokeAsync(() =>
    {
        Enabled = enabled;
        CheckMatch(NavManager.Uri);
        StateHasChanged();
    });
}

  组件提供了在 Blazor环境外部调用的代码中使用的两个方法:InvokeAsync(func) 方法用于调用 Blazor 环境中的一个函数,以确保正确地处理更改。当应用所有更改时,将调用 StateHasChanged() 方法,触发 Blazor 更新并确保更改反映在组件的输出中。
  要创建将在整个应用程序中可用的服务,请创建 Services 文件夹,并将一个名为ToggleService.cs 的类文件添加到其中。

using MyAdvanced.Blazor;
using System.Collections.Generic;
using Microsoft.JSInterop;

namespace MyAdvanced.Services
{
    public class ToggleService
    {
        private List<MultiNavLink> components = new List<MultiNavLink>();
        private bool enabled = true;

        public void EnrolComponents(IEnumerable<MultiNavLink> comps)
        {
            components.AddRange(comps);
        }

        [JSInvokable]
        public bool ToggleComponents()
        {
            enabled = !enabled;
            components.ForEach(c => c.SetEnabled(enabled));
            return enabled;
        }
    }
}

  此服务管理一个组件集合,并在调用其 ToggleComponents 方法时对所有组件调用 SetEnabled 方法。 更新 Startup.cs 应用程序配置,将 Toggleservice 类配置为单例服务。

services.AddSingleton<Services.ToggleService>();

  在 Blazor 文件夹的 NavLayout.razor 文件中使用服务。

@inherits LayoutComponentBase
@using MyAdvanced.Services

......

@code
{

    [Inject]
    public ToggleService Toggler { get; set; }

    public Dictionary<string, string[]> NavLinks = new Dictionary<string, string[]>
    {
        {"People", new string[] {"/people", "/" } },
        {"Departments", new string[] {"/depts", "/departments" } },
        {"Details", new string[] { "/person" } }
     };

    public Dictionary<string, MultiNavLink> Refs
        = new Dictionary<string, MultiNavLink>();

    private bool LinksEnabled = true;

    protected async override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            Toggler.EnrolComponents(Refs.Values);
        }
    }

    public void ToggleLinks()
    {
        Toggler.ToggleComponents();
    }
}

  如上一节所述,组件引用只有在内容呈现之后才可用。代码使用 OnAferRender 生命周期方法向服务注册组件引用,服务是通过依赖项注入接收的。
  最后一步是从 ASP.NET Core 应用程序的不同部分使用服务。每次处理请求时,在 Controllers 文件夹的 HomeController.cs 文件中,向调用 ToggleService 的主控制器添加了一个简单的操作方法。

private DataContext context;
private ToggleService toggleService;

public HomeController(DataContext dbContext,ToggleService ts)
{
    context = dbContext;
    toggleService = ts;
}
public string Toggle() =>
    $"Enabled: { toggleService.ToggleComponents() }";

  重启 ASP.NET Core 并请求 http://localhost:5000。打开一个单独的浏览器窗口并请求http:/localhost:5000/controllers/home/toggle。当 ASP.NET Core 应用程序处理第二个请求时。操作方法将使用服务来切换导航按钮的状态。每次请求 /controllers/home/toggle 时,导航按钮的状态将发生变化。

4.3 使用 JavaScript 与组件交互

  Blazor 提供了一系列在 JavaScript 和服务器端 C#代码之间交互的工具,如下所述。
1.从组件中调用 JavaScript 函数
  在 wwwoot 文件夹中添加一个名为 interop.js。

function addTableRows(colCount) {
    let elem = document.querySelector("tbody");
    let row = document.createElement("tr");
    elem.append(row);
    for (let i = 0; i < colCount; i++) {
        let cell = document.createElement("td");
        cell.innerText = "New Elements"
        row.append(cell);
    }
}

  javascript 代码使用浏览器提供的 API 来定位 tbody 元素,该元素表示表的主体,并添加一个新行,其中包含由函数参数指定的单元格数量。
  要将 JavaScript 文件合并到应用程序中,在 Pages 文件夹的 _Host.cshtml Rozar Pages 中添加元素,该页面配置为将 Blazor 应用程序交付到浏览器的回退页面。

<script src="~/interop.js"></script>

  在 Blazor文件夹的 PersonDisplay.razor 文件中调用 JavaScrpt 函数。

@page "/person"
@page "/person/{id:long}"

@if (Person == null)
{
    <h5 class="bg-info text-white text-center p-2">Loading...</h5>
}
else
{
    <table class="table table-striped table-bordered">
        <tbody>
            <tr><th>Id</th><td>@Person.PersonId</td></tr>
            <tr><th>Surname</th><td>@Person.Surname</td></tr>
            <tr><th>Firstname</th><td>@Person.Firstname</td></tr>
        </tbody>
    </table>
}

<button class="btn btn-outline-primary" @onclick="@HandleClick">
    Invoke JS Funtion
</button>


@code
{
    [Inject]
    public DataContext Context { get; set; }
    [Inject]
    public NavigationManager NavManager { get; set; }
    [Inject]
    public IJSRuntime JSRuntime { get; set; }
    [Parameter]
    public long Id { get; set; } = 0;
    public Person Person { get; set; }

    protected async override Task OnParametersSetAsync()
    {
        //await Task.Delay(1000);
        Person = await Context.People.FirstOrDefaultAsync(p => p.PersonId == Id)
            ?? new Person();
    }
    public async Task HandleClick()
    {
        await JSRuntime.InvokeVoidAsync("addTableRows", 2);
    }
}

  调用 Javascipt 函数是通过 IJSRuntime 接口完成的,该接口由组件通过依赖项注入接收。服务是作为Blazor 配置的一部分自动创建的,并提供表中描述的方法。

名称 描述
InvokeAsync(name, args) 此方法使用提供的参数调用指定的函数。结果类型由泛型类型参数指定
InvokeVoidAsync(name, args) 此方法调用一个不生成结果的函数

  在代码清单中,使用 InvokeVoidAsync 方法调用 JavaScript 函数 addTableRows,为函数参数提供一个值。重启 ASP.NET Core,导航到 http:/localhost:5000/person/1,然后单击 Invoke JS Funtion 按钮。Blazor 将调用 JavaScript 函数,该函数将在表的末尾添加一行。
2.保留对 HTML 元素的引用
  Razor 组件可以保留对它们创建的 HTML 元素的引用,并将这些引用传递给 JavaScript 代码。在wwwroot 文件夹的 interop.js 文件中定义参数,使其对通过参数接收的 HTML 元素进行操作。

function addTableRows(colCount, elem) {
    //let elem = document.querySelector("tbody");
    let row = document.createElement("tr");
    elem.parentNode.insertBefore(row, elem);
    for (let i = 0; i < colCount; i++) {
        let cell = document.createElement("td");
        cell.innerText = "New Elements"
        row.append(cell);
    }
}

  在 Blazor 文件夹的 PersonDisplay.razor 文件中保留保留了对它创建的一个 HTML 元素的引用,并将其作为参数传递给 JavaScript 函数。

<tr @ref="RowReference"><th>Surname</th><td>@Person.Surname</td></tr>
public ElementReference RowReference { get; set; }
public async Task HandleClick()
{
    await JSRuntime.InvokeVoidAsync("addTableRows", 2, RowReference);
}

  @ref 属性将 HTML 元素分配给一个属性,该属性的类型必须是 ElementReference。重启ASP.NET Core,请求 http:/localhost:5000/person/1,然后单击 Invoke Js Function 按钮。HementReference 属性的值作为参数通过 InvokeVoidAsync 方法传递给 Javascript 函数。
3.从 JavaScript 调用组件方法
  从JavaScript 调用 C#方法的基本方法是使用静态方法。 在 Blazor 文件夹的 MultiNavLink.razor 文件中引入静态成员。

[JSInvokable]
public static void ToggleEnabled() => ToggleEvent.Invoke(null, new EventArgs());
private static event EventHandler ToggleEvent;
protected override void OnInitialized()
{
    ToggleEvent += (sender, args) => SetEnabled(!Enabled);
}

  静态方法必须使用 JSInvokable 属性进行修饰,然后才能从JavaScript 代码中调用它们。使静态方法的主要限制是难以更新单个组件,因此定义了组件的每个实例都会处理的一个静态事件,该事件命名为 ToggleEvent,它由将从 JavaScript 调用的静态方法触发。为了监听事件,使用 Onnitiaized 生命周期事件。当接收到事件时,组件的启用状态通过实例方法 SetEnabled 来切换,该方法使用在 Blazor 之外进行更改时所需的 InvokeAsync 和 StateHasChanged 方法。
  在wwwroot 文件夹的 interop.js 文件中添加函数,该函数创建了一个 buton 元素,该元素在单击时调用静态 C#方法。

function createToggleButton() {
    let sibling = document.querySelector("button:last-of-type");
    let button = document.createElement("button");
    button.classList.add("btn", "btn-secondary", "btn-block");
    button.innerText = "JS Toggle";
    sibling.parentNode.insertBefore(button, sibling.nextSibling);
    button.onclick = () => DotNet.invokeMethodAsync("MyAdvanced","ToggleEnabled");
}

  新函数定位一个现有 button 元素,并在其后面添加一个新按钮。当单击按钮时,调用组件方法,如下所示: DotNet.invokeMethodAsync
  注意用于 C#方法的 JavaScript 函数的大小写很重要:它是 DoNet,后面是句点,后面是imvokeMethodAsync,后面是小写的i,参数是程序集的名称和静态方法的名称。组件的名称不是必需的。
  上述代码中函数寻找的按钮元素只有在 Blazor 为用户呈现了内容之后才可用。由于这个原因,如下 NavLayout 组件定义的 OnAheRenderAsync 方法中深加了一条语句,仅在内容呈现后才调用JavaScipt 的函数数。NavLayout 组件是 MuitiNavLink 组件的父组件,当调用静态方法时,MuitiNavLink组件会受到影响,并允许确保Javaseript函数贝调用一次。
  在Blazor 文件夹的 NavLayout.razor文件中调用 JavaScript 函数。

@code
{
    [Inject]
    public IJSRuntime JSRuntime { get; set; }

    [Inject]
    public ToggleService Toggler { get; set; }

    public Dictionary<string, string[]> NavLinks = new Dictionary<string, string[]>
    {
        {"People", new string[] {"/people", "/" } },
        {"Departments", new string[] {"/depts", "/departments" } },
        {"Details", new string[] { "/person" } }
     };

    public Dictionary<string, MultiNavLink> Refs
        = new Dictionary<string, MultiNavLink>();

    private bool LinksEnabled = true;
    protected async override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            Toggler.EnrolComponents(Refs.Values);
            await JSRuntime.InvokeVoidAsync("createToggleButton");
        }
    }

    public void ToggleLinks()
    {
        Toggler.ToggleComponents();
    }
}

4.从 JavaScript 函数中调用实例方法
  上一个示例中的复杂性部分来自于对更新 Razor 组件对象的静态方法的响应。另一种方法是为 JavaScript 代码提供对实例方法的引用,然后可以直接调用该实例方法。第一步是将 JSInvokable 属性添加到JavaScript代码将要调用的方法中。
  下面 Services 文件夹的 ToggleService.cs 类定义的 ToggleComponents 方法应用属性。

[JSInvokable]
public bool ToggleComponents()
{
    enabled = !enabled;
    components.ForEach(c => c.SetEnabled(enabled));
    return enabled;
}

  下一步是为JavaScript 函数提供一个对象的引用,该对象的方法将被调用,在Blazor文件夹的 NavLayout.razor文件中提供实例。

protected async override Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        Toggler.EnrolComponents(Refs.Values);
        await JSRuntime.InvokeVoidAsync("createToggleButton",
            DotNetObjectReference.Create(Toggler));
    }
}

  DotNetObjectRefcrence.Create 方法创建对对象的引用,该引用使用 JSRuntime.InvokeVidAsync 方法作为参数传递给 JavaScript 函数。最后一步是用 JavaScript 接收对象引用,并在单击按钮元素时调用其方法,在wwwroot 文件夹的interop.js 文件中调用 C#方法。

function createToggleButton(toggleServiceRef) {
    let sibling = document.querySelector("button:last-of-type");
    let button = document.createElement("button");
    button.classList.add("btn", "btn-secondary", "btn-block");
    button.innerText = "JS Toggle";
    sibling.parentNode.insertBefore(button, sibling.nextSibling);
    button.onclick = () => toggleServiceRef.invokeMethodAsync("ToggleComponents");
}

  JavaScript 函数接收对 C#对象的引用作为参数,并使用 invokeMethodAsync 调用它的方法将方法的名称指定为参数。也可提供方法的参数,但在本例中不是必需的。重启 ASP.NET Core,请求 http://localhost:5000,然后单击 JS Toggle 按钮。组件中的更改是通过 ToggleService 对象管理的。

posted @ 2024-06-29 09:07  一纸年华  阅读(21)  评论(0编辑  收藏  举报