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

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

2020-09-19 18:54:32 .NET開發

通過一個小組件,熟悉 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 = https://www.cnblogs.com/cerl/p/null;
            return CustomAttributes?.TryGetValue(key, out value) ?? false;
        }
        public IEnumerable 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=https://www.cnblogs.com/cerl/p/"xxx" >... ...,捕獲 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 = https://www.cnblogs.com/cerl/p/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=https://www.cnblogs.com/cerl/p/"Theme.Default">


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 不動,

  <CascadingValue Value=https://www.cnblogs.com/cerl/p/"this">
        @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]
  • 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=https://www.cnblogs.com/cerl/p/"this">
        @RenderText()
        @if (this.ShowContent)
        {
            var render = RenderContent();
            if (render == null)
            {
                
@this
//測驗用 } else { @render//render 是個函式,使用@才能輸出,如果不考慮測驗代碼,可以直接 @RenderContent() } }

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=https://www.cnblogs.com/cerl/p/"@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" }; Func GetGetter(T model, Func 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 =https://www.cnblogs.com/cerl/p/> _valueGetter?.Invoke(Model, Arg);
        public RenderArgs(TTag tag, TModel model, object arg  , Func valueGetter=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);
        }

集合,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=https://www.cnblogs.com/cerl/p/"default(object)">
                @{ 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 NTag tag)
                            {
                                @tag.RenderContent(tag.Args(o,i ))
                            }
                        }
                    
                    i++;
                }
            

        
    


@code {

    IEnumerable 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=https://www.cnblogs.com/cerl/p/"default(object)">
                @{
                    var i = 0;
                    foreach (var o in Source)
                    {
                    
                        @tbl.Tag.RenderChildren(o, i++)
                    

                    }
                    }
            

        
    

結束

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

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/83041.html

標籤:.NET Core

上一篇:.NET Core 3終結點不能映射控制器

下一篇:ASP.NET Core 選項模式原始碼學習Options IOptions(二)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more