将 Blazor 组件转换为 RenderFragment

问题描述 投票:0回答:1

我需要将 Blazor 组件的实例更改为

RenderFragment
。我能够通过使用反射来实现它:

public static RenderFragment CreateRenderFragmentFromInstance(this IComponent instance, IComponent namespaceComponent)
{
    int attributeNumber = 0;

    return builder =>
    {
        builder.OpenComponent(attributeNumber++, instance.GetType());

        var properties = instance.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
        foreach (var property in properties)
        {
            if (property.PropertyType == typeof(EventCallback))
            {
                var originalCallback = (EventCallback)property.GetValue(instance)!;

                var wrappedCallback = EventCallback.Factory.Create(namespaceComponent, async arg =>
                {
                    if (originalCallback.HasDelegate)
                    {
                        await originalCallback.InvokeAsync(arg);
                    }
                });

                builder.AddAttribute(attributeNumber++, property.Name, wrappedCallback);
            }
            else
            {
                builder.AddAttribute(attributeNumber++, property.Name, property.GetValue(instance));
            }
        }

        builder.CloseComponent();
    };
}

但是有没有更简单的方法来做到这一点?

例如使用一些奇怪的构建器树方法组合?


用例:

简介:

我创建了一个简单的 Blazor 弹出库。使用步骤:

  • 你下载我的nuget:
    Kebechet.Blazor.Components.Popup
  • 将服务安装到 DI:
    builder.Services.AddPopupWrapperServices();
  • <PopupWrapper />
    组件放入您的
    MainLayout.razor
  • 在您的页面中只需
    @inject PopupWrapperService _popupWrapperService

并这样称呼它:

var isSuccess =  await _popupWrapperService.Show(new YesNoPopup(), this);
if (isSuccess is null)
{
    // user closed the popup
} 

如您所见,我在参数中创建了新对象的实例

YesNoPopup
。这个
YesNoPopup
是实现
IPopupable
的组件,因此可以简单地将值返回给使用它的服务。

这种在代码中创建弹出窗口而不是在

.razor
中指定 HTML/创建组件的方式对我来说是最优雅且最易于使用的。

有问题的部分

当我调用

Show
方法并将组件作为输入之一时,它会调用 this 方法。在该方法中,我想从组件创建
RenderFragment
,这样我就可以使用
PopupWrapper.razor
将其渲染为
_currentPopupWrapper.RenderPopupContent(renderFragment);

我认为treeBuilder中应该有一些像

.AddComponent

这样的方法,而不仅仅是
OpenComponent
。这基本上就是我问题的重点。如果不使用我的反射方法也可以做到的话。

blazor components
1个回答
0
投票
我更喜欢使用更简单的方法来处理这些类型的组件,并且更喜欢在其标准方法中使用 Blazor,这有助于提高可读性和理解性。

因此,我建议您按照以下示例实现将您的方法更改为更标准的方法:

PopUpWrapper.razor

@inject PopUpService PopUpService @* Render notifications in the service *@ @if (PopUpService.Popups?.Count > 0) { @foreach (var popup in PopUpService.Popups) { <div class="PopupContainer"> <h1>@popup.Heading</h1> <div class="PopupContent"> @switch (popup.Type) { case PopupType.YesNo: <PopupContent_YesNo OnCallback="@popup.OnCallback" Message="@popup.Message" /> break; case PopupType.Alert: <PopupContent_Alert OnCallback="@popup.OnCallback" Message="@popup.Message" /> break; } </div> </div> } } @code { protected override void OnInitialized() { //Register the service to be able to update the state of the container PopUpService.RegisterLayout(() => StateHasChanged()); } }

PopupContent_YesNo.razor

<div> <p>@Message</p> <button @onclick="async () => await OnCallback.InvokeAsync(true)">Yes</button> <button @onclick="async () => await OnCallback.InvokeAsync(false)">No</button> </div> @code { [Parameter, EditorRequired] public string Message { get; set; } [Parameter, EditorRequired] public EventCallback<bool> OnCallback { get; set; } }

PopupContent_Alert.razor

<div> <p>@Message</p> <button @onclick="async () => await OnCallback.InvokeAsync(true)">OK</button> </div> @code { [Parameter, EditorRequired] public string Message { get; set; } [Parameter, EditorRequired] public EventCallback<bool> OnCallback { get; set; } }

PopupType.cs

//Determines the popup to render public enum PopupType { YesNo, Alert }

Popup.cs

//Class with default constructor for linking to the service //You can extend the constructor to populate the properties by parameters public class PopUp(PopUpService service) { PopUpService _popupService = service; public string Heading { get; set; } public string Message { get; set; } public PopupType Type { get; set; } public Action<bool> ActionCallback { get; set; } //The callback, will also remove the popup public void OnCallback(bool response) { _popupService.Popups.Remove(this); ActionCallback(response); } }

PopUpService.cs

public class PopUpService : IDisposable { //All popups go in here public List<PopUp> Popups { get; private set; } = new(); //State change trigger Action OnStateChange { get; set; } public void RegisterLayout(Action stateChange) { //Registering state change OnStateChange = stateChange; } //Simple standard popup, can extend with RenderFragment for customization public void Show(PopupType type, string heading, string message, Action<bool> actionCallback) { Popups.Add(new PopUp(this) { Type = type, Heading = heading, Message = message, ActionCallback = actionCallback }); OnStateChange.Invoke(); } //Remove popup, called from PopUp class public void RemovePopup(PopUp popup) { Popups.Remove(popup); OnStateChange.Invoke(); } //Dispose if closed public void Dispose() { OnStateChange = delegate { }; } }

<PopupWrapper />

 上的某处添加 
Layout
,例如 
MainLayout.cs

Index.razor
 示例:

@inject PopUpService PopUpService @page "/" <button @onclick="@(() => OpenYesNo())"> Add Yes No </button> <button @onclick="@(() => OpenOK())"> Add OK </button> @code { int popupCounter = 0; void OpenYesNo() { PopUpService.Show(PopupType.YesNo, $"{popupCounter++}: This is a yes no", "This is the message for a Yes No", (r) => Console.WriteLine($"YesNo Result: {r}")); } void OpenOK() { PopUpService.Show(PopupType.Alert, $"{popupCounter++}: This is an OK", "This is the message for an OK", (r) => Console.WriteLine($"OK Result: {r}")); } }
    
© www.soinside.com 2019 - 2024. All rights reserved.