一、组件

支撑Blazor的是微软的两大成熟技术,Razor模板和SignalR,两者的交汇点就是组件。通常,我们从ComponentBase派生的类型,或者创建的.razor 文件,就可以称作组件。基于这两大技术,组件也就具备了两大功能,1、生成html片段;2、维护组件状态。这里我们来说一下组件最基本的功能,生成html片段。

二、RenderTreeBuilder,RenderFragment

我们知道,浏览器处理HTML 文档时会将所有的标签都挂到一颗文档树中,无论一段HTML来自哪里,总会被这棵树安排的明明白白。换句话说,如果有根线的话,我们可以依靠这棵树把所有的标签都串起来,而在Blazor组件中也有这么一根线,这根线就是RenderTreeBuilder,拿这根线的人就是Blazor框架。

备注一下:以下涉及的代码如果没有特别说明,都是指写在.cs文件中,继承 Microsoft.AspNetCore.Components.ComponentBase 的组件类。

下面用代码看看这根线。 新建一个Blazor 应用 项目,新增 一个c#类,MyComp 继承 Microsoft.AspNetCore.Components.ComponentBase,然后override 一下,找到如下方法:

 protected override void BuildRenderTree(RenderTreeBuilder builder)
{
base.BuildRenderTree(builder);//加断点
}

加个断点,在项目的 Pages\Index.razor 里加上一行。<MyComp />
如果不想代码执行两次,就在Pages_Host.cshtml 里修改一下rendermode

 @(await Html.RenderComponentAsync<App>(RenderMode.Server))

F5跑起来,虽然没有任何输出,但是断点命中了,RenderTreeBuilder这根线确实串起了我们的组件。
现在让我们看看,RenderTreeBuilder 可以做什么。

  protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.AddMarkupContent(0, "<span> BuildRenderTree 使用 AddMarkupContent 输出 Html 。</span>");
// base.BuildRenderTree(builder);
}

再次跑起来,我们发现页面上多了我们加的span.也就是说HTML的输出,靠的是调用RenderTreeBuilder上的各种方法加上的。组件的基本原理也就是这样,一个RenderTreeBuilder 进入不同组件的 BuildRenderTree 方法,方法内 通过RenderTreeBuilder上的add.. open.. 方法把我们想要输出的部分,挂载到builder上,最终输出到浏览器。

接下来,我们考察一下BuildRenderTree方法, 用委托描述一下,我们发现这就是一个Action<RenderTreeBuilder>.

在标题里我们提到了RenderFragment, 查看一下它的定义。

public delegate void RenderFragment(RenderTreeBuilder builder);//还是一个 Action<RenderTreeBuilder>,或者说,BuildRenderTree 就是一个RenderFragment

我们发现和前面的BuildRenderTree 在签名上一模一样,既然blazor会使用RenderTreeBuilder 去调用BuildRenderTree 方法,那么RenderFragment会不会也被调用?

让我们暂时离开组件MyComp,转到Index.razor 内加一段code

 @code{
RenderFragment MyRender=(builder) => builder.AddMarkupContent(0, "<span>当前输出来自:Index.razor 组件, MyRender 字段。 </span>"); }

在之前我们声明 MyComp组件之后,再加一行调用 @MyRender.
完整的Index.razor

@page "/"

<MyComp />

 @MyRender

@code{

    RenderFragment MyRender = (builder) => builder.AddMarkupContent(0, "<div>当前输出来自:Index.razor 组件, MyRender 字段。 </div>");

}

两段信息,如愿输出,证明blazor能够识别出模板中的 RenderFragment ,并自动调用。
既然我们在组件模板中(Index.razor)书写RenderFragment ,当然有其他方式可以不用拼凑字符串。

 RenderFragment AnotherRender =@<div>模板写法的RenderFragment</div>;

加上调用 @AnotherRender,跑起来,三段信息。

至此,我们对RenderFragment 有了一个大概的了解,它是一个函数,内部打包了我们的输出内容。在模板中我们可以使用,@xxxrender将其就地展开输出,在c#环境下我们可以通过 xxxrender(builder)的形式进行调用(比如在BuildRenderTree方法内调用)。又因为其本身就是一个委托函数,因此我们即可以在组件内使用,也可以自由的在组件之间传递, 完成对输出内容及逻辑的复用。
同时,为了更好的配合RenderFragment 使用,Blazor中还提供了一个工厂委托,RenderFragment , 即 Func<TValue,RenderFragment> 用法一般如下

//模板中(Index.razor)
RenderFragment<object> RenderValue =value=> @<div> render value :@value</div>;

调用 @RenderValue (123) 如果在c#代码中,比如在BuildRenderTree 方法内, RenderValue (123)(builder)

vs中*.razor在编译时会生成对应的.g.cs代码,位置在obj/debug/netcoreapp3.0/ razor 下,可以多打开看看。

三、RenderFragment 的一些用法

1、html中,我们可以在一对标签内添加 内容,比如 <div>123</div>,组件默认是不支持此类操作的,这时我们就需要RenderFragment来包装标签内的内容。

让我们回到MyComp组件类中,增加一个属性

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

Index.razor

<MyComp><div> 组件标记内部</div></MyComp>

此时直接运行的话,组件不会输出内部信息,需要在BuildRenderTree 中执行一下

  protected override void BuildRenderTree(RenderTreeBuilder builder)
{
ChildContent?.Invoke(builder); base.BuildRenderTree(builder);
}

组件标记内的片段被打包进了 ChildContent,已经变成了独立的一个片段,因此需要我们显式的调用一下。
ChildContent 是特殊名称

2、组件上有多个RenderFragment

   [Parameter] public RenderFragment Fragment1 { get; set; }
[Parameter] public RenderFragment Fragment2 { get; set; }

此时调用需要调整一下,不然框架不知道把内容片段打包进哪个属性里

 <MyComp>
<Fragment1> <div> Fragment1 </div>
</Fragment1>
<Fragment1>
<div> Fragment1.1 </div> </Fragment1>
<Fragment2>
<div> Fragment2 </div> </Fragment2> </MyComp>

这里故意重复处理了Fragment1,可以看看结果。

3、带参数的RenderFragment
code:

[Parameter] public RenderFragment<MyComp> ChildContent { get; set; }

调用及传参

  <MyComp Context="self" > //<ChildContent>
@self.GetType() </MyComp> //</ChildContent>

4、打开的组件声明标记内部,除了可以使用RenderFragment 参数属性外,其他的razor 语法基本都支持,也包括另外一个组件。
比如

  <MyComp>
<CompA />
<CompB> ...... </CompB>
</MyComp>

或者

  <MyComp>
<Fragment1>
<CompA />
</Fragment1> <Fragment2>
<CompB> ...... </CompB>
</Fragment2>
</MyComp>

虽然看上去,声明标记的代码很相似,但却有着实质上的不同。
当我们使用 标记声明一个参数属性时,我们是在生成RenderFragment,随后将其赋值给对应的属性。
当我们使用标记声明一个组件时,我们是在构造一个组件实例,然后调用它,将组件输出插入到组件所在位置。
参数属性(RenderFragment )属于组件,是组件的一个属性,互相关系是明确的类型《=》成员关系。
组件内部的其他组件标记很多时候只是为了复用一些输出片段,如果不通过代码进行一些处理的话,是无法明确知道组件之间关系的。

四、CascadingValue/CascadingParameter

组件多起来之后,组件之间的数据共享和传递以及组件间的关系就会变的很麻烦,数量少的时候,还可以使用@ref 手工指定,多起来之后@ref明显不是一个好方法。 组件CascadingValue和对应的特性[CascadingParameter]就是为了解决这一问题而出现。

一个CascadingValue 内的所有组件 包括子级,只要组件属性上附加了[CascadingParameter]特性,并且值内容可以兼容,此属性就会被赋值。

比如给组件定义 属性接收CascadingValue

        [CascadingParameter] public  int Value { get; set; }
[CascadingParameter] public string SValue { get; set; } //修改下输出
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.AddMarkupContent(0, $"<div>CascadingValue: {Value},{SValue} </div>");// 一个int,一个string
ChildContent?.Invoke(this)(builder);//加载下级组件
base.BuildRenderTree(builder);
}

在razor页中

 <CascadingValue Value="123"> //int
<MyComp>
<MyComp></MyComp>
</MyComp>
</CascadingValue >

执行后我们就会发现,两个组件都捕获到了int 值 123.
现在再加一个CascadingValue

 <CascadingValue Value="123"> //int
<CascadingValue Value="@("aaaa")"> //string
<MyComp>
<MyComp></MyComp>
</MyComp>
</CascadingValue >
</CascadingValue >

分属两个CascadingValue 的两个不同类型值,就被每个组件的两个属性捕获到,方便、强大而且自身不产生任何HTML输出,因此使用场景非常广泛。比如官方Forms组件中就是借助CascadingValue/Parameter 完成model的设置,再比如,组件默认没有处理父子、包含关系的接口,这时就可以简单的定义一个[CascadingParameter] public ComponentBase Parent{get;set;}专门接收父级组件,处理类似Table/Columns之类的组件关系。

五、总结

组件是为其自身的 BuildRenderTree方法 ( RenderFragment )服务的,组件上的各种属性方法,都是为了给RenderFragment 做环境准备,因此组件实质上是个RenderFragment的包装类。组件系统则通过一个RenderTreeBuilder依次调用各组件,收集输出内容,最终交给系统内部完成输出。
1、.Razor文件会被编译为一个组件类(obj/debug/netcore3.0/razor/...)
2、组件系统创建RenderTreeBuilder,将其交给组件实例
3、组件实例使用 RenderTreeBuilder,调用自身 BuildRenderTree。
4、等待组件状态变化,再次输出。

原文连接:https://www.cnblogs.com/cerl/p/11834510.html

Blazor 服务端组件 Render, RenderFragment ,RenderTreeBuilder, CascadingValue/CascadingParameter的更多相关文章

  1. [AspNetCore 3.0 ] Blazor 服务端组件 Render, RenderFragment ,RenderTreeBuilder, CascadingValue/CascadingParameter 等等

    一.组件 支撑Blazor的是微软的两大成熟技术,Razor模板和SignalR,两者的交汇点就是组件.通常,我们从ComponentBase派生的类型,或者创建的.razor 文件,就可以称作组件. ...

  2. [Asp.net core 3.1] 通过一个小组件熟悉Blazor服务端组件开发

    通过一个小组件,熟悉 Blazor 服务端组件开发.github 一.环境搭建 vs2019 16.4, asp.net core 3.1 新建 Blazor 应用,选择 asp.net core 3 ...

  3. 基于 IOCP 的通用异步 Windows Socket TCP 高性能服务端组件的设计与实现

    设计概述 服务端通信组件的设计是一项非常严谨的工作,其中性能.伸缩性和稳定性是必须考虑的硬性质量指标,若要把组件设计为通用组件提供给多种已知或未知的上层应用使用,则设计的难度更会大大增加,通用性.可用 ...

  4. MVC文件上传04-使用客户端jQuery-File-Upload插件和服务端Backload组件实现多文件异步上传

    本篇使用客户端jQuery-File-Upload插件和服务端Badkload组件实现多文件异步上传.MVC文件上传相关兄弟篇: MVC文件上传01-使用jquery异步上传并客户端验证类型和大小  ...

  5. GrapeCity Documents (服务端文档API组件) V3.0 正式发布

    近日,葡萄城GrapeCity Documents(服务端文档API组件)V3.0 正式发布! 该版本针对 Excel 文档.PDF 文档和 Word 文档的 API 全面更新,加入了用于生成 Exc ...

  6. MVC文件上传07-使用客户端jQuery-File-Upload插件和服务端Backload组件裁剪上传图片

    本篇通过在配置文件中设置,对上传图片修剪后保存到指定文件夹. 相关兄弟篇: MVC文件上传01-使用jquery异步上传并客户端验证类型和大小  MVC文件上传02-使用HttpPostedFileB ...

  7. MVC文件上传05-使用客户端jQuery-File-Upload插件和服务端Backload组件自定义上传文件夹

    在零配置情况下,文件的上传文件夹是根目录下的Files文件夹,如何自定义文件的上传文件夹呢? MVC文件上传相关兄弟篇: MVC文件上传01-使用jquery异步上传并客户端验证类型和大小  MVC文 ...

  8. atitit.组件化事件化的编程模型--服务端控件(1)---------服务端控件与标签的关系

    atitit.组件化事件化的编程模型--服务端控件(1)---------服务端控件与标签的关系 1. 服务器控件是可被服务器理解的标签.有三种类型的服务器控件: 1 1.1. HTML 服务器控件  ...

  9. 自己封装的Socket组件,实现服务端多进程共享Socket对象,协同处理客户端请求

    DotNet.Net.MySocket是SLB.NET(Server Load Balance服务器负载均衡)项目中的核心组件. 在实际的项目中发现,单进程的服务端处理高并发的客户请求能力有限. 所以 ...

随机推荐

  1. 前端规范1-HTML规范

    HTML规范 1代码风格(参1,) 使用Tab字符(四个空格长度) 层级关系太多时尽量写在一行,但保证每行代码不宜过长  例,代码不宜过长 例,尽量写在一行 2命名(参1,) class必须使用小写, ...

  2. eclipse Some projects cannot be imported because they already exist in the workspace

    archive file 档案文件 删除对应的文件即可

  3. 通过反射 修改访问和修改属性的值 Day25

    package com.sxt.field; /* * 通过反射拿到属性值 * 修改public属性值 * 修改private属性值 * 缺点:可读性差:代码复杂 * 优点:灵活:可以访问修改priv ...

  4. 2019-11-1-asp-dotnet-core-简单开发P2P中央服务器

    title author date CreateTime categories asp dotnet core 简单开发P2P中央服务器 lindexi 2019-11-01 19:40:33 +08 ...

  5. Java练习 SDUT-2246_时间日期格式转换

    时间日期格式转换 Time Limit: 1000 ms Memory Limit: 65536 KiB Problem Description 对于日期的常用格式,在中国常采用格式的是"年 ...

  6. 日志 5.27 关于AssetBundle

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/zxsean/article/details/27228783 大概日志就这么写的吧.没什么太专业的东 ...

  7. 云原生生态周报 Vol. 3 | Java 8 ❤️ Docker

    摘要: Docker Hub遭入侵,19万账号被泄露:Java 8 终于开始提供良好的容器支持:Snyk 年度安全报告出炉,容器安全问题形势空前严峻. 业界要闻 Docker Hub遭入侵,19万账号 ...

  8. Hbase API: 读Bigtable

  9. 使用HSV色彩空间遮罩绿色区域

    HSV 颜色空间 导入资源 In []: import matplotlib.pyplot as plt import matplotlib.image as mpimg ​ import numpy ...

  10. 2、asp.net core 部署到服务器之后外网访问不了

    解决问题 把自定义端口的http://localhost:5001改成http://*:5001. 什么都没有改也不行的小伙伴试试在Program的Main方法中的.UseKestrel()后面添加. ...