Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sections support #46727

Merged
merged 34 commits into from
Mar 8, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
89c686d
sections support
surayya-MS Feb 17, 2023
7e99bde
change SecionsTest
surayya-MS Feb 17, 2023
cbb63aa
fix
surayya-MS Feb 17, 2023
397b91a
use Browser.Exists in tests; move code block to the end of .razor file
surayya-MS Feb 20, 2023
0e7d9c5
E2E tests
surayya-MS Feb 21, 2023
269b8d4
use private fields for buttons in tests; use "-" instead of "_" fir ids
surayya-MS Feb 21, 2023
85f3a7c
support changing IsDefaultContent; fix tests
surayya-MS Feb 22, 2023
ebca897
fix
surayya-MS Feb 23, 2023
32fb842
Merge branch 'main' into sectionsSupport
surayya-MS Feb 23, 2023
85b8ec6
use object SectionId instead of string Name
surayya-MS Feb 23, 2023
435716a
fix
surayya-MS Feb 23, 2023
4cca921
Merge branch 'main' into sectionsSupport
surayya-MS Feb 23, 2023
c42520a
Update src/Components/Components/src/Sections/SectionContent.cs
surayya-MS Feb 24, 2023
d3352be
fix
surayya-MS Feb 28, 2023
495bff4
Update src/Components/test/E2ETest/Tests/SectionsTest.cs
surayya-MS Mar 1, 2023
4742a2d
Update src/Components/Web/src/Head/HeadOutlet.cs
surayya-MS Mar 1, 2023
7323ca1
Update src/Components/Components/src/Sections/SectionOutlet.cs
surayya-MS Mar 1, 2023
b7be8b4
Update src/Components/Components/src/Sections/SectionContent.cs
surayya-MS Mar 1, 2023
cb0d2e3
Update src/Components/Components/src/Sections/SectionOutlet.cs
surayya-MS Mar 1, 2023
52bc8d9
fix
surayya-MS Mar 1, 2023
e21ae58
Merge branch 'sectionsSupport' of https://github.com/surayya-MS/aspne…
surayya-MS Mar 1, 2023
ae3171e
fix
surayya-MS Mar 2, 2023
35d13b9
fix
surayya-MS Mar 6, 2023
275100b
change exceptions messages in SectionRegistry
surayya-MS Mar 6, 2023
4b92f0c
remove IsDefaultContent from SectionContent
surayya-MS Mar 6, 2023
48538d1
remove IsDefaultContent from PublicAPI
surayya-MS Mar 6, 2023
08ebe15
Merge branch 'main' into sectionsSupport
surayya-MS Mar 6, 2023
e418aa6
fix
surayya-MS Mar 6, 2023
853e6ea
Merge branch 'main' into sectionsSupport
surayya-MS Mar 7, 2023
157bdaf
Revert "remove IsDefaultContent from PublicAPI"
surayya-MS Mar 7, 2023
036dc2d
Revert "remove IsDefaultContent from SectionContent"
surayya-MS Mar 7, 2023
a6ef091
made IsDefaultContent internal
surayya-MS Mar 7, 2023
403d493
Merge branch 'main' into sectionsSupport
surayya-MS Mar 7, 2023
b1b8be8
fix
surayya-MS Mar 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/Components/Components/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,18 @@ Microsoft.AspNetCore.Components.ComponentBase.DispatchExceptionAsync(System.Exce
Microsoft.AspNetCore.Components.RenderHandle.DispatchExceptionAsync(System.Exception! exception) -> System.Threading.Tasks.Task!
*REMOVED*Microsoft.AspNetCore.Components.NavigationManager.ToAbsoluteUri(string! relativeUri) -> System.Uri!
Microsoft.AspNetCore.Components.NavigationManager.ToAbsoluteUri(string? relativeUri) -> System.Uri!
Microsoft.AspNetCore.Components.Sections.SectionContent
Microsoft.AspNetCore.Components.Sections.SectionContent.ChildContent.get -> Microsoft.AspNetCore.Components.RenderFragment?
Microsoft.AspNetCore.Components.Sections.SectionContent.ChildContent.set -> void
Microsoft.AspNetCore.Components.Sections.SectionContent.Dispose() -> void
Microsoft.AspNetCore.Components.Sections.SectionContent.IsDefaultContent.get -> bool
Microsoft.AspNetCore.Components.Sections.SectionContent.IsDefaultContent.set -> void
surayya-MS marked this conversation as resolved.
Show resolved Hide resolved
Microsoft.AspNetCore.Components.Sections.SectionContent.SectionContent() -> void
Microsoft.AspNetCore.Components.Sections.SectionContent.SectionId.get -> object!
Microsoft.AspNetCore.Components.Sections.SectionContent.SectionId.set -> void
Microsoft.AspNetCore.Components.Sections.SectionOutlet
Microsoft.AspNetCore.Components.Sections.SectionOutlet.Dispose() -> void
Microsoft.AspNetCore.Components.Sections.SectionOutlet.SectionId.get -> object!
Microsoft.AspNetCore.Components.Sections.SectionOutlet.SectionId.set -> void
Microsoft.AspNetCore.Components.Sections.SectionOutlet.SectionOutlet() -> void
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddComponentParameter(int sequence, string! name, object? value) -> void
32 changes: 17 additions & 15 deletions src/Components/Components/src/Sections/SectionContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@
namespace Microsoft.AspNetCore.Components.Sections;

/// <summary>
/// Provides content to <see cref="SectionOutlet"/> components with matching <see cref="Name"/>s.
/// Provides content to <see cref="SectionOutlet"/> components with matching <see cref="SectionId"/>s.
/// </summary>
internal sealed class SectionContent : ISectionContentProvider, IComponent, IDisposable
public sealed class SectionContent : ISectionContentProvider, IComponent, IDisposable
{
private string? _registeredName;
private object? _registeredSectionId;
private bool? _registeredIsDefaultContent;
private SectionRegistry _registry = default!;

/// <summary>
/// Gets or sets the name that determines which <see cref="SectionOutlet"/> instance will render
/// Gets or sets the ID that determines which <see cref="SectionOutlet"/> instance will render
/// the content of this instance.
/// </summary>
[Parameter] public string Name { get; set; } = default!;
[Parameter, EditorRequired] public object SectionId { get; set; } = default!;

/// <summary>
/// Gets or sets whether this component should provide the default content for the target
Expand All @@ -39,33 +40,34 @@ Task IComponent.SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);

if (string.IsNullOrEmpty(Name))
if (SectionId is null)
{
throw new InvalidOperationException($"{GetType()} requires a non-empty string parameter '{nameof(Name)}'.");
throw new InvalidOperationException($"{nameof(SectionContent)} requires a non-null value for the parameter '{nameof(SectionId)}'.");
}

if (Name != _registeredName)
if (!object.Equals(SectionId, _registeredSectionId) || IsDefaultContent != _registeredIsDefaultContent)
{
if (_registeredName is not null)
if (_registeredSectionId is not null)
{
_registry.RemoveProvider(_registeredName, this);
_registry.RemoveProvider(_registeredSectionId, this);
}

_registry.AddProvider(Name, this, IsDefaultContent);
_registeredName = Name;
_registry.AddProvider(SectionId, this, IsDefaultContent);
_registeredSectionId = SectionId;
_registeredIsDefaultContent = IsDefaultContent;
}

_registry.NotifyContentChanged(Name, this);
_registry.NotifyContentChanged(SectionId, this);

return Task.CompletedTask;
}

/// <inheritdoc/>
public void Dispose()
{
if (_registeredName is not null)
if (_registeredSectionId is not null)
{
_registry.RemoveProvider(_registeredName, this);
_registry.RemoveProvider(_registeredSectionId, this);
}
}
}
28 changes: 14 additions & 14 deletions src/Components/Components/src/Sections/SectionOutlet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@
namespace Microsoft.AspNetCore.Components.Sections;

/// <summary>
/// Renders content provided by <see cref="SectionContent"/> components with matching <see cref="Name"/>s.
/// Renders content provided by <see cref="SectionContent"/> components with matching <see cref="SectionId"/>s.
/// </summary>
internal sealed class SectionOutlet : ISectionContentSubscriber, IComponent, IDisposable
public sealed class SectionOutlet : ISectionContentSubscriber, IComponent, IDisposable
{
private static readonly RenderFragment _emptyRenderFragment = _ => { };

private string? _subscribedName;
private object? _subscribedSectionId;
private RenderHandle _renderHandle;
private SectionRegistry _registry = default!;
private RenderFragment? _content;

/// <summary>
/// Gets or sets the name that determines which <see cref="SectionContent"/> instances will provide
/// Gets or sets the ID that determines which <see cref="SectionContent"/> instances will provide
/// content to this instance.
/// </summary>
[Parameter] public string Name { get; set; } = default!;
[Parameter, EditorRequired] public object SectionId { get; set; } = default!;

void IComponent.Attach(RenderHandle renderHandle)
{
Expand All @@ -31,20 +31,20 @@ Task IComponent.SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);

if (string.IsNullOrEmpty(Name))
if (SectionId is null)
{
throw new InvalidOperationException($"{GetType()} requires a non-empty string parameter '{nameof(Name)}'.");
throw new InvalidOperationException($"{nameof(SectionOutlet)} requires a non-null value for the parameter '{nameof(SectionId)}'.");
}

if (Name != _subscribedName)
if (!object.Equals(SectionId, _subscribedSectionId))
{
if (_subscribedName is not null)
if (_subscribedSectionId is not null)
{
_registry.Unsubscribe(_subscribedName);
_registry.Unsubscribe(_subscribedSectionId);
}

_registry.Subscribe(Name, this);
_subscribedName = Name;
_registry.Subscribe(SectionId, this);
_subscribedSectionId = SectionId;
}

RenderContent();
Expand Down Expand Up @@ -74,9 +74,9 @@ private void RenderContent()
/// <inheritdoc/>
public void Dispose()
{
if (_subscribedName is not null)
if (_subscribedSectionId is not null)
{
_registry.Unsubscribe(_subscribedName);
_registry.Unsubscribe(_subscribedSectionId);
}
}
}
52 changes: 26 additions & 26 deletions src/Components/Components/src/Sections/SectionRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ namespace Microsoft.AspNetCore.Components.Sections;

internal sealed class SectionRegistry
{
private readonly Dictionary<string, ISectionContentSubscriber> _subscribersByName = new();
private readonly Dictionary<string, List<ISectionContentProvider>> _providersByName = new();
private readonly Dictionary<object, ISectionContentSubscriber> _subscribersBySectionId = new();
private readonly Dictionary<object, List<ISectionContentProvider>> _providersBySectionId = new();

public void AddProvider(string name, ISectionContentProvider provider, bool isDefaultProvider)
public void AddProvider(object sectionId, ISectionContentProvider provider, bool isDefaultProvider)
{
if (!_providersByName.TryGetValue(name, out var providers))
if (!_providersBySectionId.TryGetValue(sectionId, out var providers))
{
providers = new();
_providersByName.Add(name, providers);
_providersBySectionId.Add(sectionId, providers);
}

if (isDefaultProvider)
Expand All @@ -26,18 +26,18 @@ public void AddProvider(string name, ISectionContentProvider provider, bool isDe
}
}

public void RemoveProvider(string name, ISectionContentProvider provider)
public void RemoveProvider(object sectionId, ISectionContentProvider provider)
{
if (!_providersByName.TryGetValue(name, out var providers))
if (!_providersBySectionId.TryGetValue(sectionId, out var providers))
{
throw new InvalidOperationException($"There are no content providers with the name '{name}'.");
throw new InvalidOperationException($"There are no content providers with the given section ID.");
}

var index = providers.LastIndexOf(provider);

if (index < 0)
{
throw new InvalidOperationException($"The provider was not found in the providers list of name '{name}'.");
throw new InvalidOperationException($"The provider was not found in the providers list of the given section ID.");
}

providers.RemoveAt(index);
Expand All @@ -47,44 +47,44 @@ public void RemoveProvider(string name, ISectionContentProvider provider)
// We just removed the most recently added provider, meaning we need to change
// the current content to that of second most recently added provider.
var content = GetCurrentProviderContentOrDefault(providers);
NotifyContentChangedForSubscriber(name, content);
NotifyContentChangedForSubscriber(sectionId, content);
}
}

public void Subscribe(string name, ISectionContentSubscriber subscriber)
public void Subscribe(object sectionId, ISectionContentSubscriber subscriber)
{
if (_subscribersByName.ContainsKey(name))
if (_subscribersBySectionId.ContainsKey(sectionId))
{
throw new InvalidOperationException($"There is already a subscriber to the content '{name}'.");
throw new InvalidOperationException($"There is already a subscriber to the content with the given section ID.");
}

// Notify the new subscriber with any existing content.
var content = GetCurrentProviderContentOrDefault(name);
var content = GetCurrentProviderContentOrDefault(sectionId);
subscriber.ContentChanged(content);

_subscribersByName.Add(name, subscriber);
_subscribersBySectionId.Add(sectionId, subscriber);
}

public void Unsubscribe(string name)
public void Unsubscribe(object sectionId)
{
if (!_subscribersByName.Remove(name))
if (!_subscribersBySectionId.Remove(sectionId))
{
throw new InvalidOperationException($"The subscriber with name '{name}' is already unsubscribed.");
throw new InvalidOperationException($"The subscriber with the given section ID is already unsubscribed.");
}
}

public void NotifyContentChanged(string name, ISectionContentProvider provider)
public void NotifyContentChanged(object sectionId, ISectionContentProvider provider)
{
if (!_providersByName.TryGetValue(name, out var providers))
if (!_providersBySectionId.TryGetValue(sectionId, out var providers))
{
throw new InvalidOperationException($"There are no content providers with the name '{name}'.");
throw new InvalidOperationException($"There are no content providers with the given section ID.");
}

// We only notify content changed for subscribers when the content of the
// most recently added provider changes.
if (providers.Count != 0 && providers[^1] == provider)
{
NotifyContentChangedForSubscriber(name, provider.Content);
NotifyContentChangedForSubscriber(sectionId, provider.Content);
}
}

Expand All @@ -93,14 +93,14 @@ public void NotifyContentChanged(string name, ISectionContentProvider provider)
? providers[^1].Content
: null;

private RenderFragment? GetCurrentProviderContentOrDefault(string name)
=> _providersByName.TryGetValue(name, out var existingList)
private RenderFragment? GetCurrentProviderContentOrDefault(object sectionId)
=> _providersBySectionId.TryGetValue(sectionId, out var existingList)
? GetCurrentProviderContentOrDefault(existingList)
: null;

private void NotifyContentChangedForSubscriber(string name, RenderFragment? content)
private void NotifyContentChangedForSubscriber(object sectionId, RenderFragment? content)
{
if (_subscribersByName.TryGetValue(name, out var subscriber))
if (_subscribersBySectionId.TryGetValue(sectionId, out var subscriber))
{
subscriber.ContentChanged(content);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Web/src/Head/HeadContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public sealed class HeadContent : ComponentBase
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenComponent<SectionContent>(0);
builder.AddComponentParameter(1, nameof(SectionContent.Name), HeadOutlet.HeadSectionOutletName);
builder.AddComponentParameter(1, nameof(SectionContent.SectionId), HeadOutlet.HeadSectionId);
builder.AddComponentParameter(2, nameof(SectionContent.ChildContent), ChildContent);
builder.CloseComponent();
}
Expand Down
10 changes: 5 additions & 5 deletions src/Components/Web/src/Head/HeadOutlet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ public sealed class HeadOutlet : ComponentBase
{
private const string GetAndRemoveExistingTitle = "Blazor._internal.PageTitle.getAndRemoveExistingTitle";

internal const string HeadSectionOutletName = "head";
internal const string TitleSectionOutletName = "title";
internal static readonly object HeadSectionId = new();
internal static readonly object TitleSectionId = new();

private string? _defaultTitle;

Expand All @@ -37,22 +37,22 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
{
// Render the title content
builder.OpenComponent<SectionOutlet>(0);
builder.AddComponentParameter(1, nameof(SectionOutlet.Name), TitleSectionOutletName);
builder.AddComponentParameter(1, nameof(SectionOutlet.SectionId), TitleSectionId);
builder.CloseComponent();

// Render the default title if it exists
if (!string.IsNullOrEmpty(_defaultTitle))
{
builder.OpenComponent<SectionContent>(2);
builder.AddComponentParameter(3, nameof(SectionContent.Name), TitleSectionOutletName);
builder.AddComponentParameter(3, nameof(SectionContent.SectionId), TitleSectionId);
builder.AddComponentParameter(4, nameof(SectionContent.IsDefaultContent), true);
builder.AddComponentParameter(5, nameof(SectionContent.ChildContent), (RenderFragment)BuildDefaultTitleRenderTree);
builder.CloseComponent();
}

// Render the rest of the head metadata
builder.OpenComponent<SectionOutlet>(6);
builder.AddComponentParameter(7, nameof(SectionOutlet.Name), HeadSectionOutletName);
builder.AddComponentParameter(7, nameof(SectionOutlet.SectionId), TitleSectionId);
builder.CloseComponent();
}

Expand Down
2 changes: 1 addition & 1 deletion src/Components/Web/src/Head/PageTitle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public sealed class PageTitle : ComponentBase
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenComponent<SectionContent>(0);
builder.AddComponentParameter(1, nameof(SectionContent.Name), HeadOutlet.TitleSectionOutletName);
builder.AddComponentParameter(1, nameof(SectionContent.SectionId), HeadOutlet.TitleSectionId);
builder.AddComponentParameter(2, nameof(SectionContent.ChildContent), (RenderFragment)BuildTitleRenderTree);
builder.CloseComponent();
}
Expand Down
Loading