-
Notifications
You must be signed in to change notification settings - Fork 71
Implement PageService for programmatic page title and meta tag management (Page.Title, MetaDescription, MetaKeywords) #327
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
base: dev
Are you sure you want to change the base?
Conversation
Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
…ility Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
|
@copilot there are other features from WebForms that are provided in the Page attribute of components that we should be able to support. Review the old WebFOrms documentation, evaluate what additional properties would be desired on the PageService and implement those |
| var cut = Render(@<Page />); | ||
|
|
||
| // Assert - Title is maintained in service | ||
| pageService.Title.ShouldBe("Test Title"); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Page_TitleChangedDynamically_ComponentUpdates() | ||
| { | ||
| // Arrange | ||
| var pageService = new PageService(); | ||
| Services.AddScoped<IPageService>(_ => pageService); | ||
| var cut = Render(@<Page />); |
Check warning
Code scanning / CodeQL
Useless assignment to local variable Warning test
cut
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 days ago
To fix the problem, we should remove the unused local variable while preserving the Render(@<Page />); call so that the component still renders and the pageService interactions occur. In general, when a variable is only used to store the result of a method call whose return value is never read, you should either remove the variable and call the method for its side effects only, or remove the call entirely if its side effects are not needed.
For this specific case in src/BlazorWebFormsComponents.Test/PageService/ComponentTests.razor, in the Page_ComponentWithTitle_ServiceHasCorrectTitle test method, replace var cut = Render(@<Page />); with just Render(@<Page />);. No imports, method definitions, or other changes are required. The first and third tests remain unchanged. Functionality is preserved: the page still renders and can update pageService.Title, and the test still asserts on pageService.Title.
-
Copy modified line R25
| @@ -22,7 +22,7 @@ | ||
| Services.AddScoped<IPageService>(_ => pageService); | ||
|
|
||
| // Act | ||
| var cut = Render(@<Page />); | ||
| Render(@<Page />); | ||
|
|
||
| // Assert - Title is maintained in service | ||
| pageService.Title.ShouldBe("Test Title"); |
| protected override void OnInitialized() | ||
| { | ||
| // Verify we can access the service | ||
| var title = PageService.Title; |
Check warning
Code scanning / CodeQL
Useless assignment to local variable Warning test
title
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 days ago
In general, to fix a “useless assignment to local variable” you either remove the unused variable and assignment entirely, or, if you need the right-hand side expression for its side effects, you keep just the expression (or assign it to _/a discard) without introducing an unused named local.
Here, var title = PageService.Title; is only verifying that PageService.Title can be accessed. The value is not used anywhere else. The minimal, behavior-preserving fix is to access the property without assigning it to a local, e.g. var _ = PageService.Title; or simply PageService.Title;. In tests, using a discard variable communicates intent clearly. We will therefore change line 25 in src/BlazorWebFormsComponents.Test/PageService/InjectionTests.razor to use a discard variable instead of the unused title. No new imports, methods, or other definitions are required.
-
Copy modified line R25
| @@ -22,7 +22,7 @@ | ||
| protected override void OnInitialized() | ||
| { | ||
| // Verify we can access the service | ||
| var title = PageService.Title; | ||
| var _ = PageService.Title; | ||
| } | ||
|
|
||
| protected override void BuildRenderTree(RenderTreeBuilder builder) |
| catch (Exception) | ||
| { | ||
| // Silently handle exceptions from title updates to prevent | ||
| // breaking the component lifecycle. Title updates are non-critical. | ||
| // In production, consider logging this exception. | ||
| } |
Check notice
Code scanning / CodeQL
Generic catch clause Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 days ago
Generally, the fix is to avoid a blanket catch (Exception) and instead either (1) catch only specific, expected exception types, or (2) if a broad catch is truly required, handle expected exceptions and log or rethrow unexpected ones instead of silently swallowing them. This preserves diagnosability while still preventing non-critical failures from crashing the component.
In this specific method, we want to keep the behavior that exceptions during title update do not break the component lifecycle, but we should not silently ignore every failure. The simplest, non‑breaking change is to catch Exception into a variable, log it using a standard logging mechanism (ILogger<Page>), and still prevent the exception from propagating. This keeps runtime behavior (no thrown exception from here) but improves transparency and addresses the “generic catch clause” concern by making it explicit that we are intentionally handling all exceptions and persisting details. Since we cannot modify other files, we’ll inject ILogger<Page> into this component and use it in the catch block. The steps in src/BlazorWebFormsComponents/Page.razor.cs are:
- Add a
using Microsoft.Extensions.Logging;at the top (well-known library). - Add an
[Inject] private ILogger<Page> Logger { get; set; } = null!;property to the component. - Change
catch (Exception)tocatch (Exception ex)and log the exception viaLogger.LogError(ex, "...")instead of leaving the catch block empty.
This keeps existing functionality (no visible behavior change for callers) while making unexpected exceptions visible and intentional.
-
Copy modified line R2 -
Copy modified lines R17-R19 -
Copy modified line R35 -
Copy modified lines R37-R40
| @@ -1,4 +1,5 @@ | ||
| using Microsoft.AspNetCore.Components; | ||
| using Microsoft.Extensions.Logging; | ||
| using System; | ||
| using System.Threading.Tasks; | ||
|
|
||
| @@ -13,6 +14,9 @@ | ||
| [Inject] | ||
| private IPageService PageService { get; set; } = null!; | ||
|
|
||
| [Inject] | ||
| private ILogger<Page> Logger { get; set; } = null!; | ||
|
|
||
| private string? _currentTitle; | ||
|
|
||
| protected override void OnInitialized() | ||
| @@ -28,11 +32,12 @@ | ||
| _currentTitle = newTitle; | ||
| await InvokeAsync(StateHasChanged); | ||
| } | ||
| catch (Exception) | ||
| catch (Exception ex) | ||
| { | ||
| // Silently handle exceptions from title updates to prevent | ||
| // breaking the component lifecycle. Title updates are non-critical. | ||
| // In production, consider logging this exception. | ||
| // Handle exceptions from title updates to prevent | ||
| // breaking the component lifecycle. Title updates are non-critical, | ||
| // but exceptions are logged for diagnostics. | ||
| Logger?.LogError(ex, "An error occurred while updating the page title to '{NewTitle}'.", newTitle); | ||
| } | ||
| } | ||
|
|
Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
…sting and documentation Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
Added Properties Added:
Testing: 8 new unit tests added, all 512 repository tests passing. Sample Updated: Demo now shows all three properties (Title, MetaDescription, MetaKeywords) with working inputs and Web Forms comparison. Screenshot: https://github.com/user-attachments/assets/b2b1e22d-cba6-4b4f-95c3-788db35a4f12 |
Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
ASP.NET Web Forms applications rely on
Page.Title,Page.MetaDescription, andPage.MetaKeywordsfor programmatic page metadata updates. Blazor's<PageTitle>and<HeadContent>components only support declarative markup, blocking migration scenarios where metadata is set dynamically in code.Changes
Core Implementation
IPageService/PageService: Scoped service withTitle,MetaDescription, andMetaKeywordspropertiesTitleChanged,MetaDescriptionChanged,MetaKeywordsChangedPagecomponent: Subscribes to all property changes, renders dynamic<PageTitle>and meta tags via<HeadContent>with safe cross-thread updatesAddBlazorWebFormsComponents()extensionTesting & Samples
/UtilityFeatures/PageServicewith inputs for all three properties and navigation integrationDocumentation
docs/UtilityFeatures/PageService.mdUsage
Migration Path
Screenshots
Sample application demonstrating all three properties with dynamic updates:
Web Forms Compatibility
Provides full API parity with ASP.NET Web Forms Page object properties introduced in .NET 4.0, enabling seamless migration of code that programmatically sets page metadata for SEO and browser display.
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.