Diagnostic ID: BWFC022
Severity:
Category: Migration
Status: Active
This analyzer warns when your code uses Page.ClientScript or ClientScriptManager — Web Forms' API for dynamically registering client-side JavaScript.
Detected patterns:
Page.ClientScript.RegisterStartupScript(...)Page.ClientScript.RegisterClientScriptBlock(...)Page.ClientScript.RegisterClientScriptInclude(...)Page.ClientScript.GetPostBackEventReference(...)- Any
ClientScriptManagermethod call
protected void Page_Load(object sender, EventArgs e)
{
// ⚠️ BWFC022: Page.ClientScript is not available in Blazor.
// See: docs/Migration/ClientScriptMigrationGuide.md
Page.ClientScript.RegisterStartupScript(
this.GetType(),
"InitializeUI",
"console.log('Page loaded');",
addScriptTags: true);
}In Web Forms, Page.ClientScript is the standard way to inject JavaScript into pages. In Blazor:
- There is no
Pageobject — Blazor is component-based, not page-based - No automatic script injection — JavaScript must be explicitly referenced
- Different lifecycle — Instead of page load, use component initialization hooks like
OnAfterRenderAsync()
Without addressing Page.ClientScript usage, your migrated code will compile errors or runtime failures.
For a zero-rewrite migration, use the ClientScriptShim included in BWFC. It provides the exact same API as Page.ClientScript:
// Web Forms
Page.ClientScript.RegisterStartupScript(GetType(), "init", "...", true);
// Blazor with ClientScriptShim — identical call!
ClientScript.RegisterStartupScript(GetType(), "init", "...", true);See "ClientScriptShim (Zero-Rewrite Path)" in the migration guide for details.
If you prefer to modernize your code now, rewrite to IJSRuntime directly. The fix depends on which ClientScript method you're using. See ClientScriptMigrationGuide.md for detailed before/after examples.
| Pattern | Blazor Equivalent | Difficulty |
|---|---|---|
RegisterStartupScript() |
ClientScriptShim (⭐ Easy) or OnAfterRenderAsync() + IJSRuntime (⭐⭐ Moderate) |
⭐ Easy |
RegisterClientScriptInclude() |
ClientScriptShim (⭐ Easy) or <script> tag in layout (⭐ Easy) |
⭐ Easy |
RegisterClientScriptBlock() |
ClientScriptShim (⭐ Easy) or JS module (⭐ Easy) |
⭐ Easy |
GetPostBackEventReference() |
ClientScriptShim (⭐ Easy — Phase 2) or @onclick / EventCallback<T> (⭐⭐ Medium) |
⭐ Easy |
=== "Web Forms (Before)"
csharp protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { Page.ClientScript.RegisterStartupScript( this.GetType(), "InitializeUI", "$(function() { applyTheme('dark'); });", addScriptTags: true); } }
=== "Blazor (After)" ```razor @inject IJSRuntime JS
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var module = await JS.InvokeAsync<IJSObjectReference>(
"import", "./app.js");
await module.InvokeVoidAsync("applyTheme", "dark");
}
}
}
```
=== "Web Forms (Before)"
csharp protected void Page_Load(object sender, EventArgs e) { Page.ClientScript.RegisterClientScriptInclude( "jquery-ui", ResolveUrl("~/lib/jquery-ui/jquery-ui.min.js")); }
=== "Blazor (After)"
html <!-- In layout (index.html or _Layout.html) --> <script src="lib/jquery/jquery.min.js"></script> <script src="lib/jquery-ui/jquery-ui.min.js"></script>
=== "Web Forms (Before)"
csharp public string GetDeleteButtonScript() { return Page.ClientScript.GetPostBackEventReference( new PostBackOptions(btnDelete, "clicked")); }
=== "Blazor (After — Phase 2 with Shim, Zero Rewrite)"
csharp // Same code works! ClientScriptShim returns __doPostBack() string public string GetDeleteButtonScript() { return ClientScript.GetPostBackEventReference( new PostBackOptions(btnDelete, "clicked")); }
=== "Blazor (Modern Approach)" ```razor <button @onclick="HandleDelete">Delete
@code {
private async Task HandleDelete()
{
await DeleteItemAsync();
}
}
```
For comprehensive migration guidance with code examples for each ClientScript method, see:
📖 ClientScriptMigrationGuide.md
Sections:
- Startup Scripts — Most common pattern (Section 1)
- Script Includes — External
.jsfiles (Section 2) - Script Blocks — Inline JavaScript (Section 3)
- Postback Events — Dynamic event references (Section 4)
- Form Validation —
Page.IsValidpatterns (Section 5)
// ❌ WRONG: Embedding complex logic in eval()
await JS.InvokeVoidAsync("eval", @"
function processData() {
// 50 lines of logic...
}
processData();
");// app.js
export function processData() {
// 50 lines of logic...
}// Component
var module = await JS.InvokeAsync<IJSObjectReference>("import", "./app.js");
await module.InvokeVoidAsync("processData");// ❌ WRONG: Script runs on every render
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await JS.InvokeVoidAsync("applyTheme");
}// ✅ CORRECT: Script runs only on first render
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("applyTheme");
}
}To suppress this warning for a specific line:
#pragma warning disable BWFC022
Page.ClientScript.RegisterStartupScript(/* ... */);
#pragma warning restore BWFC022Or in .editorconfig:
[*.cs]
dotnet_diagnostic.BWFC022.severity = silent- 📖 ClientScriptMigrationGuide.md — Comprehensive migration guide
- 📖 IJSRuntime Documentation — Blazor JS interop
- 📖 Component Lifecycle — OnAfterRenderAsync and friends
Status: ✅ Active
Last Updated: 2026-07-30
Owner: Beast (Technical Writer)