[Asp.net core 3.1] 通过一个小组件熟悉Blazor服务端组件开发
通過一個(gè)小組件,熟悉 Blazor 服務(wù)端組件開發(fā)。github:https://github.com/git-net/NBlazors
一、環(huán)境搭建
vs2019 16.4, asp.net core 3.1 新建 Blazor 應(yīng)用,選擇 asp.net core 3.1。根文件夾下新增目錄 Components,放置代碼。
二、組件需求定義
Components 目錄下新建一個(gè)接口文件(interface)當(dāng)作文檔,加個(gè) using?using Microsoft.AspNetCore.Components;。
先從直觀的方面入手。
類似 html 標(biāo)簽對(duì)的組件,樣子類似<xxx propA="aaa" data-propB="123" ...>其他標(biāo)簽或內(nèi)容...</xxx>或<xxx .../>。接口名:INTag.
需要 Id 和名稱,方便區(qū)分和調(diào)試。string TagId{get;set;} string TagName{get;set;}.
需要樣式支持。加上string Class{get;set;} string Style{get;set;}。
不常用的屬性也提供支持,使用字典。IDictionary<string,object> CustomAttributes { get; set; }
應(yīng)該提供 js 支持。加上using Microsoft.JSInterop;?屬性?IJSRuntime JSRuntime{get;set;}?。
考慮一下功能方面。
既然是標(biāo)簽對(duì),那就有可能會(huì)嵌套,就會(huì)產(chǎn)生層級(jí)關(guān)系或父子關(guān)系。因?yàn)橹皇强赡?#xff0c;所以我們新建一個(gè)接口,用來提供層級(jí)關(guān)系處理,IHierarchyComponent。
需要一個(gè) Parent ,類型就定為 Microsoft.AspNetCore.Components.IComponent.IComponent Parent { get; set; }.
要能添加子控件,void AddChild(IComponent child);,有加就有減,void RemoveChild(IComponent child);。
提供一個(gè)集合方便遍歷,我們已經(jīng)提供了 Add/Remove,讓它只讀就好。?IEnumerable<IComponent> Children { get;}。
一旦有了 Children 集合,我們就需要考慮什么時(shí)候從集合里移除組件,讓 IHierarchyComponent 實(shí)現(xiàn) IDisposable,保證組件被釋放時(shí)解開父子/層級(jí)關(guān)系。
組件需要處理樣式,僅有 Class 和 Style 可能不夠,通常還會(huì)需要 Skin、Theme 處理,增加一個(gè)接口記錄一下,?public interface ITheme{ string GetClass<TComponent>(TComponent component); }。INTag 增加一個(gè)屬性?ITheme Theme { get; set; }
INTag:
public interface INTag{string TagId { get; set; }string TagName { get; }string Class { get; set; }string Style { get; set; }ITheme Theme { get; set; }IJSRuntime JSRuntime { get; set; }IDictionary<string,object> CustomAttributes { get; set; }}IHierarchyComponent:
public interface IHierarchyComponent:IDisposable{IComponent Parent { get; set; }IEnumerable<IComponent> Children { get;}void AddChild(IComponent child);void RemoveChild(IComponent child);}ITheme
public interface ITheme{string GetClass<TComponent>(TComponent component);}組件的基本信息 INTag 有了,需要的話可以支持層級(jí)關(guān)系 IHierarchyComponent,可以考慮下一些特定功能的處理及類型部分。
Blazor 組件實(shí)現(xiàn)類似?<xxx>....</xxx>這種可打開的標(biāo)簽對(duì),需要提供一個(gè)?RenderFragment 或 RenderFragment<TArgs>屬性。RenderFragment 是一個(gè)委托函數(shù),帶參的明顯更靈活些,但是參數(shù)類型不好確定,不好確定的類型用泛型。再加一個(gè)接口,INTag< TArgs >:INTag, 一個(gè)屬性?RenderFragment<TArgs> ChildContent { get; set; }.
組件的主要目的是為了呈現(xiàn)我們的數(shù)據(jù),也就是一般說的 xxxModel,Data....,類型不確定,那就加一個(gè)泛型。INTag< TArgs ,TModel>:INTag.
RenderFragment 是一個(gè)函數(shù),ChildContent 是一個(gè)函數(shù)屬性,不是方法。在方法內(nèi),我們可以使用 this 來訪問組件自身引用,但是函數(shù)內(nèi)部其實(shí)是沒有 this 的。為了更好的使用組件自身,這里增加一個(gè)泛型用于指代自身,public interface INTag<TTag, TArgs, TModel>:INTag where TTag: INTag<TTag, TArgs, TModel>。
INTag[TTag, TArgs, TModel ]
public interface INTag<TTag, TArgs, TModel>:INTagwhere TTag: INTag<TTag, TArgs, TModel>{/// <summary>/// 標(biāo)簽對(duì)之間的內(nèi)容,<see cref="TArgs"/> 為參數(shù),ChildContent 為Blazor約定名。/// </summary>RenderFragment<TArgs> ChildContent { get; set; }}回顧一下我們的幾個(gè)接口。
INTag:描述了組件的基本信息,即組件的樣子。
IHierarchyComponent 提供了層級(jí)處理能力,屬于組件的擴(kuò)展能力。
ITheme 提供了 Theme 接入能力,也屬于組件的擴(kuò)展能力。
INTag<TTag, TArgs, TModel> 提供了打開組件的能力,ChildContent 像一個(gè)動(dòng)態(tài)模板一樣,讓我們可以在聲明組件時(shí)自行決定組件的部分內(nèi)容和結(jié)構(gòu)。
所有這些接口最主要的目的其實(shí)是為了產(chǎn)生一個(gè)合適的 TArgs, 去調(diào)用 ChildContent。
有描述,有能力還有了主要目的,我們就可以去實(shí)現(xiàn) NTag 組件。
三、組件實(shí)現(xiàn)
抽象基類 AbstractNTag
Components 目錄下新增 一個(gè) c#類,AbstractNTag.cs,?using Microsoft.AspNetCore.Components;?借助 Blazor 提供的 ComponentBase,實(shí)現(xiàn)接口。
public abstract class AbstractNTag<TTag, TArgs, TModel> : ComponentBase, IHierarchyComponent, INTag<TTag, TArgs, TModel>where TTag: AbstractNTag<TTag, TArgs, TModel>{}調(diào)整一下 vs 生成的代碼, IHierarchyComponent 使用字段實(shí)現(xiàn)一下。
Children:
List<IComponent> _children = new List<IComponent>();public void AddChild(IComponent child){this._children.Add(child);}public void RemoveChild(IComponent child){this._children.Remove(child);}Parent,dispose
IComponent _parent; public IComponent Parent { get=>_parent; set=>_parent=OnParentChange(_parent,value); }protected virtual IComponent OnParentChange(IComponent oldValue, IComponent newValue){if(oldValue is IHierarchyComponent o) o.RemoveChild(this);if(newValue is IHierarchyComponent n) n.AddChild(this);return newValue;} public void Dispose(){this.Parent = null;}增加對(duì)瀏覽器 console.log 的支持, razor Attribute...,完整的 AbstractNTag.cs
public abstract class AbstractNTag<TTag, TArgs, TModel> : ComponentBase, IHierarchyComponent, INTag<TTag, TArgs, TModel>where TTag: AbstractNTag<TTag, TArgs, TModel> {List<IComponent> _children = new List<IComponent>();IComponent _parent;public string TagName => typeof(TTag).Name;[Inject]public IJSRuntime JSRuntime { get; set; }[Parameter]public RenderFragment<TArgs> ChildContent { get; set; }[Parameter] public string TagId { get; set; }[Parameter]public string Class { get; set; }[Parameter]public string Style { get; set; }[Parameter(CaptureUnmatchedValues =true)]public IDictionary<string, object> CustomAttributes { get; set; }[CascadingParameter] public IComponent Parent { get=>_parent; set=>_parent=OnParentChange(_parent,value); }[CascadingParameter] public ITheme Theme { get; set; }public bool TryGetAttribute(string key, out object value){value = null;return CustomAttributes?.TryGetValue(key, out value) ?? false;}public IEnumerable<IComponent> Children { get=>_children;}protected virtual IComponent OnParentChange(IComponent oldValue, IComponent newValue){ConsoleLog($"OnParentChange: {newValue}");if(oldValue is IHierarchyComponent o) o.RemoveChild(this);if(newValue is IHierarchyComponent n) n.AddChild(this);return newValue;}protected bool FirstRender = false;protected override void OnAfterRender(bool firstRender){FirstRender = firstRender;base.OnAfterRender(firstRender);}public override Task SetParametersAsync(ParameterView parameters){return base.SetParametersAsync(parameters);}int logid = 0;public object ConsoleLog(object msg){logid++;Task.Run(async ()=> await this.JSRuntime.InvokeVoidAsync("console.log", $"{TagName}[{TagId}_{ logid}:{msg}]"));return null;}public void AddChild(IComponent child){this._children.Add(child);}public void RemoveChild(IComponent child){this._children.Remove(child);}public void Dispose(){this.Parent = null;} }Inject 用于注入
Parameter 支持組件聲明的 Razor 語法中直接賦值,<NTag Class="ssss" .../>;
Parameter(CaptureUnmatchedValues =true)?支持聲明時(shí)將組件上沒定義的屬性打包賦值;
CascadingParameter?配合 Blazor 內(nèi)置組件?<CascadingValue Value="xxx" >... <NTag /> ...</CascadingValue>,捕獲 Value。處理過程和級(jí)聯(lián)樣式表(css)很類似。
具體類 NTag
泛型其實(shí)就是定義在類型上的函數(shù),TTag,TArgs,TModel?就是 入?yún)?#xff0c;得到的類型就是返回值。因此處理泛型定義的過程,就很類似函數(shù)逐漸消參的過程。比如:
func(a,b,c)確定a之后,func(b,c)=>func(1,b,c);確定b之后,func(c)=>func(1,2,c);最終:func()=>func(1,2,3);執(zhí)行 func 可以得到一個(gè)明確的結(jié)果。同樣的,我們繼承 NTag 基類時(shí)需要考慮各個(gè)泛型參數(shù)應(yīng)該是什么:
TTag:這個(gè)很容易確定,誰繼承了基類就是誰。
TModel: 這個(gè)不到最后使用我們是無法確定的,需要保留。
TArgs: 前面說過,組件的主要目的是為了給 ChildContent 提供參數(shù).從這一目的出發(fā),TTag 和 TModel 的用途之一就是給TArgs提供類型支持,或者說 TArgs 應(yīng)該包含 TTag 和 TModel。又因?yàn)?ChildContent 只有一個(gè)參數(shù),因此 TArgs 應(yīng)該有一定的擴(kuò)展性,不妨給他一個(gè)屬性做擴(kuò)展。綜合一下,TArgs 的大概模樣就有了,來個(gè) struct。
RenderArgs 屬于常用輔助類型,因此不需要給 TArgs 指定約束。
Components 目錄下新增 Razor 組件,NTag.razor;aspnetcore3.1 組件支持分部類,新增一個(gè) NTag.razor.cs;
NTag.razor.cs 就是標(biāo)準(zhǔn)的 c#類寫法
public partial class NTag< TModel> :AbstractNTag<NTag<TModel>,RenderArgs<NTag<TModel>,TModel>,TModel>{[Parameter]public TModel Model { get; set; }public RenderArgs<NTag<TModel>, TModel> Args(object arg=null){return new RenderArgs<NTag<TModel>, TModel>(this, this.Model, arg);}}重寫一下 NTag 的 ToString,方便測(cè)試
public override string ToString(){return $"{this.TagName}<{typeof(TModel).Name}>[{this.TagId},{Model}]";}NTag.razor
@typeparam TModel @inherits AbstractNTag<NTag<TModel>,RenderArgs<NTag<TModel>,TModel>,TModel>//保持和NTag.razor.cs一致@if (this.ChildContent == null){<div>@this.ToString()</div>//默認(rèn)輸出,用于測(cè)試}else{@this.ChildContent(this.Args());} @code {}簡(jiǎn)單測(cè)試一下, 數(shù)據(jù)就用項(xiàng)目模板自帶的 Data 打開項(xiàng)目根目錄,找到_Imports.razor,把 using 加進(jìn)去
@using xxxx.Data @using xxxx.Components新增 Razor 組件【Test.razor】
未打開的NTag,輸出NTag.ToString(): <NTag TModel="object" /> 打開的NTag: <NTag Model="TestData" Context="args" ><div>NTag內(nèi)容 @args.Model.Summary; </div> </NTag><NTag Model="@(new {Name="匿名對(duì)象" })" Context="args"><div>匿名Model,使用參數(shù)輸出【Name】屬性: @args.Model.Name</div> </NTag>@code{ WeatherForecast TestData = new WeatherForecast { TemperatureC = 222, Summary = "aaa" }; }轉(zhuǎn)到 Pages/Index.razor, 增加一行<Test />,F5 。
應(yīng)用級(jí)聯(lián)參數(shù) CascadingValue/CascadingParameter
我們的組件中 Theme 和 Parent 被標(biāo)記為【CascadingParameter】,因此需要通過 CascadingValue 把值傳遞過來。
首先,修改一下測(cè)試組件,使用嵌套 NTag,描述一個(gè)樹結(jié)構(gòu),Model 值指定為樹的 Level。
<NTag Model="0" TagId="root" Context="root"><div>root.Parent:@root.Tag.Parent </div><div>root Theme:@root.Tag.Theme</div><NTag TagId="t1" Model="1" Context="t1"><div>t1.Parent:@t1.Tag.Parent </div><div>t1 Theme:@t1.Tag.Theme</div><NTag TagId="t1_1" Model="2" Context="t1_1"><div>t1_1.Parent:@t1_1.Tag.Parent </div><div>t1_1 Theme:@t1_1.Tag.Theme </div><NTag TagId="t1_1_1" Model="3" Context="t1_1_1"><div>t1_1_1.Parent:@t1_1_1.Tag.Parent </div><div>t1_1_1 Theme:@t1_1_1.Tag.Theme </div></NTag><NTag TagId="t1_1_2" Model="3" Context="t1_1_2"><div>t1_1_2.Parent:@t1_1_2.Tag.Parent</div><div>t1_1_2 Theme:@t1_1_2.Tag.Theme </div></NTag></NTag></NTag></NTag>1、 Theme:Theme 的特點(diǎn)是共享,無論組件在什么位置,都應(yīng)該共享同一個(gè) Theme。這類場(chǎng)景,只需要簡(jiǎn)單的在組件外套一個(gè) CascadingValue。
<CascadingValue Value="Theme.Default"> <NTag TagId="root" ...... </CascadingValue>F5 跑起來,結(jié)果大致如下:
root.Parent:
<div>root Theme:Theme[blue]</div> <div>t1.Parent:</div> <div>t1 Theme:Theme[blue]</div> <div>t1_1.Parent:</div><div>t1_1 Theme:Theme[blue] </div><div>t1_1_1.Parent:</div><div>t1_1_1 Theme:Theme[blue] </div><div>t1_1_2.Parent:</div><div>t1_1_2 Theme:Theme[blue] </div>2、Parent:Parent 和 Theme 不同,我們希望他和我們組件的聲明結(jié)構(gòu)保持一致,這就需要我們?cè)诿總€(gè) NTag 內(nèi)部增加一個(gè) CascadingValue,直接寫在 Test 組件里過于啰嗦了,讓我們調(diào)整一下 NTag 代碼。打開 NTag.razor,修改一下,Test.razor 不動(dòng)。
<CascadingValue Value="this">@if (this.ChildContent == null){<div>@this.ToString()</div>//默認(rèn)輸出,用于測(cè)試}else{@this.ChildContent(this.Args());}</CascadingValue>看一下結(jié)果
root.Parent:
<div>root Theme:Theme[blue]</div> <div> t1.Parent:NTag`1[root,0] </div> <div>t1 Theme:Theme[blue]</div> <div> t1_1.Parent:NTag`1[t1,1] </div> <div> t1_1 Theme:Theme[blue] </div> <div> t1_1_1.Parent:NTag`1[t1_1,2] </div> <div> t1_1_1 Theme:Theme[blue] </div> <div> t1_1_2.Parent:NTag`1[t1_1,2]</div> <div> t1_1_2 Theme:Theme[blue] </div>CascadingValue/CascadingParameter 除了可以通過類型匹配之外還可以指定 Name。
呈現(xiàn) Model
到目前為止,我們的 NTag 主要在處理一些基本功能,比如隱式的父子關(guān)系、子內(nèi)容 ChildContent、參數(shù)、泛型。。接下來我們考慮如何把一個(gè) Model 呈現(xiàn)出來。
對(duì)于常見的 Model 對(duì)象來說,呈現(xiàn) Model 其實(shí)就是把 Model 上的屬性、字段。。。這些成員信息呈現(xiàn)出來,因此我們需要給 NTag 增加一點(diǎn)能力。
描述成員最直接的想法就是 lambda,model=>model.xxxx,此時(shí)我們只需要 Model 就足夠了;
UI 呈現(xiàn)時(shí)僅有成員還不夠,通常會(huì)有格式化需求,比如:{0:xxxx};或者帶有前后綴:"¥{xxxx}元整",甚至就是一個(gè)常量。。。。此類信息通常應(yīng)記錄在組件上,因此我們需要組件自身。
呈現(xiàn)時(shí)有時(shí)還會(huì)用到一些環(huán)境變量,比如序號(hào)/行號(hào)這種,因此需要引入一個(gè)參數(shù)。
以上需求可以很容易的推導(dǎo)出一個(gè)函數(shù)類型:Func<TTag, TModel,object,object> ;考慮 TTag 就是組件自身,這里可以簡(jiǎn)化一下:Func<TModel,object,object>。主要目的是從 model 上取值,兼顧格式化及環(huán)境變量處理,返回結(jié)果會(huì)直接用于頁面呈現(xiàn)輸出。
調(diào)整下 NTag 代碼,增加一個(gè)類型為 Func<TModel,TArg,object> 的 Getter 屬性,打上【Parameter】標(biāo)記。
[Parameter]public Func<TModel,object,object> Getter { get; set; }此處也可使用表達(dá)式(Expression<Func<TModel,object,object>>),需要增加一些處理。
呈現(xiàn)時(shí)通常還需要一些文字信息,比如 lable,text 之類, 支持一下;
UI 呈現(xiàn)的需求難以確定,通常還會(huì)有對(duì)狀態(tài)的處理, 這里提供一些輔助功能就可以。
一個(gè)小枚舉
public enum NVisibility{Default,Markup,Hidden}狀態(tài)屬性和 render 方法,NTag.razor.cs
[Parameter] public NVisibility TextVisibility { get; set; } = NVisibility.Default;[Parameter] public bool ShowContent { get; set; } = true;public RenderFragment RenderText(){if (TextVisibility == NVisibility.Hidden|| string.IsNullOrEmpty(this.Text)) return null;if (TextVisibility == NVisibility.Markup) return (b) => b.AddContent(0, (MarkupString)Text);return (b) => b.AddContent(0, Text);}public RenderFragment RenderContent(RenderArgs<NTag<TModel>, TModel> args){return this.ChildContent?.Invoke(args) ;}public RenderFragment RenderContent(object arg=null){return this.RenderContent(this.Args(arg));}NTag.razor
<CascadingValue Value="this">@RenderText()@if (this.ShowContent){var render = RenderContent();if (render == null){<div>@this</div>//測(cè)試用}else{@render//render 是個(gè)函數(shù),使用@才能輸出,如果不考慮測(cè)試代碼,可以直接 @RenderContent()}}</CascadingValue>Test.razor 增加測(cè)試代碼
7、呈現(xiàn)Model <br /> value:@@arg.Tag.Getter(arg.Model,null) <br /> <NTag Text="日期" Model="TestData" Getter="(m,arg)=>m.Date" Context="arg"><input type="datetime" value="@arg.Tag.Getter(arg.Model,null)" /> </NTag> <br /> Text中使用Markup:value:@@((DateTime)arg.Tag.Getter(arg.Model, null)) <br /> <label><NTag Text="<span style='color:red;'>日期</span>" TextVisibility="NVisibility.Markup" Model="TestData" Getter="(m,a)=>m.Date" Context="arg"><input type="datetime" value="@((DateTime)arg.Tag.Getter(arg.Model,null))" /></NTag> </label> <br /> 也可以直接使用childcontent:value:@@arg.Model.Date <div><NTag Model="TestData" Getter="(m,a)=>m.Date" Context="arg"><label> <span style='color:red;'>日期</span> <input type="datetime" value="@arg.Model.Date" /></label></NTag> </div> getter 格式化:@@((m,a)=>m.Date.ToString("yyyy-MM-dd")) <div><NTag Model="TestData" Getter="@((m,a)=>m.Date.ToString("yyyy-MM-dd"))" Context="arg"><label> <span style='color:red;'>日期</span> <input type="datetime" value="@arg.Tag.Getter(arg.Model,null)" /></label></NTag> </div> 使用customAttributes ,借助外部方法推斷TModel類型 <div><NTag type="datetime" Getter="@GetGetter(TestData,(m,a)=>m.Date)" Context="arg"><label> <span style='color:red;'>日期</span> <input @attributes="arg.Tag.CustomAttributes" value="@arg.Tag.Getter(arg.Model,null)" /></label></NTag> </div>@code {WeatherForecast TestData = new WeatherForecast { TemperatureC = 222, Date = DateTime.Now, Summary = "test summary" };Func<T, object, object> GetGetter<T>(T model, Func<T, object, object> func) {return (m, a) => func(model, a);} }考察一下測(cè)試代碼,我們發(fā)現(xiàn) 用作取值的?arg.Tag.Getter(arg.Model,null)?明顯有些啰嗦了,調(diào)整一下 RenderArgs,讓它可以直接取值。
public struct RenderArgs<TTag,TModel>{public TTag Tag;public TModel Model;public object Arg;Func<TModel, object, object> _valueGetter;public object Value => _valueGetter?.Invoke(Model, Arg);public RenderArgs(TTag tag, TModel model, object arg , Func<TModel, object, object> valueGetter=null) {this.Tag = tag;this.Model = model;this.Arg = arg;_valueGetter = valueGetter;}} //NTag.razor.cspublic RenderArgs<NTag<TModel>, TModel> Args(object arg = null){return new RenderArgs<NTag<TModel>, TModel>(this, this.Model, arg,this.Getter);}集合,Table 行列
集合的簡(jiǎn)單處理只需要循環(huán)一下。Test.razor
<ul>@foreach (var o in this.Datas){<NTag Model="o" Getter="(m,a)=>m.Summary" Context="arg"><li @key="o">@arg.Value</li></NTag>} </ul> @code {IEnumerable<WeatherForecast> Datas = Enumerable.Range(0, 10).Select(i => new WeatherForecast { Summary = i + "" });}復(fù)雜一點(diǎn)的時(shí)候,比如 Table,就需要使用列。
列有 header:可以使用 NTag.Text;
列要有單元格模板:NTag.ChildContent;
行就是所有列模板的呈現(xiàn)集合,行數(shù)據(jù)即是集合數(shù)據(jù)源的一項(xiàng)。
具體到 table 上,thead 定義列,tbody 生成行。
新增一個(gè)組件用于測(cè)試:TestTable.razor,試著用 NTag 呈現(xiàn)一個(gè) table。
<NTag TagId="table" TModel="WeatherForecast" Context="tbl"><table><thead><tr><NTag Text="<th>#</th>"TextVisibility="NVisibility.Markup"ShowContent="false"TModel="WeatherForecast"Getter="(m, a) =>a"Context="arg"><td>@arg.Value</td></NTag><NTag Text="<th>Summary</th>"TextVisibility="NVisibility.Markup"ShowContent="false"TModel="WeatherForecast"Getter="(m, a) => m.Summary"Context="arg"><td>@arg.Value</td></NTag><NTag Text="<th>Date</th>"TextVisibility="NVisibility.Markup"ShowContent="false"TModel="WeatherForecast"Getter="(m, a) => m.Date"Context="arg"><td>@arg.Value</td></NTag></tr></thead><tbody><CascadingValue Value="default(object)">@{ var cols = tbl.Tag.Children;var i = 0;tbl.Tag.ConsoleLog(cols.Count());}@foreach (var o in Source){<tr @key="o">@foreach (var col in cols){if (col is NTag<WeatherForecast> tag){@tag.RenderContent(tag.Args(o,i ))}}</tr>i++;}</CascadingValue></tbody></table> </NTag>@code {IEnumerable<WeatherForecast> Source = Enumerable.Range(0, 10).Select(i => new WeatherForecast { Date=DateTime.Now,Summary=$"data_{i}", TemperatureC=i });}服務(wù)端模板處理時(shí),代碼會(huì)先于輸出執(zhí)行,直觀的說,就是組件在執(zhí)行時(shí)會(huì)有層級(jí)順序。所以我們?cè)?tbody 中增加了一個(gè) CascadingValue,推遲一下代碼的執(zhí)行時(shí)機(jī)。否則,tbl.Tag.Children會(huì)為空。
thead 中的 NTag 作為列定義使用,與最外的 NTag(table)正好形成父子關(guān)系。
觀察下 NTag,我們發(fā)現(xiàn)有些定義重復(fù)了,比如 TModel,單元格<td>@arg.Value</td>。下面試著簡(jiǎn)化一些。
之前測(cè)試 Model 呈現(xiàn)的代碼中我們說到可以 “借助外部方法推斷 TModel 類型”,當(dāng)時(shí)使用了一個(gè) GetGetter 方法,讓我們?cè)囍?RenderArg 中增加一個(gè)類似方法。
RenderArgs.cs:
public Func<TModel, object, object> GetGetter(Func<TModel, object, object> func) => func;GetGetter 極簡(jiǎn)單,不需要任何邏輯,直接返回參數(shù)。原理是 RenderArgs 可用時(shí),TModel 必然是確定的。
用法:
<NTag Text="<th>#<th>"TextVisibility="NVisibility.Markup"ShowContent="false"Getter="(m, a) =>a"Context="arg"><td>@arg.Value</td>作為列的 NTag,每列的 ChildContent 其實(shí)是一樣的,變化的只有 RenderArgs,因此只需要定義一個(gè)就足夠了。
NTag.razor.cs 增加一個(gè)方法,對(duì)于 ChildContent 為 null 的組件我們使用一個(gè)默認(rèn)組件來 render。
public RenderFragment RenderChildren(TModel model, object arg=null){return (builder) =>{var children = this.Children.OfType<NTag<TModel>>();NTag<TModel> defaultTag = null;foreach (var child in children){if (defaultTag == null && child.ChildContent != null) defaultTag = child;var render = (child.ChildContent == null ? defaultTag : child);render.RenderContent(child.Args(model, arg))(builder);}};}TestTable.razor
<NTag TagId="table" TModel="WeatherForecast" Context="tbl"><table><thead><tr><NTag Text="<th >#</th>"TextVisibility="NVisibility.Markup"ShowContent="false"Getter="tbl.GetGetter((m,a)=>a)"Context="arg"><td>@arg.Value</td></NTag><NTag Text="<th>Summary</th>"TextVisibility="NVisibility.Markup"ShowContent="false"Getter="tbl.GetGetter((m, a) => m.Summary)"/><NTag Text="<th>Date</th>"TextVisibility="NVisibility.Markup"ShowContent="false"Getter="tbl.GetGetter((m, a) => m.Date)"/></tr></thead><tbody><CascadingValue Value="default(object)">@{var i = 0;foreach (var o in Source){<tr @key="o">@tbl.Tag.RenderChildren(o, i++)</tr>}}</CascadingValue></tbody></table> </NTag>結(jié)束
文中通過 NTag 演示一些組件開發(fā)常用技術(shù),因此功能略多了些。
TArgs 可以視作 js 組件中的 option.
總結(jié)
以上是生活随笔為你收集整理的[Asp.net core 3.1] 通过一个小组件熟悉Blazor服务端组件开发的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .Net Core使用Ocelot网关(
- 下一篇: 你知道怎么使用DebugView查看调试