[Asp.net core 3.1] 通過一個小組件熟悉Blazor服務端組件開發

来源:https://www.cnblogs.com/cerl/archive/2019/12/12/12030355.html
-Advertisement-
Play Games

通過一個小組件,熟悉 Blazor 服務端組件開發。 "github" 一、環境搭建 vs2019 16.4, asp.net core 3.1 新建 Blazor 應用,選擇 asp.net core 3.1。 根文件夾下新增目錄 Components,放置代碼。 二、組件需求定義 Compone ...


通過一個小組件,熟悉 Blazor 服務端組件開發。github

一、環境搭建

vs2019 16.4, asp.net core 3.1 新建 Blazor 應用,選擇 asp.net core 3.1。 根文件夾下新增目錄 Components,放置代碼。

二、組件需求定義

Components 目錄下新建一個介面文件(interface)當作文檔,加個 using using Microsoft.AspNetCore.Components;

先從直觀的方面入手。

  • 類似 html 標簽對的組件,樣子類似<xxx propA="aaa" data-propB="123" ...>其他標簽或內容...</xxx><xxx .../>。介面名:INTag.
  • 需要 Id 和名稱,方便區分和調試。string TagId{get;set;} string TagName{get;set;}.
  • 需要樣式支持。加上string Class{get;set;} string Style{get;set;}
  • 不常用的屬性也提供支持,使用字典。IDictionary<string,object> CustomAttributes { get; set; }
  • 應該提供 js 支持。加上using Microsoft.JSInterop; 屬性 IJSRuntime JSRuntime{get;set;}

考慮一下功能方面。

  • 既然是標簽對,那就有可能會嵌套,就會產生層級關係或父子關係。因為只是可能,所以我們新建一個介面,用來提供層級關係處理,IHierarchyComponent。
  • 需要一個 Parent ,類型就定為 Microsoft.AspNetCore.Components.IComponent.IComponent Parent { get; set; }.
  • 要能添加子控制項,void AddChild(IComponent child);,有加就有減,void RemoveChild(IComponent child);
  • 提供一個集合方便遍歷,我們已經提供了 Add/Remove,讓它只讀就好。 IEnumerable<IComponent> Children { get;}
  • 一旦有了 Children 集合,我們就需要考慮什麼時候從集合里移除組件,讓 IHierarchyComponent 實現 IDisposable,保證組件被釋放時解開父子/層級關係。
  • 組件需要處理樣式,僅有 Class 和 Style 可能不夠,通常還會需要 Skin、Theme 處理,增加一個介面記錄一下, public interface ITheme{ string GetClass<TComponent>(TComponent component); }。INTag 增加一個屬性 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 有了,需要的話可以支持層級關係 IHierarchyComponent,可以考慮下一些特定功能的處理及類型部分。

  • Blazor 組件實現類似 <xxx>....</xxx>這種可打開的標簽對,需要提供一個 RenderFragment 或 RenderFragment<TArgs>屬性。RenderFragment 是一個委托函數,帶參的明顯更靈活些,但是參數類型不好確定,不好確定的類型用泛型。再加一個介面,INTag< TArgs >:INTag, 一個屬性 RenderFragment<TArgs> ChildContent { get; set; }.
  • 組件的主要目的是為了呈現我們的數據,也就是一般說的 xxxModel,Data....,類型不確定,那就加一個泛型。INTag< TArgs ,TModel>:INTag.
  • RenderFragment 是一個函數,ChildContent 是一個函數屬性,不是方法。在方法內,我們可以使用 this 來訪問組件自身引用,但是函數內部其實是沒有 this 的。為了更好的使用組件自身,這裡增加一個泛型用於指代自身,public interface INTag<TTag, TArgs, TModel>:INTag where TTag: INTag<TTag, TArgs, TModel>

INTag[TTag, TArgs, TModel ]

 public interface INTag<TTag, TArgs, TModel>:INTag
        where TTag: INTag<TTag, TArgs, TModel>
    {
        /// <summary>
        /// 標簽對之間的內容,<see cref="TArgs"/> 為參數,ChildContent 為Blazor約定名。
        /// </summary>
        RenderFragment<TArgs> ChildContent { get; set; }
    }

回顧一下我們的幾個介面。

  • INTag:描述了組件的基本信息,即組件的樣子。
  • IHierarchyComponent 提供了層級處理能力,屬於組件的擴展能力。
  • ITheme 提供了 Theme 接入能力,也屬於組件的擴展能力。
  • INTag<TTag, TArgs, TModel> 提供了打開組件的能力,ChildContent 像一個動態模板一樣,讓我們可以在聲明組件時自行決定組件的部分內容和結構。
  • 所有這些介面最主要的目的其實是為了產生一個合適的 TArgs, 去調用 ChildContent。
  • 有描述,有能力還有了主要目的,我們就可以去實現 NTag 組件。

三、組件實現

抽象基類 AbstractNTag

Components 目錄下新增 一個 c#類,AbstractNTag.cs, using Microsoft.AspNetCore.Components; 藉助 Blazor 提供的 ComponentBase,實現介面。

public    abstract class AbstractNTag<TTag, TArgs, TModel> : ComponentBase, IHierarchyComponent, INTag<TTag, TArgs, TModel>
   where TTag: AbstractNTag<TTag, TArgs, TModel>{

}

調整一下 vs 生成的代碼, IHierarchyComponent 使用欄位實現一下。

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;
        }

增加對瀏覽器 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) 支持聲明時將組件上沒定義的屬性打包賦值;
  • CascadingParameter 配合 Blazor 內置組件 <CascadingValue Value="xxx" >... <NTag /> ...</CascadingValue>,捕獲 Value。處理過程和級聯樣式表(css)很類似。

具體類 NTag

泛型其實就是定義在類型上的函數,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 基類時需要考慮各個泛型參數應該是什麼:

  • TTag:這個很容易確定,誰繼承了基類就是誰。
  • TModel: 這個不到最後使用我們是無法確定的,需要保留。
  • TArgs: 前面說過,組件的主要目的是為了給 ChildContent 提供參數.從這一目的出發,TTag 和 TModel 的用途之一就是給TArgs提供類型支持,或者說 TArgs 應該包含 TTag 和 TModel。又因為 ChildContent 只有一個參數,因此 TArgs 應該有一定的擴展性,不妨給他一個屬性做擴展。 綜合一下,TArgs 的大概模樣就有了,來個 struct。
public struct RenderArgs<TTag,TModel>
    {
        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;

        }
    }
  • RenderArgs 屬於常用輔助類型,因此不需要給 TArgs 指定約束。

Components 目錄下新增 Razor 組件,NTag.razor;aspnetcore3.1 組件支持分部類,新增一個 NTag.razor.cs;

NTag.razor.cs 就是標準的 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,方便測試

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>//預設輸出,用於測試
        }
        else
        {
            @this.ChildContent(this.Args());
        }
@code {

}

簡單測試一下, 數據就用項目模板自帶的 Data 打開項目根目錄,找到_Imports.razor,把 using 加進去

@using xxxx.Data
@using xxxx.Components

新增 Razor 組件【Test.razor】

未打開的NTag,輸出NTag.ToString():
<NTag TModel="object" />
打開的NTag:
<NTag Model="TestData" Context="args" >
        <div>NTag內容 @args.Model.Summary; </div>
</NTag>

<NTag Model="@(new {Name="匿名對象" })" Context="args">
    <div>匿名Model,使用參數輸出【Name】屬性: @args.Model.Name</div>
</NTag>

@code{
WeatherForecast TestData = new WeatherForecast { TemperatureC = 222, Summary = "aaa" };
}

轉到 Pages/Index.razor, 增加一行<Test />,F5 。

應用級聯參數 CascadingValue/CascadingParameter

我們的組件中 Theme 和 Parent 被標記為【CascadingParameter】,因此需要通過 CascadingValue 把值傳遞過來。

首先,修改一下測試組件,使用嵌套 NTag,描述一個樹結構,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 的特點是共用,無論組件在什麼位置,都應該共用同一個 Theme。這類場景,只需要簡單的在組件外套一個 CascadingValue。

<CascadingValue Value="Theme.Default">
<NTag  TagId="root" ......
</CascadingValue>

F5 跑起來,結果大致如下:

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 不同,我們希望他和我們組件的聲明結構保持一致,這就需要我們在每個 NTag 內部增加一個 CascadingValue,直接寫在 Test 組件里過於啰嗦了,讓我們調整一下 NTag 代碼。打開 NTag.razor,修改一下,Test.razor 不動。

  <CascadingValue Value="this">
        @if (this.ChildContent == null)
        {
            <div>@this.ToString()</div>//預設輸出,用於測試
        }
        else
        {
            @this.ChildContent(this.Args());
        }
     </CascadingValue>

看一下結果

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。

呈現 Model

到目前為止,我們的 NTag 主要在處理一些基本功能,比如隱式的父子關係、子內容 ChildContent、參數、泛型。。接下來我們考慮如何把一個 Model 呈現出來。

對於常見的 Model 對象來說,呈現 Model 其實就是把 Model 上的屬性、欄位。。。這些成員信息呈現出來,因此我們需要給 NTag 增加一點能力。

  • 描述成員最直接的想法就是 lambda,model=>model.xxxx,此時我們只需要 Model 就足夠了;
  • UI 呈現時僅有成員還不夠,通常會有格式化需求,比如:{0:xxxx}; 或者帶有前尾碼: "¥{xxxx}元整",甚至就是一個常量。。。。此類信息通常應記錄在組件上,因此我們需要組件自身。
  • 呈現時有時還會用到一些環境變數,比如序號/行號這種,因此需要引入一個參數。
  • 以上需求可以很容易的推導出一個函數類型:Func<TTag, TModel,object,object> ;考慮 TTag 就是組件自身,這裡可以簡化一下:Func<TModel,object,object>。 主要目的是從 model 上取值,兼顧格式化及環境變數處理,返回結果會直接用於頁面呈現輸出。

調整下 NTag 代碼,增加一個類型為 Func<TModel,TArg,object> 的 Getter 屬性,打上【Parameter】標記。

[Parameter]public Func<TModel,object,object> Getter { get; set; }
  • 此處也可使用表達式(Expression<Func<TModel,object,object>>),需要增加一些處理。
  • 呈現時通常還需要一些文字信息,比如 lable,text 之類, 支持一下;
  [Parameter] public string Text { get; set; }
  • UI 呈現的需求難以確定,通常還會有對狀態的處理, 這裡提供一些輔助功能就可以。

一個小枚舉

   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<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>//測試用
            }
            else
            {
                @render//render 是個函數,使用@才能輸出,如果不考慮測試代碼,可以直接 @RenderContent()
            }

        }
    </CascadingValue>

Test.razor 增加測試代碼

7、呈現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);
    }
}

考察一下測試代碼,我們發現 用作取值的 arg.Tag.Getter(arg.Model,null) 明顯有些啰嗦了,調整一下 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.cs
 public RenderArgs<NTag<TModel>, TModel> Args(object arg = null)
        {

            return new RenderArgs<NTag<TModel>, TModel>(this, this.Model, arg,this.Getter);
        }

集合,Table 行列

集合的簡單處理只需要迴圈一下。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 + "" });

}

複雜一點的時候,比如 Table,就需要使用列。

  • 列有 header:可以使用 NTag.Text;
  • 列要有單元格模板:NTag.ChildContent;
  • 行就是所有列模板的呈現集合,行數據即是集合數據源的一項。
  • 具體到 table 上,thead 定義列,tbody 生成行。

新增一個組件用於測試:TestTable.razor,試著用 NTag 呈現一個 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 });

}
  • 服務端模板處理時,代碼會先於輸出執行,直觀的說,就是組件在執行時會有層級順序。所以我們在 tbody 中增加了一個 CascadingValue,推遲一下代碼的執行時機。否則,tbl.Tag.Children會為空。
  • thead 中的 NTag 作為列定義使用,與最外的 NTag(table)正好形成父子關係。
  • 觀察下 NTag,我們發現有些定義重覆了,比如 TModel,單元格<td>@arg.Value</td>。下麵試著簡化一些。

之前測試 Model 呈現的代碼中我們說到可以 “藉助外部方法推斷 TModel 類型”,當時使用了一個 GetGetter 方法,讓我們試著在 RenderArg 中增加一個類似方法。

RenderArgs.cs:

public Func<TModel, object, object> GetGetter(Func<TModel, object, object> func) => func;
  • GetGetter 極簡單,不需要任何邏輯,直接返回參數。原理是 RenderArgs 可用時,TModel 必然是確定的。

用法:

<NTag Text="<th>#<th>"
                      TextVisibility="NVisibility.Markup"
                      ShowContent="false"
                      Getter="(m, a) =>a"
                      Context="arg">
                    <td>@arg.Value</td>

作為列的 NTag,每列的 ChildContent 其實是一樣的,變化的只有 RenderArgs,因此只需要定義一個就足夠了。

NTag.razor.cs 增加一個方法,對於 ChildContent 為 null 的組件我們使用一個預設組件來 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>

結束

  • 文中通過 NTag 演示一些組件開發常用技術,因此功能略多了些。
  • TArgs 可以視作 js 組件中的 option.

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • @[TOC] Mybatis整合spring其實就是SSM框架中SM的整合集成。 1.整合思路 整合的思路其實就是Mybatis整合spring的核心 1、SqlSessionFactory對象應該放到spring容器中 作為單例存在 ,spring預設是單例的。 2、傳統dao的開發方式中,應該從 ...
  • 1.在測試一個按照時間的範圍查詢時,儘管增加了索引,發現使用不到索引,可以使用這個來強制使用索引 測試過程為,創建下麵的表,以及創建了聯合索引 create table delay_delete_users( id int auto_increment, email_id int not null ...
  • 什麼是mock測試? 在測試過程中,對於某些不容易構成或者不容易獲取的對象,用一個虛擬的對象來創建以便測試的測試方法,就是Mock測試。 Servlet、Request、Response等Servlet API相關對象本來就是由Servlet容器(Tomcat)創建的。 這個虛擬的對象就是Mock對 ...
  • 恢復內容開始 1.type()是檢驗一個變數的類型。int是數字型變數,str是字元型變數 name = "yao" Year = 1993 print( name + str(Year))>>yao1993 2.ord() 是一個內建函數,能夠返回某個字元, chr() 是根據整數值得到相應的字元 ...
  • 分享優銳課學習筆記~來看一下如何使用Spring Boot創建Windows服務以及通過配置詳細信息來快速啟動並運行。 最近不得不將Spring Boot應用程式部署為Windows服務,感到驚訝的是使用winsw如此容易。 入門 Spring Boot文檔的第59節是關於安裝Spring Boot ...
  • 1.json介紹 json與xml相比, 對數據的描述性比XML較差,但是數據體積小,傳遞速度更快. json數據的書寫格式是"名稱:值對",比如: "Name" : "John" //name為名稱,值對為"john"字元串 值對類型共分為: 數字(整數或浮點數) 字元串(在雙引號中) 邏輯值(t ...
  • 內網穿透 有時候,我們在外想要訪問家裡主機的資料,要麼由於主機處於家庭路由器下,是非公網IP,要麼就是運營商隨機分配的一個公網IP,都很難直接連上主機獲取資料。那麼,有什麼辦法可以解決這一難題?答案就是 內網穿透。 內網穿透,又叫NAT(Network Address Translation)穿透, ...
  • HTTP狀態碼 HTTP狀態碼會告訴API的消費者以下事情: 請求是否執行成功了 如果請求失敗了,那麼誰為它負責 HTTP的狀態碼有很多,但是Web API不一定需要支持所有的狀態碼。HTTP狀態碼一共分為5個級別: 1xx,屬於信息性的狀態碼。Web API並不使用1xx的狀態碼。 2xx,意味著 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...