我正在为Blazor组件构建一个可重用的Razor类库。
我有一个接口,任何Blazor组件都可以实现:
public interface IControllableComponent
{
void SetSomeValue(int someValue);
}
如何获取实现该接口的所有组件的列表?
public interface IControllableComponentManager
{
void SetSomeValueForAllControllableComponents(int someValue);
}
public class ControllableComponentManager : IControllableComponentManager
{
IList<IControllableComponent> _controllableComponentList;
public ControllableComponentManager()
{
_controllableComponentList = ??? // how to populate this list?
}
public void SetSomeValueForAllControllableComponents(int someValue)
{
foreach (var controllableComponent in _controllableComponentList)
{
controllableComponent.SetSomeValue(someValue);
}
}
}
我想让我的可重用Razor类库在不同项目的代码背后使用:
public class MyControllableComponentBase : ComponentBase, IControllableComponent
{
protected int _someValue;
public void SetSomeValue(int someValue)
{
_someValue = someValue;
}
}
有没有办法在ControlleComponentManager中填充_controllableComponentList?
我想以一种干净、恰当的方式来做这件事,不需要反思。最好是带有依赖项注入的ASP.NET Core风格。
问题是Blazor组件在Razor标记中被实例化为<Component></Component>
,所以我不知道如何获得它们的引用来将它们添加到List<>
中。
我想你可以按如下方式完成:
- 定义一个存储接口类型集合的服务
- 公开用于添加组件的方法、用于删除组件的方法以及用于通知添加、删除等的事件委托
-
将服务注入到要将其自身添加到服务的组件中,并在OnInitialized方法中执行以下操作:
protected override void OnInitialized() { MyComponents.AddComponent(this); this.MyProperty = "I was born with the sun..."; }
您可以使用@ref
获得Blazor组件引用,但只能在Razor标记中实现。
若要在Razor标记之外使用对Blazor组件的引用,该组件必须自行注册。
要做到这一点,您必须为代码背后的组件使用一个分部类:
public partial class ComponentContainer : ComponentBase, IComponentContainer
{
public Type ComponentType { get; protected set; }
public RenderFragment Component { get; set; }
[Parameter]
public string Name { get; set; }
[Inject]
protected IComponentContainerManager ComponentContainerManager { get; set; }
public void SetComponentType(Type componentType)
{
ComponentType = componentType;
StateHasChanged();
}
protected override void OnInitialized()
{
ComponentContainerManager.RegisterComponentContainer(Name, this);
Component = builder =>
{
if (ComponentType != null)
{
builder.OpenComponent(0, ComponentType);
builder.CloseComponent();
}
};
}
}
使用[Inject]
属性可以使用依赖项注入来注入组件可以注册的服务。
使用ComponentBase
的protected override void OnInitialized()
在注册服务中注册组件。
public interface IComponentContainer
{
Type ComponentType { get; }
void SetComponentType(Type componentType);
}
public interface IComponentContainerManager
{
void RegisterComponentContainer(string componentContainerName, IComponentContainer componentContainer);
void SetComponentType(string componentContainerName, Type componentType);
Type GetComponentType(string componentContainerName);
}
public class ComponentContainerManager : IComponentContainerManager
{
readonly IDictionary<string, IComponentContainer> _componentContainerDict = new Dictionary<string, IComponentContainer>();
public void RegisterComponentContainer(string componentContainerName, IComponentContainer componentContainer)
{
_componentContainerDict[componentContainerName] = componentContainer;
}
public void SetComponentType(string componentContainerName, Type componentType)
{
_componentContainerDict[componentContainerName].SetComponentType(componentType);
}
public Type GetComponentType(string componentContainerName)
{
return _componentContainerDict[componentContainerName].ComponentType;
}
}
现在,您可以在代码中的任何位置使用IComponentContainerManager
。
如果要动态创建组件,则可以使用RenderFragment
概念。渲染片段本质上是一个使用RenderTreeBuilder
创建组件的函数。这就是编写.razor
文件时Razor组件的编译目的。
例如,下面的代码创建了一个RenderFragment
,它只渲染一个组件:
Type componentType = typeof(MyControllableComponentBase);
RenderFragment fragment = builder =>
{
builder.OpenComponent(0, componentType);
builder.CloseComponent();
};
然后,您可以将该片段分配给一个属性,并在您的Razor组件中动态呈现它:
<div>
@Fragment
</div>
@code {
public RenderFragment Fragment
{ get; set; }
}
这将解决如何在只知道组件类型的情况下动态创建组件的问题。但是,它不会解决您想要在组件实例上调用SetSomeValue
的问题。为此,您必须明白,您并不能真正控制组件实例:渲染树生成器负责从该虚拟标记创建组件,因此您永远不会调用new ComponentType()
。相反,您依靠渲染器为自己创建实例,然后您可以引用所使用的实例并使用它。
可以使用@ref
指令捕获对渲染组件实例的引用:
<MyControllableComponent @ref="controllableComponent" />
@code {
private MyControllableComponent controllableComponent;
private void OnSomething()
{
controllableComponent.SetSomeValue(123);
}
}