动态附加 Blazor 组件

Attach blazor component dynamically

我可以在 C# 中创建 Blazor 组件的实例并在之后附加它吗?
或者,是否可以从 C# 代码在 DOM 中动态创建一个组件并取回对它的引用?

我正在创建一个由 C# 代码触发的"弹出对话框"。

Dialog.razor

1
2
3
4
5
6
    Dialog Text: @Text


@code{
    public string Text { get; set; }
}

尝试失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    @foreach (var d in List)
    {
        @d<!--this doesn't work since d is not a RenderFragment-->
    }


@code{
    public List<Dialog> List { get; set; } = new List<Dialog>();

    void AddDialog()
    {
        var d = new Dialog();
        d.Text ="Hello" + List.Count;
        List.Add(d);
    }
}

这里的问题是我还没有找到将 ComponentBase 的实例附加到 DOM 上的方法。
有没有办法获取 RenderFragment?

第二个问题是,在附加对话框之前子组件不会初始化,因此首先限制了可以做的事情。


这是另一种方法……

AlertMessage.razor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div style="border: 1px solid red; width: 500px; height:auto; margin: 3px;
  padding:0px;">
    <div style="height:auto; width:inherit; padding:5px; border: 1px solid
    blue; text-align:right;">
        <span style="float:left">@Title</span>
         Close.InvokeAsync(ID))"
   role="button">X
        <input type="text" value="@content" />
   
     @ChildContent

 

@code {
  [Parameter]
  public int ID { get; set; }
  [Parameter]
  public string Title { get; set; }
  [Parameter]
  public RenderFragment ChildContent { get; set; }
  [Parameter]
  public EventCallback <int> Close {get; set;}

}

AlertMessageGroup.razor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
@using Microsoft.AspNetCore.Components.CompilerServices;


AlertMessageGroup
@if (alerts.Count > 0)
{
<p>Contains @alerts.Count AlertMessage Components</p>

@foreach (var alert in alerts)
 {
       <p>Alert ID: @alert.ID</p>

 }
}



    @foreach (var alert in alerts)
    {
        @RenderAlert(alert);

    }






@code {

List<Alert> alerts = new List<Alert>
        {
            new Alert{ ID = 1, Title ="First Message", Message ="This is my
                                                            first message" },
            new Alert{ ID = 2, Title ="Second Message", Message ="This is
                                                        my second message" },
            new Alert{ ID = 3, Title ="Third Message", Message ="This is my
                                                             third message" }
        };


private RenderFragment RenderAlert(Alert alert) => builder =>
{

    builder.OpenComponent(0, typeof(AlertMessage));
    builder.AddAttribute(1,"ID", alert.ID);
    builder.AddAttribute(2,"Title", alert.Title);


    builder.AddAttribute(3,"ChildContent", (RenderFragment)((builder) =>
    {
        builder.AddContent(4, alert.Message);


    }
    ));

     builder.AddAttribute(5,"Close", EventCallback.Factory.Create<int>
                                          (this, RemoveAlertMessage));
     builder.CloseComponent();

  };



public void RemoveAlertMessage(int ID)
{

    alerts.Remove( alerts.Where(alert => alert.ID == ID).FirstOrDefault());
    StateHasChanged();
}


public class Alert
{
    public int ID { get; set; }
    public string Title { get; set; }
    public string Message { get; set; }

}

}

用法

1
2
3
@page"/"

<AlertMessageGroup />


这是一种方法

  • 创建一个 Placeholder 对象并将其放在列表中
  • 从列表中,剃刀页面将创建一个新实例。
  • 捕获实例引用并将其传递回调用代码
  • 在对实例进行操作之前等待 OnAfterRender
  • Dialog.razor
    不知道它是动态加载的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
        Dialog Text: @Text
        <Red @ref="Red"></Red>


    @code{
        public Red Red { get; set; }

        public string Text { get; set; }

        public void SetText(string text)
        {
            this.Text = text;
            StateHasChanged();
        }
    }

    DynamicWrapper.razor
    用于通知组件何时完全加载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @ChildContent

    @code{
        [Parameter]
        public RenderFragment ChildContent { get; set; }

        [Parameter]
        public EventCallback AfterFirstRender { get; set; }

        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
                await AfterFirstRender.InvokeAsync(null);

            base.OnAfterRender(firstRender);
        }
    }

    TestPage.razor

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    <button @onclick="AddDialog">Add one more</button>


        @foreach (var p in List)
        {
            <DynamicWrapper AfterFirstRender="p.AfterFirstRender">
                <Dialog @ref="p.Dialog"></Dialog>
            </DynamicWrapper>
        }


    @code{
        public class Placeholder< T >
        {
            public T Dialog { get; set; }

            TaskCompletionSource< T > task = new TaskCompletionSource< T >();

            public void AfterFirstRender(object args)
            {
                task.SetResult(Dialog);
            }

            public Task< T > GetDialog() => task.Task;
        }

        public List<Placeholder<Dialog>> List { get; set; } = new List<Placeholder<Dialog>>();

        async Task AddDialog()
        {
            var p = new Placeholder<Dialog>();
            List.Add(p);
            var d = await p.GetDialog();
            d.SetText("Hello");
            d.Red.Show();
        }
    }