Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 31, 2026

ASP.NET Web Forms applications rely on Page.Title, Page.MetaDescription, and Page.MetaKeywords for 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 with Title, MetaDescription, and MetaKeywords properties
  • Event notifications: TitleChanged, MetaDescriptionChanged, MetaKeywordsChanged
  • Page component: Subscribes to all property changes, renders dynamic <PageTitle> and meta tags via <HeadContent> with safe cross-thread updates
  • Auto-registered via existing AddBlazorWebFormsComponents() extension

Testing & Samples

  • 17 unit tests covering service behavior, component rendering, events, and DI
  • Interactive sample at /UtilityFeatures/PageService with inputs for all three properties and navigation integration
  • All 512 tests passing (476 original + 36 from recent merges)

Documentation

  • Complete migration guide at docs/UtilityFeatures/PageService.md
  • SEO best practices and usage examples
  • Added to mkdocs.yml nav and README.md

Usage

@page "/customers/{id:int}"
@inject IPageService PageService

<Page />

@code {
    protected override async Task OnInitializedAsync()
    {
        var customer = await GetCustomer(Id);
        PageService.Title = $"Customer: {customer.Name}";
        PageService.MetaDescription = $"View details for {customer.Name}";
        PageService.MetaKeywords = "customer, crm, details";
    }
}

Migration Path

// Web Forms (.NET 4.0+)
Page.Title = "Dynamic Title";
Page.MetaDescription = "Description for SEO";
Page.MetaKeywords = "keyword1, keyword2, keyword3";

// Blazor
PageService.Title = "Dynamic Title";
PageService.MetaDescription = "Description for SEO";
PageService.MetaKeywords = "keyword1, keyword2, keyword3";

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

This section details on the original issue you should resolve

<issue_title>Need a BasePage to provide some of the base services</issue_title>
<issue_description>There are several features in the Page object that should be made available to the application.

  • Page.Title</issue_description>

<agent_instructions>I need some research and ideas around this. I think the Page object may need to be some shared state that all of the components and blazor page elements are able to reference</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@csharpfritz Some initial research done in the spike_UserControl branch @csharpfritz Not entirely sure... needs some more research and discussion. This could be a key-piece to allow complete site-migration.

Adding to the parking lot for investigation after components are completed</comment_new>


💡 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.

Copilot AI and others added 5 commits January 31, 2026 15:30
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 AI changed the title [WIP] Add BasePage to provide essential services Implement PageService for programmatic page title management (Page.Title) Jan 31, 2026
Copilot AI requested a review from csharpfritz January 31, 2026 15:39
@csharpfritz
Copy link
Collaborator

@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

Comment on lines +25 to +37
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

This assignment to
cut
is useless, since its value is never read.

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.

Suggested changeset 1
src/BlazorWebFormsComponents.Test/PageService/ComponentTests.razor

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/BlazorWebFormsComponents.Test/PageService/ComponentTests.razor b/src/BlazorWebFormsComponents.Test/PageService/ComponentTests.razor
--- a/src/BlazorWebFormsComponents.Test/PageService/ComponentTests.razor
+++ b/src/BlazorWebFormsComponents.Test/PageService/ComponentTests.razor
@@ -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");
EOF
@@ -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");
Copilot is powered by AI and may make mistakes. Always verify output.
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

This assignment to
title
is useless, since its value is never read.

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.


Suggested changeset 1
src/BlazorWebFormsComponents.Test/PageService/InjectionTests.razor

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/BlazorWebFormsComponents.Test/PageService/InjectionTests.razor b/src/BlazorWebFormsComponents.Test/PageService/InjectionTests.razor
--- a/src/BlazorWebFormsComponents.Test/PageService/InjectionTests.razor
+++ b/src/BlazorWebFormsComponents.Test/PageService/InjectionTests.razor
@@ -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)
EOF
@@ -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)
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +31 to +36
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

Generic catch clause.

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) to catch (Exception ex) and log the exception via Logger.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.

Suggested changeset 1
src/BlazorWebFormsComponents/Page.razor.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/BlazorWebFormsComponents/Page.razor.cs b/src/BlazorWebFormsComponents/Page.razor.cs
--- a/src/BlazorWebFormsComponents/Page.razor.cs
+++ b/src/BlazorWebFormsComponents/Page.razor.cs
@@ -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);
         }
     }
 
EOF
@@ -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);
}
}

Copilot is powered by AI and may make mistakes. Always verify output.
Copilot AI and others added 2 commits January 31, 2026 15:45
Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
…sting and documentation

Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 31, 2026

@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 additiona...

Added MetaDescription and MetaKeywords properties to PageService, providing complete parity with ASP.NET Web Forms Page object (.NET 4.0+) for SEO meta tag management. Implemented in commit fafdb57.

Properties Added:

  • PageService.MetaDescription - Equivalent to Page.MetaDescription
  • PageService.MetaKeywords - Equivalent to Page.MetaKeywords
  • Both include change events and reactive updates via the Page component

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>
Copilot AI changed the title Implement PageService for programmatic page title management (Page.Title) Implement PageService for programmatic page title and meta tag management (Page.Title, MetaDescription, MetaKeywords) Jan 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Need a BasePage to provide some of the base services

2 participants