Blazor:从布局访问参数

Blazor: Access parameter from layout

如何从布局访问页面的路由参数?

我有一个页面接受如下路由参数:

@page /my-page/{Slug}

在共享布局中呈现标记时,我需要访问 Slug 的值。

我试过在布局文件中实现 OnParametersSet,如下所示,但未设置该值。它仅在页面级别分配。

@inherits LayoutComponentBase

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <div class="top-row px-4">
            @this.Slug   <<<<------ display the parameter
    </div>

    <div class="content px-4">
        @Body
    </div>
</div>

@code
{
    [Parameter]
    public string Slug { get; set; }

    protected override void OnParametersSet()
    {
        // Slug is always null :-/
    }
}

经过一番摸索,我想出了以下解决方案。它可能都可以在 .razor 文件中完成,但我在“代码隐藏”文件中实现了一些混乱以隐藏似乎是杂乱无章的东西。

在 Layout 实例中,如果您覆盖 OnParametersSet,并钻取 Body.Target,您将找到包含路由参数的 RouteData。然后,您可以将这些值传播到布局中的子组件。

包含我们希望布局可用的“Slug”参数的页面

@page "/mypage/{Slug}"

布局 .razor 文件

@inherits Components.MyLayoutBase

<div class="sidebar">
    <!-- Pass the Slug property to the NavMenu's Parameter -->
    <MyNavMenu Slug="@Slug" />
</div>

<div class="main">
    <div class="top-row px-4"></div>    
    <div class="content px-4">
        @Body
    </div>
</div>

后面的布局代码

public class MyLayoutBase : LayoutComponentBase
{
    public string Slug { get; set; }

    protected override void OnParametersSet()
    {
        // pull out the "Slug" parameter from the route data
        object slug = null;
        if ((this.Body.Target as RouteView)?.RouteData.RouteValues?.TryGetValue("Slug", out slug) == true)
        {
            Slug = slug?.ToString();
        }
    }
}

导航菜单

<div class="top-row pl-4 navbar navbar-dark">
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="@($"/some-page/{Slug}/foo")">
                <span class="oi oi-home" aria-hidden="true"></span> Foo
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="@($"/some-page/{Slug}/bar")">
                <span class="oi oi-home" aria-hidden="true"></span> Bar
            </NavLink>
        </li>
    </ul>
</div>

@code {

    [Parameter]
    public string Slug { get; set; }

    bool collapseNavMenu = true;

    string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    string setupUrl = string.Empty;

    void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

您可以通过 CascadingParameters 使 RouteData 可用于所有组件。在您的 App.razor 中,执​​行此操作:

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <CascadingValue Value="@routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MyLayout)" />
        </CascadingValue>
    </Found>
</Router>

然后,在需要 RouteData 的所有组件中,只需添加:

@code
{
    [CascadingParameter]
    RouteData RouteData { get; set; }
}

该值将自动填充。

上面的答案没有解决嵌套布局。在那些情况下,我得到了以下工作。级联参数方法也有一些潜在的问题:

public static bool TryGetRouteData(this Delegate renderFragment, out RouteData routeData)
{
    routeData = null;

    if (renderFragment?.Target is RouteView routeView)
    {
        routeData = routeView.RouteData;
    }
    else if (renderFragment?.Target is object target)
    {
        // the target may be a CompilerGenerated class
        var bodyField = target.GetType().GetField("bodyParam");

        if (bodyField is not null)
        {
            var value = bodyField.GetValue(target);

            if (value is Delegate childDel)
            {
                TryGetRouteData(childDel, out routeData);
            }
        }
    }

    return routeData is not null;
}

然后在 layout/component class:

protected override void OnInitialized()
{
   if (Body?.TryGetRouteData(out var routeData) == true)
   {
       // stuff here
    }
}