我需要将 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 弹出库。使用步骤:
Kebechet.Blazor.Components.Popup
,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
。这基本上就是我问题的重点。如果不使用我的反射方法也可以做到的话。
因此,我建议您按照以下示例实现将您的方法更改为更标准的方法:
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}"));
}
}