通過一個小組件,熟悉 Blazor 服務端組件開發。github
vs2019 16.4, asp.net core 3.1 新建 Blazor 應用,選擇 asp.net core 3.1。 根文件夾下新增目錄 Components,放置代碼。
Components 目錄下新建一個接口文件(interface)當作文檔,加個 using using Microsoft.AspNetCore.Components;。
先從直觀的方面入手。
考慮一下功能方面。
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; }
IDictionaryCustomAttributes { get; set; }
}
IHierarchyComponent:
public interface IHierarchyComponent:IDisposable
{
IComponent Parent { get; set; }
IEnumerableChildren { get;}
void AddChild(IComponent child);
void RemoveChild(IComponent child);
}
ITheme
public interface ITheme
{
string GetClass(TComponent component);
}
組件的基本信息 INTag 有了,需要的話可以支持層級關係 IHierarchyComponent,可以考慮下一些特定功能的處理及類型部分。
INTag[TTag, TArgs, TModel ]
public interface INTag:INTag
where TTag: INTag
{
///
/// 標籤對之間的內容,為參數,ChildContent 為Blazor約定名。
///
RenderFragmentChildContent { get; set; }
}
回顧一下我們的幾個接口。
Components 目錄下新增 一個 c#類,AbstractNTag.cs, using Microsoft.AspNetCore.Components; 藉助 Blazor 提供的 ComponentBase,實現接口。
public abstract class AbstractNTag: ComponentBase, IHierarchyComponent, INTag
where TTag: AbstractNTag{
}
調整一下 vs 生成的代碼, IHierarchyComponent 使用欄位實現一下。
Children:
List_children = new List ();
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;
}
增加對瀏覽器 console.log 的支持, razor Attribute...,完整的 AbstractNTag.cs
public abstract class AbstractNTag: ComponentBase, IHierarchyComponent, INTag
where TTag: AbstractNTag
{
List_children = new List ();
IComponent _parent;
public string TagName => typeof(TTag).Name;
[Inject]public IJSRuntime JSRuntime { get; set; }
[Parameter]public RenderFragmentChildContent { get; set; }
[Parameter] public string TagId { get; set; }
[Parameter]public string Class { get; set; }
[Parameter]public string Style { get; set; }
[Parameter(CaptureUnmatchedValues =true)]public IDictionaryCustomAttributes { 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 IEnumerableChildren { 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;
}
}
泛型其實就是定義在類型上的函數,TTag,TArgs,TModel 就是 入參,得到的類型就是返回值。因此處理泛型定義的過程,就很類似函數逐漸消參的過程。比如:
func(a,b,c)
確定a之後,func(b,c)=>func(1,b,c);
確定b之後,func(c)=>func(1,2,c);
最終: func()=>func(1,2,3);
執行 func 可以得到一個明確的結果。
同樣的,我們繼承 NTag 基類時需要考慮各個泛型參數應該是什麼:
public struct RenderArgs
{
public TTag Tag;
public TModel Model;
public object Arg;
public RenderArgs(TTag tag, TModel model, object arg ) {
this.Tag = tag;
this.Model = model;
this.Arg = arg;
}
}
Components 目錄下新增 Razor 組件,NTag.razor;aspnetcore3.1 組件支持分部類,新增一個 NTag.razor.cs;
NTag.razor.cs 就是標準的 c#類寫法
public partial class NTag< TModel> :AbstractNTag,RenderArgs ,TModel>,TModel>
{
[Parameter]public TModel Model { get; set; }
public RenderArgs, TModel> Args(object arg=null)
{
return new RenderArgs, TModel>(this, this.Model, arg);
}
}
重寫一下 NTag 的 ToString,方便測試
public override string ToString()
{
return $"{this.TagName}<{typeof(TModel).Name}>[{this.TagId},{Model}]";
}
NTag.razor
@typeparam TModel
@inherits AbstractNTag,RenderArgs ,TModel>,TModel>//保持和NTag.razor.cs一致
@if (this.ChildContent == null)
{
@this.ToString()//默認輸出,用於測試
}
else
{
@this.ChildContent(this.Args());
}
@code {
}
簡單測試一下, 數據就用項目模板自帶的 Data 打開項目根目錄,找到_Imports.razor,把 using 加進去
@using xxxx.Data
@using xxxx.Components
新增 Razor 組件【Test.razor】
未打開的NTag,輸出NTag.ToString():
打開的NTag:
NTag內容 @args.Model.Summary;
匿名Model,使用參數輸出【Name】屬性: @args.Model.Name
@code{
WeatherForecast TestData = new WeatherForecast { TemperatureC = 222, Summary = "aaa" };
}
轉到 Pages/Index.razor, 增加一行
我們的組件中 Theme 和 Parent 被標記為【CascadingParameter】,因此需要通過 CascadingValue 把值傳遞過來。
首先,修改一下測試組件,使用嵌套 NTag,描述一個樹結構,Model 值指定為樹的 Level。
root.Parent:@root.Tag.Parent
root Theme:@root.Tag.Theme
t1.Parent:@t1.Tag.Parent
t1 Theme:@t1.Tag.Theme
t1_1.Parent:@t1_1.Tag.Parent
t1_1 Theme:@t1_1.Tag.Theme
t1_1_1.Parent:@t1_1_1.Tag.Parent
t1_1_1 Theme:@t1_1_1.Tag.Theme
t1_1_2.Parent:@t1_1_2.Tag.Parent
t1_1_2 Theme:@t1_1_2.Tag.Theme
1、 Theme:Theme 的特點是共享,無論組件在什麼位置,都應該共享同一個 Theme。這類場景,只需要簡單的在組件外套一個 CascadingValue。
F5 跑起來,結果大致如下:
root.Parent:
root Theme:Theme[blue]
t1.Parent:
t1 Theme:Theme[blue]
t1_1.Parent:
t1_1 Theme:Theme[blue]
t1_1_1.Parent:
t1_1_1 Theme:Theme[blue]
t1_1_2.Parent:
t1_1_2 Theme:Theme[blue]
2、Parent:Parent 和 Theme 不同,我們希望他和我們組件的聲明結構保持一致,這就需要我們在每個 NTag 內部增加一個 CascadingValue,直接寫在 Test 組件里過於囉嗦了,讓我們調整一下 NTag 代碼。打開 NTag.razor,修改一下,Test.razor 不動。
@if (this.ChildContent == null)
{
@this.ToString()//默認輸出,用於測試
}
else
{
@this.ChildContent(this.Args());
}
看一下結果
root.Parent:
root Theme:Theme[blue]
t1.Parent:NTag`1[root,0]
t1 Theme:Theme[blue]
t1_1.Parent:NTag`1[t1,1]
t1_1 Theme:Theme[blue]
t1_1_1.Parent:NTag`1[t1_1,2]
t1_1_1 Theme:Theme[blue]
t1_1_2.Parent:NTag`1[t1_1,2]
t1_1_2 Theme:Theme[blue]
到目前為止,我們的 NTag 主要在處理一些基本功能,比如隱式的父子關係、子內容 ChildContent、參數、泛型。。接下來我們考慮如何把一個 Model 呈現出來。
對於常見的 Model 對象來說,呈現 Model 其實就是把 Model 上的屬性、欄位。。。這些成員信息呈現出來,因此我們需要給 NTag 增加一點能力。
調整下 NTag 代碼,增加一個類型為 Func
[Parameter]public FuncGetter { get; set; }
[Parameter] public string Text { get; set; }
一個小枚舉
public enum NVisibility
{
Default,
Markup,
Hidden
}
狀態屬性和 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, TModel> args)
{
return this.ChildContent?.Invoke(args) ;
}
public RenderFragment RenderContent(object arg=null)
{
return this.RenderContent(this.Args(arg));
}
NTag.razor
@RenderText()
@if (this.ShowContent)
{
var render = RenderContent();
if (render == null)
{
@this//測試用
}
else
{
@render//render 是個函數,使用@才能輸出,如果不考慮測試代碼,可以直接 @RenderContent()
}
}
Test.razor 增加測試代碼
7、呈現Model
value:@@arg.Tag.Getter(arg.Model,null)
Text中使用Markup:value:@@((DateTime)arg.Tag.Getter(arg.Model, null))
也可以直接使用childcontent:value:@@arg.Model.Date
getter 格式化:@@((m,a)=>m.Date.ToString("yyyy-MM-dd"))
使用customAttributes ,藉助外部方法推斷TModel類型
@code {
WeatherForecast TestData = new WeatherForecast { TemperatureC = 222, Date = DateTime.Now, Summary = "test summary" };
FuncGetGetter (T model, Func func) {
return (m, a) => func(model, a);
}
}
考察一下測試代碼,我們發現 用作取值的 arg.Tag.Getter(arg.Model,null) 明顯有些囉嗦了,調整一下 RenderArgs,讓它可以直接取值。
public struct RenderArgs
{
public TTag Tag;
public TModel Model;
public object Arg;
Func_valueGetter;
public object Value => _valueGetter?.Invoke(Model, Arg);
public RenderArgs(TTag tag, TModel model, object arg , FuncvalueGetter=null) {
this.Tag = tag;
this.Model = model;
this.Arg = arg;
_valueGetter = valueGetter;
}
}
//NTag.razor.cs
public RenderArgs, TModel> Args(object arg = null)
{
return new RenderArgs, TModel>(this, this.Model, arg,this.Getter);
}
集合的簡單處理只需要循環一下。Test.razor
複雜一點的時候,比如 Table,就需要使用列。
新增一個組件用於測試:TestTable.razor,試著用 NTag 呈現一個 table。
TextVisibility="NVisibility.Markup"
ShowContent="false"
TModel="WeatherForecast"
Getter="(m, a) =>a"
Context="arg">
@arg.Value
TextVisibility="NVisibility.Markup"
ShowContent="false"
TModel="WeatherForecast"
Getter="(m, a) => m.Summary"
Context="arg">
@arg.Value
TextVisibility="NVisibility.Markup"
ShowContent="false"
TModel="WeatherForecast"
Getter="(m, a) => m.Date"
Context="arg">
@arg.Value
@{ var cols = tbl.Tag.Children;
var i = 0;
tbl.Tag.ConsoleLog(cols.Count());
}
@foreach (var o in Source)
{
@foreach (var col in cols)
{
if (col is NTagtag)
{
@tag.RenderContent(tag.Args(o,i ))
}
}
i++;
}
@code {
IEnumerableSource = Enumerable.Range(0, 10)
.Select(i => new WeatherForecast { Date=DateTime.Now,Summary=$"data_{i}", TemperatureC=i });
}
之前測試 Model 呈現的代碼中我們說到可以 「藉助外部方法推斷 TModel 類型」,當時使用了一個 GetGetter 方法,讓我們試著在 RenderArg 中增加一個類似方法。
RenderArgs.cs:
public FuncGetGetter(Func func) => func;
用法:
TextVisibility="NVisibility.Markup"
ShowContent="false"
Getter="(m, a) =>a"
Context="arg">
@arg.Value
作為列的 NTag,每列的 ChildContent 其實是一樣的,變化的只有 RenderArgs,因此只需要定義一個就足夠了。
NTag.razor.cs 增加一個方法,對於 ChildContent 為 null 的組件我們使用一個默認組件來 render。
public RenderFragment RenderChildren(TModel model, object arg=null)
{
return (builder) =>
{
var children = this.Children.OfType>();
NTagdefaultTag = 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
TextVisibility="NVisibility.Markup"
ShowContent="false"
Getter="tbl.GetGetter((m,a)=>a)"
Context="arg">
@arg.Value
TextVisibility="NVisibility.Markup"
ShowContent="false"
Getter="tbl.GetGetter((m, a) => m.Summary)"/>
TextVisibility="NVisibility.Markup"
ShowContent="false"
Getter="tbl.GetGetter((m, a) => m.Date)"
/>
@{
var i = 0;
foreach (var o in Source)
{
@tbl.Tag.RenderChildren(o, i++)
}
}
原文地址:https://www.cnblogs.com/cerl/p/12030355.html