实现接口的Blazor组件列表

3

我正在构建一个可重用的Razor类库,用于Blazor组件。

我有一个接口,任何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;
    }
}

有没有办法在ControllableComponentManager中填充_controllableComponentList?

我想以干净、合适的方式实现,不使用反射。最好采用ASP.NET Core风格的依赖注入。

问题在于,Blazor组件在Razor标记中被实例化为<Component></Component>,因此我不知道如何获取它们的引用并将它们添加到List<>中。


1
您可以使用反射和 Assembly.GetTypes() 来搜索类型。然后,您可以通过它们实现的接口来过滤这些类型。请注意,这通常是一项昂贵的操作,因此您应该缓存结果。 - poke
@poke 那将是最后的手段。我真的更喜欢以干净、适当的方式完成它,而不使用反射。 - Jinjinov
唯一的另一种方法是积极创建所有这些组件类型的列表。但如果您想要检测它们,那么反射就是为此而生的。 - poke
@poke 不,我不想检测它们。我正在寻找一种创建列表的方法。但由于Blazor组件在Razor标记中实例化为<Component></Component>,我不知道如何获取它们的引用以将它们添加到列表中。 - Jinjinov
3个回答

4
我猜你可以按以下方式完成它:
  1. Define a service that stores a collection of the interface type
  2. Expose a method to add a component, a method to remove a component, an event delegate to notify of addition, removal, etc.
  3. Inject the service into the component you want to add itself to the service, and in the OnInitialized method do something like this:

    protected override void OnInitialized()
    {
        MyComponents.AddComponent(this);
        this.MyProperty = "I was born with the sun...";
    }  
    

但我的答案非常详细,包括所有的实现细节,而且,20秒就可以完成啦。 - Jinjinov
实现这个并不是问题。我可以做得很好。问题在于使用“this”关键字从OnInitialized方法将组件添加到服务中。但是,嘿,我并不坚持。 - enet
好的,没问题,公平竞争 :) - Jinjinov

4

您可以使用@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] 属性来进行依赖注入,将服务注入到组件中注册的位置。

使用 ComponentBaseprotected 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


1
如果你想动态创建组件,那么可以使用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);
    }
}

谢谢你的尝试,我非常感激,但我已经知道所有这些。你回答的一切都已经在微软文档中了。@ref只能在Razor标记内部工作,所以我不能用它来填充我的List。目前,我正在尝试让组件在protected override void OnInitialized()上注册到它们通过[Inject]获得的IControllableComponentManager - Jinjinov
正如我所说,您无法按照您想要的方式填充列表。您不能自己创建组件实例。您需要重新调整方法以适应 Blazor 的基于组件的特性。 - poke
我并不是试图自己创建组件实例。我希望它们能够在 protected override void OnInitialized() 中通过 [Inject] 获取到的 IControllableComponentManager 中注册自己 :) 是的,你猜对了,“some value” 在我的情况下是一个 RenderFragment :) 我正在尝试创建一个管理器类来控制所有这些 :) - Jinjinov
事实证明,您可以填充列表 :) 谢谢您的时间! :) - Jinjinov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接