一、组件

支撑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、等待组件状态变化,再次输出。

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

  1. 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. 使用 Vue 2.0 实现服务端渲染的 HackerNews

    Vue 2.0 支持服务端渲染 (SSR),并且是流式的,可以做组件级的缓存,这使得极速渲染成为可能.同时, 和 2.0 也都能够配合 SSR 提供同构路由和客户端 state hydration.v ...

  4. Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端

    Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端 目录 前言 OAuth2.0简介 授权模式 (SimpleSSO示例) 使用Microsoft.Owin.Se ...

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

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

  6. React(0.13) 服务端渲染的两个函数

    1.React.renderToString 函数,  参数是组件,返回一个字符串 <!DOCTYPE html> <html> <head> <title& ...

  7. Zabbix 5.0:服务端进程总结

    Blog:博客园 个人 参考:<深入理解Zabbix监控系统>.<Zabbix用户手册> Zabbix服务端进程被分为不同的种类,每一种进程负责相应的任务,包括收集原始监控数据 ...

  8. SimpleSSO:使用Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端

    目录 前言 OAuth2.0简介 授权模式 (SimpleSSO示例) 使用Microsoft.Owin.Security.SimpleSSO模拟OpenID认证 通过authorization co ...

  9. zabbix系列(一)centos7搭建zabbix3.0.4服务端及配置详解

    1.安装常用的工具软件 yum install -y vim wget centos7关闭防火墙 systemctl stop firewalld.service systemctl disable ...

随机推荐

  1. vue 上传文件 和 下载文件 面试的时候被问到过

    Vue上传文件,不必使用什么element 的uplaod, 也不用什么npm上找的个人写的包,就用原生的Vue加axios就行了, 废话不多说,直接上代码:html: <input type= ...

  2. poll(2) 源码分析

    poll(2) poll(2) 系统调用的功能和 select(2) 类似:等待一个文件集合中的文件描述符就绪进行I/O操作. 使用 实现 select(2) 的局限性: 关注的文件描述符集合大小最大 ...

  3. Java基础学习(八) - 多线程

    理解线程 进程是指一个内存中运行的应用程序,系统运行一个程序即是一个进程从创建,运行,结束的过程. 线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程. 多线程的特点是并发 ...

  4. web前端体系-了解前端,深入前端,架构前端,再看前端。大体系-知识-小细节

    1.了解前端,深入前端,架构前端,再看前端.大体系-知识-小细节 个人认为:前端发展最终的导向是前端工程化,智能化,模块化,组件化,层次化. 2.面试第一关:理论知识. 2-1.http标准 2-2. ...

  5. css3:bacground-size

    个人博客: https://chenjiahao.xyz CSS3之背景尺寸Background-size是CSS3中新加的一个有关背景的属性,这个属性是改变背景尺寸的通过各种不同是属性值改变背景尺寸 ...

  6. 某CTF平台一道PHP代码审计

    这道题不是说太难,但是思路一定要灵活,灵活的利用源码中给的东西.先看一下源码. 首先要理解大意. 这段源码的大致的意思就是,先将flag的值读取放在$flag里面. 后面再接受你输入的值进行判断(黑名 ...

  7. RegExp实现字符替换

    将字符串组中的所有Paul替换成Ringo,g:执行全局匹配,查找所有匹配而非在找到第一个匹配后停止;\b:匹配单词边界,划分匹配字符的起始范围 <!DOCTYPE html> <h ...

  8. Web安全之url跳转漏洞及bypass总结

    0x01 成因 对于URL跳转的实现一般会有几种实现方式: META标签内跳转 javascript跳转 header头跳转 通过以GET或者POST的方式接收将要跳转的URL,然后通过上面的几种方式 ...

  9. 过waf实战之文件上传bypass总结

    这里总结下关于waf中那些绕过文件上传的姿势和尝试思路 环境 apache + mysql +php waf:某狗waf 这里写了一个上传页面 <html> <body> &l ...

  10. Oracle数据库提权(低权限提升至dba)

    0x01 Oracle存储过程”缺陷” 在 Oracle 的存储过程中,有一个有趣的特点:运行权限.运行权限分为两种,definer 和 invoker. definer 为函数创建者的权限,而 in ...