简体   繁体   English

为多个页面定义一次 HTML 和 Blazor 组件

[英]Define HTML and Blazor components just once for multiple pages

I want to split my code to be as reusable as possible.我想拆分我的代码以使其尽可能可重用。

Lets consider I have this structure:让我们考虑一下我有这个结构:

I have a base class for a page called FormBasePage which inherits from ComponentBase我有一个名为FormBasePage的页面的基数 class,它继承自ComponentBase

This class holds a property for an identifier这个 class 拥有标识符的属性

public int Identifier { get; set;}

Now I have two other pages which inherit from my FormBasePage lets call them ProductForm and CustomerForm .现在我有两个其他页面继承自我的FormBasePage让我们称它们为ProductFormCustomerForm

In both files I want to display the identifier in the heading just like:在这两个文件中,我都想在标题中显示标识符,如下所示:

<h2>@Identifier</h2>

To achive this now, I'll need to repeat this line in both files.现在要实现这一点,我需要在两个文件中重复这一行。 This might be OK for a single line of code but lets think I'm having some sort of modals which are being used by all the page and just display a different name based on my identifier, eg a modal to confirm a deletion.这对于单行代码来说可能没问题,但假设我有某种模式,所有页面都在使用这些模式,并且只是根据我的标识符显示不同的名称,例如确认删除的模式。

Is there any way to provide some HTML or Blazor Components within my FormBasePage and insert the content around it?有没有办法在我的FormBasePage中提供一些 HTML 或 Blazor 组件并在其周围插入内容?

For example:例如:

  1. Content from FormBasePage (Header)来自FormBasePage的内容(页眉)
  2. Content from either ProductForm or CustomerForm来自ProductFormCustomerForm的内容
  3. Content from FormPageBase (Footer)来自FormPageBase的内容(页脚)

ComponentBase isn't designed to handle wrappers. ComponentBase不是为处理包装器而设计的。 You have two alternatives.你有两种选择。

Build a wrapper component构建包装器组件

Build a component that contains your code, add it to each page and add your content as it's ChildContent .构建一个包含您的代码的组件,将其添加到每个页面并添加您的内容,因为它是ChildContent

BaseWrapper.razor BaseWrapper.razor

<div class="bg-secondary text-white p-3 b-2">
    @this.ChildContent
</div>

@code {
    [Parameter] public RenderFragment? ChildContent { get; set; }
}

And then:接着:

@page "/"

<PageTitle>Index</PageTitle>

<BaseWrapper>
    <h1>Hello, world!</h1>

    Welcome to your new app.

    <SurveyPrompt Title="How is Blazor working for you?" />
<BaseWrapper>

Build a new Wrapper Base Component构建一个新的包装器基础组件

This is more advanced as you need a full blooded replacement for ComponentBase .这是更高级的,因为您需要ComponentBase的全血替代品。

This is mine: I use it principally in Edit/Display/List Forms. It does a few things differently such as condenses the lifecycle methods and doesn't automatically implement UI event rendering.这是我的:我主要在 Edit/Display/List Forms 中使用它。它做了一些不同的事情,例如压缩生命周期方法并且不会自动实现 UI 事件呈现。 It's intended to be leaner and meaner on resources than ComponentBase .它旨在比ComponentBase在资源上更精简。 There are lots of comments to explain the code.有很多注释来解释代码。

Whether you wish to implement something like this will depend on your C# skills and Blazor experience.您是否希望实施这样的事情将取决于您的 C# 技能和 Blazor 经验。

First the base component:首先是基本组件:

/// ============================================================
/// Author: Shaun Curtis, Cold Elm Coders
/// License: Use And Donate
/// If you use it, donate something to a charity somewhere
/// ============================================================

namespace YourNameSpace;

/// <summary>
/// Base minimum footprint component for building simple wrapper Components
/// Note there is no No automatic event rendering
/// </summary>
public abstract class UIWrapperBase : IComponent
{
    protected RenderFragment renderFragment;
    protected internal RenderHandle renderHandle;
    protected bool hasPendingQueuedRender = false;
    protected internal bool hasNeverRendered = true;
    protected bool hide;
    protected bool initialized;

    /// <summary>
    /// Content to render within the component
    /// </summary>
    [Parameter] public RenderFragment? ChildContent { get; set; }

    /// <summary>
    /// Parameter to control the display of the component
    /// </summary>
    [Parameter] public bool Hidden { get; set; } = false;

    /// <summary>
    /// This is the Wrapper Content where we define the wrapper content
    /// Use @Content for the child content
    /// </summary>
    protected abstract RenderFragment? Wrapper { get; }

    // This is where we capture the content from the child component
    // The Blazor compiler overrides BuildRenderTree with this content
    protected RenderFragment? Content => (builder) => this.BuildRenderTree(builder);

    /// <summary>
    /// CTor
    /// caches a copy of the Render code
    /// Detects if the component shoud be rendered and if not doesn't render any content
    /// </summary>
    public UIWrapperBase()
    {
        this.renderFragment = builder =>
        {
            hasPendingQueuedRender = false;
            hasNeverRendered = false;
            var hide = this.hide | this.Hidden;

            if (hide)
                return;

            if (this.Wrapper is not null)
            {
                this.Wrapper(builder);
                return;
            }

            BuildRenderTree(builder);
        };
    }

    /// <summary>
    /// Default Render method required by Razor to compile the Razor markup to.
    /// </summary>
    /// <param name="builder"></param>
    protected virtual void BuildRenderTree(RenderTreeBuilder builder) { }

    /// <summary>
    /// Method to queue the component Render Fragment onto the Renderer's Render Queue
    /// Only adds it if there are no other copies already queued
    /// </summary>
    protected void StateHasChanged()
    {
        if (hasPendingQueuedRender)
            return;

        hasPendingQueuedRender = true;
        renderHandle.Render(renderFragment);
    }

    /// <summary>
    /// StateHasChanged Method that is invoked on the UI Thread
    /// Do not call through InvokeAsync, it already does it.
    /// </summary>
    protected void InvokeStateHasChanged()
        => renderHandle.Dispatcher.InvokeAsync(StateHasChanged);

    /// <summary>
    ///  IComponent implementation
    ///  Gets and saves the provided RenderHandle
    /// </summary>
    /// <param name="renderHandle"></param>
    public void Attach(RenderHandle renderHandle)
        => this.renderHandle = renderHandle;

    /// <summary>
    /// Method that can be overridden by child components
    /// Equivalent to OnInitialized{Async}/OnParametersSet{Async} all rolled up into a single method
    /// Return false to prevent a Render.
    /// </summary>
    /// <param name="firstRender"></param>
    /// <returns>True to call StateHasChanged</returns>
    protected virtual ValueTask<bool> OnParametersChangedAsync(bool firstRender)
        => ValueTask.FromResult(true);

    /// <summary>
    ///  IComponent implementation
    /// Called by the Renderer at initialization and whenever any of the requested Parameters change
    /// </summary>
    /// <param name="parameters"></param>
    /// <returns></returns>
    public async Task SetParametersAsync(ParameterView parameters)
    {
        parameters.SetParameterProperties(this);

        var dorender = await this.OnParametersChangedAsync(!initialized)
            || hasNeverRendered
            || !hasPendingQueuedRender;

        if (dorender)
            this.StateHasChanged();

        this.initialized = true;
    }
}

And our defined wrapper:以及我们定义的包装器:

@inherits UIWrapperBase

@*@implements IHandleAfterRender*@
@*@implements IHandleEvent*@

@code {
    protected override RenderFragment Wrapper => (__builder) =>
    {
        <div class="bg-secondary text-white p-3 b-2">
        @this.Content
        </div>
    };

    // example using the replacement for OnInitialized/OnParametersSet
    protected override ValueTask<bool> OnParametersChangedAsync(bool firstRender)
    {
        // Do whatever lifecycle stuff you want to
        return ValueTask.FromResult(true);
    }

    // uncomment if you want single render UI events
    // Also uncomment @implements IHandleEvent at the top of the page
    //public async Task HandleEventAsync(EventCallbackWorkItem callback, object? arg)
    //{
    //    await callback.InvokeAsync(arg);
    //    StateHasChanged();
    //}

    // uncomment if you want double render UI events as in ComponentBase
    // Also uncomment @implements IHandleEvent at the top of the page
    //public async Task HandleEventAsync(EventCallbackWorkItem callback, object? arg)
    //{
    //    var task = callback.InvokeAsync(arg);
    //    if (task.Status != TaskStatus.RanToCompletion && task.Status != TaskStatus.Canceled)
    //    {
    //        StateHasChanged();
    //        await task;
    //    }
    //    StateHasChanged();
    //}


    // Uncomment of you want OnAfterRenderAsync
    // Also uncomment @implements IHandleAfterRender at the top of the page
    //private bool _hasCalledOnAfterRender;
    //public Task OnAfterRenderAsync()
    //{
    //    var firstRender = !_hasCalledOnAfterRender;
    //    _hasCalledOnAfterRender |= true;

    //    // your code here

    //    return Task.CompletedTask;
    //}
}

And finally Index:最后索引:

@page "/"
@inherits MyWrapper

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

This is what it looks like:这是它的样子: 在此处输入图像描述

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM