模板化 Blazor 组件

Posted

技术标签:

【中文标题】模板化 Blazor 组件【英文标题】:Templating Blazor Components 【发布时间】:2020-08-04 08:13:29 【问题描述】:

只是想用一个模板化的 blazor 组件来做一个 CRUD 风格的单页应用程序,它可以传入一个特定的对象,这样我就不必一遍又一遍地编写相同的样板代码。

例如,下面的部分可以通过使用 RenderFragment 对象进行模板化:

@typeparam TItem

<div>
    @if (AddObjectTemplate != null)
    
        @AddObjectTemplate
    
    else
    
        <div style="float:left">
            <button class="btn btn-primary" @onclick="AddObject">Add Object</button>
        </div>
    
</div>

@code 
    [Parameter]
    public RenderFragment AddObjectTemplate  get; set; 

    [Parameter]
    public IList<TItem> Items  get; set; 


但我可能想要这样的东西:

<button class="btn btn-default" @onclick="@(() => EditObject(item.Id))">Edit</button>

protected void EditObject(int id)

    TItem cust = _itemServices.Details(id);

问题是上面对 EditObject(item.Id) 的调用目前无法解析到特定对象,因为它不知道 TItem 是什么。有没有办法在模板组件中使用每个对象必须实现的特定接口,或者有其他方法吗?

我们的想法是让 AddObject、EditObject、DeleteObject 等基本上做同样的事情,但使用不同类型的对象。

【问题讨论】:

【参考方案1】:

由于您将IList&lt;TItem&gt; 作为参数,因此列表存在于该组件之外的组件结构的另一个级别。因此,您最好对 Add、Edit 和 Delete 方法使用 EventCallBack&lt;T&gt; 属性,并在连接组件时设置实际方法。这使您的模板组件仅成为渲染对象,并且您将要完成的真正“工作”保持在需要完成工作的实际列表附近。

当你设置你的模板组件时,你可能会尝试这样的事情,我已经得到了很好的结果。

Templator.razor

@typeparam TItem

<h3>Templator</h3>

@foreach (var item in Items)


    @ItemTemplate(item)

    <button @onclick="@(() => EditItemCallBack.InvokeAsync(item))">Edit Item</button>


@code 

    [Parameter]
    public IList<TItem> Items  get; set; 

    [Parameter]
    public EventCallback<TItem> EditItemCallBack  get; set; 

    [Parameter]
    public RenderFragment<TItem> ItemTemplate

Container.Razor

<h3>Container</h3>

<Templator TItem="Customer" Items="Customers" EditItemCallBack="@EditCustomer">

    <ItemTemplate Context="Cust">
        <div>@Cust.Name</div>
    </ItemTemplate>

</Templator>

@code 

    public List<Customer> Customers  get; set; 

    void EditCustomer(Customer customer)
    
        var customerId = customer.Id;

        //Do something here to update the customer

    


Customer.cs

public class Customer

    public int Id  get; set; 

    public string Name  get; set; 


这里的重点如下:

    实际的根列表位于模板化组件之外,处理该列表的方法也是如此,因此上述所有内容都处于相同的抽象级别和相同的代码块。

    模板组件接收一个列表、一个用于指定列表项的类型的类型,以及在每个项目上执行的方法回调。 (或一系列方法,您可以使用相同的方法添加您认为合适的“添加”和“删除”方法)。它还接收您在调用 Container.razor 文件中的代码时指定的 &lt;ItemTemplate&gt; 渲染片段。

    模板项中的“foreach”确保每个 TItem 都针对自己的 RenderFragment、一组按钮和回调函数进行设置。

    使用EventCallBack&lt;TItem&gt; 作为参数意味着您为其分配了一个方法,该方法期望返回TItem 的整个对象。 这很好,因为模板现在不关心 TItem 是什么类型,只是能够调用将 TItem 作为参数的方法! 处理任何 TItem 的实例现在是调用代码的责任,您不必尝试约束泛型类型 TItem。 (我在 Blazor 中还没有运气,也许是未来的版本)

至于如何渲染您输入的任何 TItem,文档HERE 中对此进行了很好的解释。

希望这会有所帮助!

【讨论】:

感谢您的信息。我一直在做一些非常相似的事情,除了我想知道而不是让模板的客户端定义这些 CRUD 操作它们是否可以是通用的,因为我希望这些 CRUD 操作除了它们使用的对象之外是相同的,例如创建一个类型的对象TItem,使用其 ID 字段作为键更新 TItem 类型的对象。通过使这些模型对象符合接口,我认为可以将通用 CRUD 操作定义为默认值,如果需要更大的灵活性,则可以定义回调函数。 您可能需要使用反射来提取类型,然后根据类型运行 switch 块,或者使用模式匹配。对于每种类型,您都必须在 switch 表达式中有自己的方法。在这种情况下,我将设置一个注入服务并将对象传递给服务上的方法,并让该服务处理 switch 表达式、类型提取和 CRUD 操作。这样,您只需将开关代码和相关函数调用维护在一个地方。

以上是关于模板化 Blazor 组件的主要内容,如果未能解决你的问题,请参考以下文章

Blazor University (17)使用 RenderFragments 模板化组件

Blazor University (18)使用 RenderFragments 模板化组件 —— 创建 TabControl

Blazor University (19)使用 RenderFragments 模板化组件 —— 数据传递

Blazor University (21)使用 RenderFragments 模板化组件 —— 传递占位符

Blazor_WASM之4:路由

Blazor 页面组件