` It's relatively easy to do this:
<h3>DivContainerComponent</h3>
<div class="bg-dark text-white m-3 p-2">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
}
However, there are situations where this doesn't quite fit.
Forms are a good example. You want to impose a standard edit or view form wrapper to all your data forms. It's just the internal field definitions that are specific. You can use the method above, but it's clumpsy and contains repetitive code.
Unfortunately the standard Blazor component wasn't designed for this. There are workarounds, but none are nice.
Blazr.Demo.ComponentWrapper Repo
A new component is needed to implement this cleanly. The base component we use here is my lean, mean, green component. There's a reference link in the Appendix.
First this is a wrapper in action. Note:
BuildRenderTree which we ignore.Wrapper render fragment.@this.Content is where the content from the child will be rendered.
@inherits UIWrapperComponentBase
@code {
protected override RenderFragment Wrapper => (__builder) =>
{
<div class="bg-primary text-white p-3 b-2">
@this.Content
</div>
};
}
Note:
Wrapper defined as abstract. It must be implemented in child classes.this.BuildRenderTree(builder) is assigned to Content. this.BuildRenderTree(builder) contains the compiled Razor code that represents the child component's content.renderFragment for performance. If not hidden it renders the contents of Wrapper if it's not null, or the child content directly.public abstract class UIWrapperComponentBase : UIComponentBase { protected virtual RenderFragment? Wrapper { get; } protected RenderFragment? Content => (builder) => this.BuildRenderTree(builder); public UIWrapperComponentBase() { this.renderFragment = builder => { hasPendingQueuedRender = false; hasNeverRendered = false; var hide = this.hide | this.Hidden; if (hide) return; if (this.Wrapper is not null) { this.Wrapper.Invoke(builder); return; } BuildRenderTree(builder); }; } }
UIComponentBase doesn't implement any automated UI Event Handling. If you want ComponentBase type handling you need to implement it yourself.
If you need automated UI rendering, implement IHandleEvent.
For a single render:
@implements IHandleEvent //... @code { public async Task HandleEventAsync(EventCallbackWorkItem callback, object? arg) { await callback.InvokeAsync(arg); StateHasChanged(); } }
For the ComponentBase double event:
@implements IHandleEvent //... @code { 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(); } }
If you need to implement the OnAfterRender event, implement IHandleAfterRender.
@implements IHandleAfterRender //... @code { private bool _hasCalledOnAfterRender; public Task OnAfterRenderAsync() { var firstRender = !_hasCalledOnAfterRender; _hasCalledOnAfterRender |= true; // your code here return Task.CompletedTask; } }
Here's a simple demo setting Index to inherit from MyWrapper.
The result:
