You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

通过JavaScript渲染Blazor组件时,如何传递CascadingParameter且不通过JS传值

问题

想给Counter组件传递CascadingParameter,但不通过路由,而是用JavaScript将组件渲染到Winbox.js窗口中。目前遇到的问题:

  • 通过JS渲染时,CascadingParameter被初始化为null,触发NullReferenceException
  • 通过路由访问Counter页面时,参数能正常加载,currentCount初始值为5

要求不通过JavaScript传递CascadingParameter值解决,因为存在调用组件时参数为null的场景(如MudDialog),或多个CascadingParameter的情况。


相关代码

1. Program.cs(注册Counter供JS调用)

builder.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter");

2. MainLayout.razor(定义CascadingValue)

<!-- 省略部分代码 -->
<CascadingValue Value="_TestCascadingParameter">
    <article class="content px-4">
        @Body
    </article>
</CascadingValue>
<!-- 省略部分代码 -->
@code{
private TestCascadingParameter _TestCascadingParameter = new() { GlobalCounter = 5 };

public class TestCascadingParameter
{
    public int GlobalCounter { get; set; }
}
}

3. Counter.razor(目标组件)

@page "/counter"
@using static BlazorApp1.Shared.MainLayout

<PageTitle>Counter</PageTitle>
<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    [CascadingParameter]
    public TestCascadingParameter TestCascadingParameter { get; set; }
    private int currentCount = 0;

    protected override void OnParametersSet()
    {
        currentCount = TestCascadingParameter.GlobalCounter;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}

4. WinBox.razor(触发Winbox的页面)

@page "/WinBox"
@layout MainLayout
@inject IJSRuntime _Js
<PageTitle>Win box</PageTitle>

<button @onclick="@OpenWinAsync">Open window</button>

@code{
    async Task OpenWinAsync()
    {
        await _Js.InvokeVoidAsync("MyWinBox.OpenTestWindow");
    }
}

5. MyWinBox.js(Winbox操作逻辑)

window.MyWinBox = {
    OpenTestWindow: async () => {
        let containerElement = new WinBox("WinBox.js");
        await Blazor.rootComponents.add(containerElement.body, 'counter', {});
    }
};

解决方案

方案一:依赖注入共享参数实例

通过将CascadingParameter的实例注册为服务,让组件既可以通过组件树的CascadingValue获取,也可以通过依赖注入获取,兼容两种渲染场景。

修改步骤:

  1. Program.cs:注册参数类为单例服务
// 注册TestCascadingParameter为单例,确保全局唯一实例
builder.Services.AddSingleton<BlazorApp1.Shared.MainLayout.TestCascadingParameter>(
    new BlazorApp1.Shared.MainLayout.TestCascadingParameter { GlobalCounter = 5 }
);
  1. MainLayout.razor:从服务注入实例,替代本地创建
<!-- 省略部分代码 -->
<CascadingValue Value="_testCascadingParameter">
    <article class="content px-4">
        @Body
    </article>
</CascadingValue>
<!-- 省略部分代码 -->
@code{
    // 从DI注入实例
    [Inject]
    private TestCascadingParameter _testCascadingParameter { get; set; }

    public class TestCascadingParameter
    {
        public int GlobalCounter { get; set; }
    }
}
  1. Counter.razor:优先使用CascadingParameter,无则用注入的实例
@page "/counter"
@using static BlazorApp1.Shared.MainLayout
// 注入参数实例
@inject TestCascadingParameter InjectedTestParameter

<PageTitle>Counter</PageTitle>
<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    [CascadingParameter]
    public TestCascadingParameter TestCascadingParameter { get; set; }
    private int currentCount = 0;
    private TestCascadingParameter _activeParameter;

    protected override void OnParametersSet()
    {
        // 优先取CascadingParameter,没有则用DI注入的实例
        _activeParameter = TestCascadingParameter ?? InjectedTestParameter;
        currentCount = _activeParameter.GlobalCounter;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}

方案二:在Blazor组件树内渲染后移至Winbox

让Counter始终处于MainLayout的CascadingValue范围内渲染,再将其DOM元素移动到Winbox窗口中,避免脱离组件树导致的参数丢失。

修改步骤:

  1. WinBox.razor:添加隐藏容器渲染Counter,再调用JS移动到Winbox
@page "/WinBox"
@layout MainLayout
@inject IJSRuntime _Js
<PageTitle>Win box</PageTitle>

<button @onclick="@OpenWinAsync">Open window</button>

<!-- 隐藏容器,用来渲染Counter组件 -->
<div @ref="_counterContainer" style="display: none;">
    <Counter />
</div>

@code{
    private ElementReference _counterContainer;

    async Task OpenWinAsync()
    {
        // 等待Blazor完成Counter的渲染
        await InvokeAsync(StateHasChanged);
        await Task.Delay(10); // 简单等待,生产环境可使用更可靠的渲染完成检测

        // 调用JS将Counter移动到Winbox
        await _Js.InvokeVoidAsync("MyWinBox.MoveCounterToWinBox", _counterContainer);
    }
}
  1. MyWinBox.js:修改逻辑为移动DOM元素,而非直接添加根组件
window.MyWinBox = {
    MoveCounterToWinBox: async (containerElement) => {
        const winbox = new WinBox("WinBox.js");
        // 获取Counter的DOM元素并移动到Winbox内
        const counterElement = containerElement.firstElementChild;
        winbox.body.appendChild(counterElement);
        
        // 可选:监听Winbox关闭事件,清理组件
        winbox.onclose = () => {
            // 这里可以调用Blazor的组件销毁逻辑,避免内存泄漏
            counterElement.remove();
            return true;
        };
    }
};

内容的提问来源于stack exchange,提问作者TameemHabash

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 12:03:14