| File: Sections\SectionOutlet.cs | Web Access |
| Project: src\aspnetcore\src\Components\Components\src\Microsoft.AspNetCore.Components.csproj (Microsoft.AspNetCore.Components) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Components.Rendering; namespace Microsoft.AspNetCore.Components.Sections; /// <summary> /// Renders content provided by <see cref="SectionContent"/> components with matching <see cref="SectionId"/>s. /// </summary> public sealed class SectionOutlet : IComponent, IDisposable { private static readonly RenderFragment _emptyRenderFragment = _ => { }; private object? _subscribedIdentifier; private RenderHandle _renderHandle; private SectionRegistry _registry = default!; private SectionContent? _currentContentProvider; /// <summary> /// Gets or sets the <see cref="string"/> ID that determines which <see cref="SectionContent"/> instances will provide /// content to this instance. /// </summary> [Parameter] public string? SectionName { get; set; } /// <summary> /// Gets or sets the <see cref="object"/> ID that determines which <see cref="SectionContent"/> instances will provide /// content to this instance. /// </summary> [Parameter] public object? SectionId { get; set; } internal IComponent? CurrentLogicalParent => _currentContentProvider; void IComponent.Attach(RenderHandle renderHandle) { _renderHandle = renderHandle; _registry = _renderHandle.Dispatcher.SectionRegistry; } Task IComponent.SetParametersAsync(ParameterView parameters) { parameters.SetParameterProperties(this); object? identifier; if (SectionName is not null && SectionId is not null) { throw new InvalidOperationException($"{nameof(SectionOutlet)} requires that '{nameof(SectionName)}' and '{nameof(SectionId)}' cannot both have non-null values."); } else if (SectionName is not null) { identifier = SectionName; } else if (SectionId is not null) { identifier = SectionId; } else { throw new InvalidOperationException($"{nameof(SectionOutlet)} requires a non-null value either for '{nameof(SectionName)}' or '{nameof(SectionId)}'."); } if (!object.Equals(identifier, _subscribedIdentifier)) { if (_subscribedIdentifier is not null) { _registry.Unsubscribe(_subscribedIdentifier); } _registry.Subscribe(identifier, this); _subscribedIdentifier = identifier; } RenderContent(); return Task.CompletedTask; } internal void ContentUpdated(SectionContent? provider) { _currentContentProvider = provider; RenderContent(); } private void RenderContent() { // Here, we guard against rendering after the renderer has been disposed. // This can occur after prerendering or when the page is refreshed. // In these cases, a no-op is preferred. if (_renderHandle.IsRendererDisposed) { return; } _renderHandle.Render(BuildRenderTree); } private void BuildRenderTree(RenderTreeBuilder builder) { var fragment = _currentContentProvider?.ChildContent ?? _emptyRenderFragment; builder.OpenComponent<SectionOutletContentRenderer>(0); builder.SetKey(fragment); builder.AddComponentParameter(1, SectionOutletContentRenderer.ContentParameterName, fragment); builder.CloseComponent(); } /// <inheritdoc/> public void Dispose() { if (_subscribedIdentifier is not null) { _registry.Unsubscribe(_subscribedIdentifier); } } // This component simply renders the RenderFragment it is given // The reason for rendering SectionOutlet output via this component is so that // [1] We can use @key to guarantee that we only preserve descendant component // instances when they come from the same SectionContent, not unrelated ones // [2] We know that whenever the SectionContent is changed to another one, there // will be a new ComponentState established to represent this intermediate // component, and it will already have the correct LogicalParentComponentState // so anything computed from this (e.g., whether or not streaming rendering is // enabled) will be freshly re-evaluated, without that information having to // change in place on an existing ComponentState. internal sealed class SectionOutletContentRenderer : IComponent { public const string ContentParameterName = "content"; private RenderHandle _renderHandle; public void Attach(RenderHandle renderHandle) { _renderHandle = renderHandle; } public Task SetParametersAsync(ParameterView parameters) { var fragment = parameters.GetValueOrDefault<RenderFragment>(ContentParameterName)!; _renderHandle.Render(fragment); return Task.CompletedTask; } } }