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 对象管理的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/758870.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

黑马点评-Redis的缓存击穿,缓存雪崩,缓存穿透,互斥锁

文章目录 1.缓存穿透2.缓存雪崩3.缓存击穿3.1 互斥锁 1.缓存穿透 解决办法 写入NULL值到Redis缓存&#xff0c;以后就会命中Redis的控制缓存而不会出现请求直接打到数据库的问题&#xff01; 代码 2.缓存雪崩 这个概念很好理解&#xff0c;雪崩就是无数的小雪花结构突然因…

pandas数据分析(1)

pandas&#xff0c;即Python数据分析库&#xff08;Python data analysis library&#xff09; DataFrame和Series DataFrame&#xff08;数据帧&#xff09;和Series&#xff08;序列&#xff09;是pandas的核心数据结构。DataFrame的主要组件包含索引、列、数据。DataFrame和…

扫描全能王的AI驱动创新与智能高清滤镜技术解析

目录 引言1、扫描全能王2、智能高清滤镜黑科技2.1、图像视觉矫正2.2、去干扰技术 3、实际应用案例3.1、打印文稿褶皱检测3.2、试卷擦除手写3.3、老旧文件处理3.4、收银小票3.5、从不同角度扫描文档 4、用户体验结论与未来展望 引言 在数字化时代背景下&#xff0c;文档扫描功能…

云计算【第一阶段(21)】Linux引导过程与服务控制

目录 一、linux操作系统引导过程 1.1、开机自检 1.2、MBR引导 1.3、GRUB菜单 1.4、加载 Linux 内核 1.5、init进程初始化 1.6、简述总结 1.7、初始化进程centos 6和7的区别 二、排除启动类故障 2.1、修复MBR扇区故障 2.1.1、 实验 2.2、修复grub引导故障 2.2.1、实…

从AICore到TensorCore:华为910B与NVIDIA A100全面分析

华为NPU 910B与NVIDIA GPU A100性能对比&#xff0c;从AICore到TensorCore&#xff0c;展现各自计算核心优势。 AI 2.0浪潮汹涌而来&#xff0c;若仍将其与区块链等量齐观&#xff0c;视作炒作泡沫&#xff0c;则将错失新时代的巨大机遇。现在&#xff0c;就是把握AI时代的关键…

深入解析高斯过程:数学理论、重要概念和直观可视化全解

与其他算法相比&#xff0c;高斯过程不那么流行&#xff0c;但是如果你只有少量的数据&#xff0c;那么可以首先高斯过程。在这篇文章中&#xff0c;我将详细介绍高斯过程。并可视化和Python实现来解释高斯过程的数学理论。 多元高斯分布 多元高斯分布是理解高斯过程所必须的概…

图书管理系统(附源码)

前言&#xff1a;前面一起和小伙伴们学习了较为完整的Java语法体系&#xff0c;那么本篇将运用这些知识连串在一起实现图书管理系统。 目录 一、总体设计 二、书籍与书架 书籍&#xff08;Book&#xff09; 书架&#xff08;Booklist&#xff09; 三、对图书的相关操作 I…

java将html转成图片

java 将html转成图片 1.导入jar2.代码3.展示结果4.注意事项 最近有一个需求需要根据指定的样式生成图片&#xff0c;使用java原生技术有些麻烦&#xff0c;所以上网搜了下案例&#xff0c;最后发现最好用的还是html2image&#xff0c;这里进行简单总结下。 1.导入jar <!-- 用…

metasfresh开源ERP系统Windows开发环境配置参考

目录 概述 开发环境 配置过程 后端启动 前端启动 登陆系统 其他 概述 Compiere闭源之后衍生出了Admpiere等若干开源的产品&#xff0c;metasfresh就是其中之一&#xff0c;metasfresh截至发稿时在GitHub上已有64000多次的修改提交&#xff0c;而且仍在维护中&#xff0…

Python应用开发——30天学习Streamlit Python包进行APP的构建(12)

st.checkbox 显示复选框部件。 Function signature[source] st.checkbox(label, valueFalse, keyNone, helpNone, on_changeNone, argsNone, kwargsNone, *, disabledFalse, label_visibility"visible") Returns (bool) Whether or not the checkbox is checked. …

Sentinel解决雪崩问题

我们或多或少都对雪崩问题有点了解&#xff0c;在微服务系统中&#xff0c;各个微服务互相调用&#xff0c;关系错综复杂&#xff0c;如果其中一个微服务挂了或者处理消息的速度大幅下降&#xff0c;需要被处理的消息越积越多&#xff0c;那么影响的不仅仅是本微服务的功能&…

算法入门(上)

什么是算法&#xff1f; 算法&#xff08;Algorithm&#xff09;是解决特定问题求解步骤的描述&#xff0c;在计算机中表现为指令的有限序列&#xff0c;并且每条指令表示一个或多个操作。 给定一个问题&#xff0c;能够解决这个问题的算法是有很多种的。算式中的问题是千奇百怪…

C语言单链表的算法之插入节点

一&#xff1a;访问各个节点中的数据 &#xff08;1&#xff09;访问链表中的各个节点的有效数据&#xff0c;这个访问必须注意不能使用p、p1、p2&#xff0c;而只能使用phead &#xff08;2&#xff09;只能用头指针不能用各个节点自己的指针。因为在实际当中我们保存链表的时…

后端之路第三站(Mybatis)——XML文件操作sql

一、XML映射文件是啥 前面我们学过了在Mapper接口用注解的方式来操作sql语句 那么XML映射文件就另一种操作sql语句的方法 为什么还要有这么个玩意&#xff1f; 我简单说就是&#xff1a;如果有的sql特别复杂的话&#xff0c;比如需要【动态sql】的话&#xff0c;就得用到XM…

数据可视化期末总结

期末考试重点&#xff08;世界上最没意义的事情&#xff09; 选择 p8 数据可视化的标准&#xff1a; 实用、完整、真实、艺术、交互&#xff08;性&#xff09; p21 色彩三属性 色相、饱和度、亮度 p23 视觉通道的类型&#xff1a; 记得色调是定性 p39 散点图&#xff08;二维…

GIT-LFS使用

0.前言 目前git仓库有很多很大的文件需要管理&#xff0c;但是直接上传&#xff0c;每次clone的文件太大&#xff0c;所有准备使用git-lfs解决。 1、下载和安装 Git LFS 1.1、直接下载二进制包&#xff1a; Releases git-lfs/git-lfs GitHub 安装 Git LFS sudo rpm -ivh…

Leica Cyclone 3DR2024 一款功能强大的点云建模软件下载License获取

Leica Cyclone 3DR 2024 是一款功能强大的点云建模软件&#xff0c;使用旨在为用户提供全面的点云管理、自动化的点云分析&#xff0c;结合强大的建模&#xff0c;在一个直观友好的环境中&#xff0c;专注的完成挑战&#xff0c;提高生产力&#xff0c;轻松创建并交付专业的成果…

杨幂跨界学术圈:内容营销专家刘鑫炜带你了解核心期刊的学术奥秘

近日&#xff0c;知名艺人杨幂在权威期刊《中国广播电视学刊》上发表了一篇名为《浅谈影视剧中演员创作习惯——以电视剧<哈尔滨一九四四>为例》的学术论文&#xff0c;此举在学术界和娱乐圈均引起了广泛关注。该期刊不仅享有极高的声誉&#xff0c;还同时被北大中文核心…

Data-Driven Reinforcement Learning for Robotic Manipulation

意思是 不同的任务以及机器人都有单独的数据和模型 未来需要整合 一个大的数据集包含所有的 然后训练一个大模型 以后具体的任务只需要针对这个模型进行微调 challenge bootstrapping with large data 2 3 4 高清图补充

【C++】using namespace std 到底什么意思

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文作为 JohnKi 的学习笔记&#xff0c;引用了部分大佬的案例 &#x1f4e2;未来很长&a…