I'm using .NET 8 Blazor Web App with static rendering (and some part is WebAssembly Interactive but it's not relevant here I think). In MVC/Razor Pages, a page could pass variables (such as a bool
) to the layout using ViewBag or similar mechanics, allowing the layout to conditionally show or hide parts of the UI, or in my case, add a class name to the <body>
tag.
In Blazor, I tried to implement something similar to HeadContent/HeadOutlet by using a shared service, I even have an event and call StateHasChanged
when the property is set but it's not reflected in the layout (I think it makes sense since the server does not care about it).
I know a workaround is to make many variations of the Layout but I am trying to avoid doing this. I don't think even generic Layout works (or am I wrong?).
Is it possible to pass certain info from the routed Component back to the Layout? I am fine if the values must be known at compile time.
For some reason, wrapping the entire content with SectionOutlet
let me cascade the whole thing!
MainLayout.razor
:
<SectionOutlet SectionName="Test" />
<SectionContent SectionName="Test">
<CascadingValue Value="layoutModel">
<PageTitle>@(layoutModel.Title ?? "NO TITLE")</PageTitle>
<PageHeader />
<div>
Title: @(layoutModel.Title)
@(layoutModel.Wide)
</div>
</CascadingValue>
</SectionContent>
Home.razor
:
[CascadingParameter]
public LayoutModel Layout { get; set; } = null!;
protected override void OnInitialized()
{
Layout.Title = "Home page here";
Layout.Wide = true;
}
The static HTML rendered by the server:
<div b-d4mc6q6izj>
Title: Home page here
True</div>
Old answer:
I finally made it with generic! Technically it moves the "variation" from the Layout to the model but I think it's better. Like I mentioned, this only works if you know your values at runtime though.
First, make an model class for layout's options and its variants:
public class LayoutModel
{
public string Title { get; set; } = "Default Title";
public string BodyClasses { get; set; } = "default-body-class";
}
public class CustomLayoutModel : LayoutModel
{
public CustomLayoutModel()
{
Title = "Custom Tilte";
BodyClasses = "custom-body-class";
}
}
Then, the Layout should now accept a generic parameter (MainLayout.razor
):
@inherits LayoutComponentBase
@typeparam TModel where TModel : LayoutModel, new()
<h1>@(model.Title)</h1>
<div class="@(model.BodyClasses)">
<p>This div has class: @(model.BodyClasses)</p>
</div>
@Body
@code {
TModel model = new();
}
Optionally make a "default" non-generic version for convenience (MainLayout.cs
):
public class MainLayout : MainLayout<LayoutModel> { }
Now you can set a custom value layout with:
@layout MainLayout<CustomLayoutModel>
Output:
<h1>Custom Tilte</h1>
<div class="custom-body-class"><p>This div has class: custom-body-class</p></div>