Diagnostic ID: BWFC024
Severity:
Category: Migration
Status: Active
This analyzer warns when you use ScriptManager code-behind methods like GetCurrent(), SetFocus(), RegisterAsyncPostBackControl(), and similar — Web Forms APIs for managing client scripts and UpdatePanel behavior.
Note: Phase 2 now includes support for GetCurrent() and script registration methods via ScriptManagerShim. Methods like SetFocus() and RegisterAsyncPostBackControl() still require modernization.
Detected patterns:
ScriptManager.GetCurrent(Page)/ScriptManager.GetCurrent(this)— ✅ Phase 2 supported.SetFocus(control)— ❌ Still requires JS interop.RegisterAsyncPostBackControl(control)— ❌ Still requires component binding.RegisterUpdateProgress(...)— ❌ Still requires component state.RegisterPostBackControl(...)— ❌ Not supported
protected void Page_Load(object sender, EventArgs e)
{
// ⚠️ BWFC024: ScriptManager.GetCurrent() and related methods are not available in Blazor.
ScriptManager sm = ScriptManager.GetCurrent(Page);
// Set focus
sm.SetFocus(txtSearchBox);
// Register for async postback
sm.RegisterAsyncPostBackControl(gvData);
}ScriptManager methods are deeply tied to Web Forms' postback and UpdatePanel architecture:
GetCurrent()retrieves the page's ScriptManager instanceSetFocus()ensures client focus after postbackRegisterAsyncPostBackControl()enables AJAX partial-page updatesRegisterUpdateProgress()shows progress during UpdatePanel operations
In Blazor:
- No
ScriptManagerinstance — script management is component-scoped - No postback lifecycle — focus handling is explicit
- No UpdatePanel model — components handle their own updates natively
- No async postback concept — Blazor uses real-time SignalR sync
These methods have no direct equivalents. Each requires a different approach.
The fix depends on which ScriptManager method you're using and which Phase you're in.
ScriptManager.GetCurrent() now returns a working ScriptManagerShim that delegates to ClientScriptShim.
=== "Web Forms (Before)"
csharp protected void Page_Load(object sender, EventArgs e) { ScriptManager sm = ScriptManager.GetCurrent(Page); sm.RegisterStartupScript(this.GetType(), "init", "initPage();", true); }
=== "Blazor (After — Phase 2, Zero Rewrite)"
csharp protected override void OnInitialized() { // Same code works! ScriptManagerShim returns the component's ClientScriptShim ScriptManager sm = ScriptManager.GetCurrent(this); sm.RegisterStartupScript(this.GetType(), "init", "initPage();", true); }
Other ScriptManager methods still require refactoring. See the fixes below.
Focus management in Blazor uses @ref and IJSRuntime.
=== "Web Forms (Before)"
csharp protected void Page_Load(object sender, EventArgs e) { ScriptManager.SetFocus(txtSearchBox); }
=== "Blazor (After)" ```razor @inject IJSRuntime JS
<input @ref="searchBox" />
@code {
private ElementReference searchBox;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("focus", searchBox);
}
}
}
```
Or define a helper function in JavaScript:
// app.js
export function focusElement(element) {
element?.focus();
}// Component
await module.InvokeVoidAsync("focusElement", searchBox);RegisterAsyncPostBackControl() enables partial-page AJAX updates via UpdatePanel.
Blazor does NOT support UpdatePanel-style AJAX postbacks. Blazor components handle all updates natively via parameter binding.
=== "Web Forms (Before)" ```csharp protected void Page_Load(object sender, EventArgs e) { // Enable AJAX partial updates for GridView ScriptManager.RegisterAsyncPostBackControl(gvData); }
protected void gvData_SelectedIndexChanged(object sender, EventArgs e)
{
// Partial page refresh in UpdatePanel
gvData.DataSource = GetUpdatedData();
gvData.DataBind();
}
```
=== "Blazor (After)" ```razor @page "/data" @inject HttpClient Http
<GridView Data="@items" OnRowSelected="@HandleRowSelected" />
@code {
private List<Item> items;
protected override async Task OnInitializedAsync()
{
items = await Http.GetFromJsonAsync<List<Item>>("/api/items");
}
private async Task HandleRowSelected(int itemId)
{
// Component automatically re-renders when data changes
items = await Http.GetFromJsonAsync<List<Item>>("/api/items");
}
}
```
Key difference: Blazor components automatically re-render when state changes. No manual registration needed.
RegisterUpdateProgress() shows a message or spinner during UpdatePanel operations.
=== "Web Forms (Before)" ```csharp ScriptManager.RegisterUpdateProgress(updateProgress, masterUpdateProgress);
// During async postback, the UpdateProgress displays
protected void LongRunningOperation()
{
System.Threading.Thread.Sleep(5000); // Simulates work
}
```
=== "Blazor (After)" ```razor @if (isLoading) {
Loading data...
<GridView Data="@items" />
@code {
private List<Item> items;
private bool isLoading;
private async Task LoadData()
{
isLoading = true;
try
{
items = await Http.GetFromJsonAsync<List<Item>>("/api/items");
}
finally
{
isLoading = false;
}
}
}
```
ScriptManager.GetCurrent() retrieves the page's ScriptManager instance for other operations.
In Blazor, there is no page-level ScriptManager. Remove calls to GetCurrent() and replace the specific methods (SetFocus, RegisterAsyncPostBackControl, etc.) with the patterns above.
=== "Web Forms (Before)"
csharp ScriptManager sm = ScriptManager.GetCurrent(Page); sm.SetFocus(txtField); sm.RegisterAsyncPostBackControl(gridView);
=== "Blazor (After)" ```razor @inject IJSRuntime JS
<input @ref="field" />
<GridView Data="@items" OnRowSelected="@HandleRowSelected" />
@code {
private ElementReference field;
private List<Item> items;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("focus", field);
}
}
private async Task HandleRowSelected(int id)
{
// Update component state; Blazor handles re-render
items = await FetchDataAsync();
}
}
```
| Web Forms Method | Blazor Equivalent | Approach |
|---|---|---|
GetCurrent(Page) |
ScriptManager.GetCurrent(this) (Phase 2 — Zero Rewrite) |
Easy |
.SetFocus(control) |
JS.InvokeVoidAsync("focus", @ref) |
JavaScript interop |
.RegisterAsyncPostBackControl() |
Component parameter binding | Remove; use @bind or EventCallback |
.RegisterPostBackControl() |
— | Remove; not needed in Blazor |
.RegisterUpdateProgress() |
Component state flag | Use @if (isLoading) UI binding |
.IsInAsyncPostBack |
— | Remove; always synchronous in Blazor |
=== "Web Forms (Before)" ```csharp public partial class SearchPage : Page { protected void Page_Load(object sender, EventArgs e) { ScriptManager sm = ScriptManager.GetCurrent(Page); sm.SetFocus(txtSearchBox); sm.RegisterUpdateProgress(updateProgress, masterUpdateProgress); }
protected void btnSearch_Click(object sender, EventArgs e)
{
// Long-running search
var results = SearchDatabase(txtSearchBox.Text);
gvResults.DataSource = results;
gvResults.DataBind();
// UpdateProgress shows during postback
}
}
```
=== "Blazor (After)" ```razor @page "/search" @inject IJSRuntime JS @inject HttpClient Http
<input @ref="searchBox" placeholder="Search..." />
<button @onclick="PerformSearch" disabled="@isLoading">Search</button>
@if (isLoading)
{
<div class="loading-overlay">
<p>Searching...</p>
<div class="spinner"></div>
</div>
}
<GridView Data="@results" />
@code {
private ElementReference searchBox;
private string searchQuery;
private List<SearchResult> results = new();
private bool isLoading;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("focus", searchBox);
}
}
private async Task PerformSearch()
{
if (string.IsNullOrWhiteSpace(searchQuery)) return;
isLoading = true;
try
{
results = await Http.GetFromJsonAsync<List<SearchResult>>(
$"/api/search?q={Uri.EscapeDataString(searchQuery)}");
}
finally
{
isLoading = false;
}
}
}
public class SearchResult
{
public int Id { get; set; }
public string Title { get; set; }
}
```
// ❌ WRONG: ScriptManager.GetCurrent() returns null in Blazor
ScriptManager sm = ScriptManager.GetCurrent(Page); // null
sm.SetFocus(txtField); // NullReferenceException!// ✅ CORRECT: Use @ref and IJSRuntime
@ref="field"
await JS.InvokeVoidAsync("focus", field);// ❌ WRONG: UpdatePanel AJAX is not supported
ScriptManager.RegisterAsyncPostBackControl(gridView);
// gridView.DataBind() won't trigger partial refresh// ✅ CORRECT: Component re-renders on state change
items = await FetchUpdatedData();
// Blazor automatically syncs the UI// ❌ WRONG: No async postback concept in Blazor
if (ScriptManager.GetCurrent(Page).IsInAsyncPostBack)
{
// This code path doesn't exist
}// ✅ CORRECT: Blazor components are always "interactive"
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// Component is interactive; safe to use JS interop
}
}- BWFC022 — Page.ClientScript usage (see ClientScriptShim for easy migration)
- BWFC023 — IPostBackEventHandler usage
To suppress this warning for a specific line:
#pragma warning disable BWFC024
ScriptManager.GetCurrent(Page).SetFocus(txtField);
#pragma warning restore BWFC024Or in .editorconfig:
[*.cs]
dotnet_diagnostic.BWFC024.severity = silent- 📖 ClientScriptMigrationGuide.md — Section 7: ScriptManager Patterns
- 📖 IJSRuntime Documentation — JavaScript interop
- 📖 Component Lifecycle — OnAfterRenderAsync
- 📖 EditForm Component — Form handling in Blazor
Status: ✅ Active
Last Updated: 2026-07-30
Owner: Beast (Technical Writer)