Closed akorchev closed 1 year ago
Thanks for contacting us.
We're moving this issue to the Next sprint planning
milestone for future evaluation / consideration. We will evaluate the request when we are planning the work for the next milestone. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.
One of the reasons we haven't got this as a baked-in feature yet is that it's possible to implement something like this in your own application code. If there's enough consensus that some single pattern takes care of almost all requirements, we might well consider putting it into the framework. But until then, it would help if people were able to try out ways of doing this and report back on what's working well or not well for them.
To get started, here's one way to do it:
SectionContent
, SectionOutlet
, and SectionRegistry
) from this Gist to your project: https://gist.github.com/SteveSandersonMS/4f08efe2ad32178add12bfa3eb6e4559<SectionOutlet>
with a name to define where you want some content to go. For example, you could add the following into the default template's MainLayout.razor
:<div class="top-row px-4">
<SectionOutlet Name="topbar" />
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<SectionContent>
with a name to define some content to be rendered. For example, you change Counter.razor
to render the following:<h1>Counter</h1>
<p>Current count: @currentCount</p>
<SectionContent Name="topbar">
<button class="btn btn-primary" @onclick="IncrementCount">Increment counter</button>
</SectionContent>
Now when the user navigates to "Counter", they will have a button in the navigation bar that updates the count. And when they navigate to any other page, that button will disappear.
Of course, each page could provide its own different content for topbar
, and you can have any number of different named sections.
Technically you can even use this approach to supply content to the <head>
element in your page (e.g., to have a page-specific <title>
) - I checked this works. It's a bit awkward because you do need to migrate everything inside <html>
to be part of your App.razor
root component so the <head>
is rendered by Blazor. But it can be done. If this sort of approach pleases people we can look into ways of making it more natural in .NET 6.
Thank you @SteveSandersonMS ! This is a viable solution.
One of the reasons we haven't got this as a baked-in feature yet is that it's possible to implement something like this in your own application code
Indeed it is. But the implementation is so simple and small that you can consider including it in Blazor (or another official Nuget package that people can use). Thanks again for the code and general idea. I will use it in my projects.
To anybody trying @SteveSandersonMS's solution don't make the same mistake as me. Do not forget to include @Body
in your layout! I assumed it was not needed when using SectionOutlet and was wondering why nothing worked. Rendering the Body is still needed in order to actually run the code in the SectionContent and register it with the SectionRegistry.
We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.
Is this still being considered for .NET 7 ? As far as I can tell the outlet-code is already included in Blazor - just internal.
It's in .NET 7 Planning
so it may or may not make it in depending on the overall workload and how it's prioritized relative to other things. You're correct that the implementation is basically already done - the remaining cost is around ensuring the API is correct for long-term support, figuring out if there might be any problems caused by incorrect usage, docs, perhaps more tests for other use cases, etc.
Any news about this? I've used the above code to create Sections but I'd like to use the official bits...
@javiercn thanks to mention #42880 here. we can hope to have solution for this problem in blazor and .net 7!?
@sajjadarashhh not for .NET 7 for sure.
@danroth27 would this issue be implemented as public in the .NET7 planning or its going to be push back to .NET8?
Thanks and best regards
@wangkanai this is not something we are doing for .NET 7.0.
We will consider it for 8.0, however, we want to better understand concrete scenarios people have around the feature before we decide to include it in the framework. Specifically with comparisons between the code you have to write today and the code you would write with the feature in place.
Okay, I am writing a demo project blazor that would need this feature. https://github.com/wangkanai/wangkanai/tree/main/Tabler I’m making the blazor version of the Tabler CCS framework. https://tabler.wangkanai.com/
What I’m looking to do the change body css class, similar to what you can do with <PageTitle>
@wangkanai I am not sure we have enough detail to understand what you are trying to accomplish. Could you provide a concrete code snippet?
Okay, Let me design something up quickly.
I would like at extend the Sections to public and I would use it update <Body>
class with example code below. By design it would work just the like the blazor <PageTitle>
component.
Reference demo code: Box layout
@wangkanai Thanks for providing this code sketch. This helps to clarify, because what you're looking for doesn't really resemble the sections proposal we have. You need some way to add CSS classes into an existing external element, which is not something addressed by sections.
Instead I recommend you simply use JS interop to toggle the class on the <body>
element.
I'll expose my use case, just in case it serves as an example: I have a main layout with a header. This header has a button on the left to open the main menu (a sidenav panel on the main layout also). But this header also contains the title of the current page and one or more buttons on the right side that depend on the contents of the page being opened. So, I created a section inside this header using the above code, and I fill it's different content on every page of the app. Otherwise I'd have to replicate the header and the main layout structure on every page...
Thanks for contacting us.
We're moving this issue to the .NET 8 Planning
milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.
Just wanted to add my few cents. I'm making a CMS-like engine, where different pages can render to different parts of a page. Namely, on the top, there are breadcrumbs, and on the right, there is a table of contents. The body of the page is the content itself. The layout can define the sections.
I'm not a big fan of magic strings, I'd rather pass discrete references to objects, so I've made the following, using the example of putting a table of contents on the page in a different place. It's similar (closest) in concept to MVC's .cshtml sections.
Section.cs
public sealed class Section<TOutlet> : IComponent where TOutlet : SectionOutlet, IDisposable
{
[Parameter, EditorRequired]
public RenderFragment ChildContent { get; set; } = null!;
[Parameter, EditorRequired]
public TOutlet Outlet { get; set; } = null!;
void IComponent.Attach(RenderHandle renderHandle)
{
// The section isn't rendered where it is defined, so the RenderHandle is discarded here by design.
}
public Task SetParametersAsync(ParameterView parameters)
{
ChildContent = parameters.GetValueOrDefault<RenderFragment>(nameof(ChildContent))!;
Outlet = parameters.GetValueOrDefault<TOutlet>(nameof(Outlet))!;
Outlet?.Render(ChildContent); // The ?. is only needed if the Outlet hasn't been defined yet.
return Task.CompletedTask;
}
public void Dispose()
{
Outlet?.Render(_=>{}); // Keep in mind this might also clear the content of the outlet if another section is attached to it.
}
}
SectionOutlet.cs
public class SectionOutlet : IComponent
{
public RenderHandle Handle { get; private set; }
public void Attach(RenderHandle renderHandle) => Handle = renderHandle;
public virtual Task SetParametersAsync(ParameterView parameters) => Task.CompletedTask;
public void Render(RenderFragment renderFragment) => Handle.Render(renderFragment);
}
Usage is as expected: you need to create an outlet somewhere (higher up the hierarchy), hold its reference, then define a section in the body of your page referring to the reference held, something like:
App.razor
<CascadingValue Value="this" IsFixed="true">
<!-- Router, layout, etc. -->
<div class="table-of-contents">
<SectionOutlet @ref="TableOfContents">
</div>
</CascadingValue>
@code {
public SectionOutlet? TableOfContents { get; set; }
}
MyPage.razor
@page /mypage
<Section Outlet="App.TableOfContents">
<h1>Lorem ipsum dolor</h1>
<h2>Lorem ipsum dolor</h3>
<h3>Lorem ipsum dolor</h3>
</Section>
@code {
[CascadingParameter]
public App App { get; set; }
}
This seems to work for my scenario very well. I wonder if I'm missing something or if this will cause issues in the future, but I would like to see this implemented in the framework as well. One obvious drawback of this direct implementation is if there are multiple Section instances using the same Outlet, they'll keep overwriting each others' contents. I'm not sure how this could be done otherwise (except for throwing in such cases when multiple section try to write to the same outlet).
The other one using the breadcrumbs I'm currently in the middle of developing, as it shouldn't use arbitrary content, but rather should use a model provided by the page itself; so it's possibly better to implement it in some other way, or it requires maybe one or two more levels of abstraction. It can easily be used by something similar though:
<Section Outlet="App.Breadcrumbs">
<Breadcrumbs Crumbs="..."/>
</Section>
It also would be nice to check if the section is available or has content similar to IsSectionDefined
@yugabe I like your version, it's very short and clean. I don't know if you have improved it in the meantime, but I found two issues.
Section
the SectionOutlet
keeps its content. To fix this issue you can implement IDisposable
in Section and call Outlet?.Render(_=>{});
in the Dispose
method.@Body
a NullReferenceException is thrown for the outlets below @Body
because they don't exists yet when the Section's SetParametersAsync
of a component in the @Body
is called. The ?
in Outlet?.Render(ChildContent);
fixes the exception, but the ChildContent
isn't rendered until it get's a rerendering.@Flachdachs Thanks a lot for the comment! My original solution was just a proof of concept I was hoping the team could use when they implemented the in-box solution. However, it seems they opted to make public the previously private version, so this wasn't needed. I'm not sure I'll move unconditionally to that solution because I think this one has a few advantages over that.
Both of your problems I also encountered myself and solved them, but in the meantime the solution got more verbose and complicated (I actually created a third component called RenderSection
, which handles rendering the SectionOutlet
), so I didn't keep the above code up-to-date. I'll make an update to the code above though, so anyone stumbling on it can use it as is. Thank you!
Basically the same as https://github.com/dotnet/aspnetcore/issues/10131 which has been closed in in favour of #10452 which was in turn closed in favour of #10450 (influencing the
<head>
).Having the ability to embed content from the page in the layout in more than one placeholder is a common need. A typical use case is to display the page name in the "header" of the application which is usually defined in the layout. Another use case is to display personalised content for the currently logged user.
A lot of other technologies support this feature and I think it would be a great addition to Blazor. A few examples:
Thank you for your consideration!