Skip to content

BWFC022: Page.ClientScript Usage

Diagnostic ID: BWFC022
Severity: ⚠️ Warning
Category: Migration
Status: Active


What It Detects

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 ClientScriptManager method call


Use the ClientScriptShim for zero-code rewrite — See ClientScript Migration Guide for details on the easiest migration path that requires no code changes.


Example

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);
}

Why It Matters

In Web Forms, Page.ClientScript is the standard way to inject JavaScript into pages. In Blazor:

  • There is no Page object — 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.


How to Fix

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.


Alternative: Manual Rewrite

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.

Quick Reference

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

Common Fix: Startup Script

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        Page.ClientScript.RegisterStartupScript(
            this.GetType(),
            "InitializeUI",
            "$(function() { applyTheme('dark'); });",
            addScriptTags: true);
    }
}
@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");
        }
    }
}

Common Fix: Script Include

protected void Page_Load(object sender, EventArgs e)
{
    Page.ClientScript.RegisterClientScriptInclude(
        "jquery-ui",
        ResolveUrl("~/lib/jquery-ui/jquery-ui.min.js"));
}
<!-- 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>

Common Fix: PostBack Event Reference

public string GetDeleteButtonScript()
{
    return Page.ClientScript.GetPostBackEventReference(
        new PostBackOptions(btnDelete, "clicked"));
}
// Same code works! ClientScriptShim returns __doPostBack() string
public string GetDeleteButtonScript()
{
    return ClientScript.GetPostBackEventReference(
        new PostBackOptions(btnDelete, "clicked"));
}
<button @onclick="HandleDelete">Delete</button>

@code {
    private async Task HandleDelete()
    {
        await DeleteItemAsync();
    }
}

Detailed Migration Paths

For comprehensive migration guidance with code examples for each ClientScript method, see:

📖 ClientScriptMigrationGuide.md

Sections: 1. Startup Scripts — Most common pattern (Section 1) 2. Script Includes — External .js files (Section 2) 3. Script Blocks — Inline JavaScript (Section 3) 4. Postback Events — Dynamic event references (Section 4) 5. Form ValidationPage.IsValid patterns (Section 5)


Common Mistakes

❌ Don't: Use eval() for Complex Scripts

// ❌ WRONG: Embedding complex logic in eval()
await JS.InvokeVoidAsync("eval", @"
    function processData() {
        // 50 lines of logic...
    }
    processData();
");

✅ Do: Define Functions in JavaScript Modules

// app.js
export function processData() {
    // 50 lines of logic...
}
// Component
var module = await JS.InvokeAsync<IJSObjectReference>("import", "./app.js");
await module.InvokeVoidAsync("processData");

❌ Don't: Skip the firstRender Guard

// ❌ WRONG: Script runs on every render
protected override async Task OnAfterRenderAsync(bool firstRender)
{
    await JS.InvokeVoidAsync("applyTheme");
}

✅ Do: Guard with if (firstRender)

// ✅ CORRECT: Script runs only on first render
protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        await JS.InvokeVoidAsync("applyTheme");
    }
}

  • BWFC023 — IPostBackEventHandler usage
  • BWFC024 — ScriptManager code-behind usage

Configuration

To suppress this warning for a specific line:

#pragma warning disable BWFC022
Page.ClientScript.RegisterStartupScript(/* ... */);
#pragma warning restore BWFC022

Or in .editorconfig:

[*.cs]
dotnet_diagnostic.BWFC022.severity = silent

See Also


Status: ✅ Active
Last Updated: 2026-07-30
Owner: Beast (Technical Writer)