ClientScript Migration Patterns
This sample demonstrates how to migrate common ASP.NET Web Forms Page.ClientScript
and ScriptManager JavaScript patterns to Blazor using IJSRuntime,
component lifecycle events, and native Blazor event handling.
Migration Principle
Prefer IJSRuntime over wrapper shims. Blazor's component model replaces
the Web Forms postback lifecycle — use OnAfterRenderAsync for DOM-touching JavaScript
and @onclick / EventCallback for event handling.
1. Startup Script Migration
Before (Web Forms)
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
Page.ClientScript.RegisterStartupScript(
this.GetType(),
"InitializeUI",
"initializePage('Page loaded!');",
addScriptTags: true);
}
}After (Blazor)
@inject IJSRuntime JS
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("initializePage",
"Page loaded via IJSRuntime!");
}
}
}Live Demo — Startup Script
The message below was set by IJSRuntime calling initializePage()
during OnAfterRenderAsync(firstRender: true):
⏳ Waiting for OnAfterRenderAsync...
Key point: OnAfterRenderAsync with if (firstRender)
is the Blazor equivalent of if (!IsPostBack) +
RegisterStartupScript. It runs after the component's DOM is available.
2. Script Include Migration
Before (Web Forms)
protected void Page_Load(object sender, EventArgs e)
{
Page.ClientScript.RegisterClientScriptInclude(
"customScript",
ResolveUrl("~/Scripts/custom.js"));
}After (Blazor)
<!-- Option A: Static tag in App.razor or layout -->
<script src="js/clientscript-demo.js"></script>
<!-- Option B: Dynamic import via IJSRuntime -->
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// Dynamic module import
await JS.InvokeVoidAsync("import", "./js/my-module.js");
}
}
}Live Demo — Script Include
This page includes js/clientscript-demo.js via a <script>
tag in the layout. The startup script demo above calls a function defined in that file.
Script include status: Loaded ✓
Key point: Web Forms' RegisterClientScriptInclude() becomes a
static <script src="..."> tag. For conditional loading, use
IJSRuntime.InvokeAsync("import", ...).
3. Focus Management (replacing ScriptManager.SetFocus)
Before (Web Forms)
protected void Page_Load(object sender, EventArgs e)
{
// Using ScriptManager
ScriptManager.GetCurrent(Page).SetFocus(txtSearch);
// Or using ClientScript
Page.SetFocus("txtSearch");
}After (Blazor)
// Option A: ElementReference + FocusAsync (preferred)
<input @ref="_searchBox" type="text" />
@code {
private ElementReference _searchBox;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await _searchBox.FocusAsync();
}
}
}Live Demo — Focus Management
Click the button to set focus to the search box using ElementReference.FocusAsync():
Focus set count: 0
Key point: ElementReference.FocusAsync() is the direct replacement
for ScriptManager.SetFocus(). No JavaScript needed — it's built into Blazor.
4. Event Handler Migration (replacing GetPostBackEventReference)
Before (Web Forms)
// IPostBackEventHandler implementation
public class MyControl : UserControl, IPostBackEventHandler
{
public void RaisePostBackEvent(string eventArgument)
{
if (eventArgument == "delete")
OnDeleteRequested?.Invoke(this, EventArgs.Empty);
}
// Generate client-side postback trigger
string script = Page.ClientScript.GetPostBackEventReference(
new PostBackOptions(this, "delete"));
// Produces: __doPostBack('ctl00$MyControl','delete')
}After (Blazor)
// Direct event binding — no postback needed
<button @onclick="HandleDelete">Delete</button>
<button @onclick='() => HandleAction("archive")'>Archive</button>
@code {
[Parameter]
public EventCallback OnDeleteRequested { get; set; }
private async Task HandleDelete()
{
await OnDeleteRequested.InvokeAsync();
}
private async Task HandleAction(string action)
{
_lastAction = action;
}
}Live Demo — Event Handlers
In Web Forms, events went through __doPostBack() and IPostBackEventHandler.
In Blazor, events are direct method calls:
Key point: Blazor's @onclick and EventCallback
completely replace __doPostBack() and GetPostBackEventReference().
Events are strongly-typed C# method calls — no string-based event arguments needed.
5. DOM Manipulation (replacing RegisterClientScriptBlock)
Before (Web Forms)
Page.ClientScript.RegisterClientScriptBlock(
this.GetType(),
"HighlightRow",
"function highlightRow(id) { " +
" document.getElementById(id).style.backgroundColor = '#ffc'; " +
"}",
addScriptTags: true);After (Blazor)
// Define function in a .js file, call via IJSRuntime
await JS.InvokeVoidAsync("highlightElement",
"my-element", "#ffffcc");
// Or better — use Blazor's built-in binding:
<div style="@(_isHighlighted ? "background:#ffc" : "")">
Content
</div>Live Demo — DOM Manipulation
Two approaches: JS interop for complex DOM work, or Blazor binding for simple styling:
Via IJSRuntime:
IJSRuntimeVia Blazor Binding (Preferred):
@bindKey point: For simple styling, use Blazor's binding (style="@(...)").
Reserve IJSRuntime for cases where you need to call third-party libraries or
manipulate the DOM in ways Blazor can't express.
Source Code
Complete @code block for this page:
@inject IJSRuntime JS
@code {
private ElementReference _searchBox;
private int _focusCount;
private bool _isHighlighted;
private string _scriptIncludeStatus = "Loaded ✓";
private List<string> _actionLog = new();
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// Startup script — equivalent of RegisterStartupScript
await JS.InvokeVoidAsync("initializePage", "Page loaded via IJSRuntime!");
}
}
private async Task SetFocusToSearch()
{
// Equivalent of ScriptManager.SetFocus()
await _searchBox.FocusAsync();
_focusCount++;
}
private void HandleDelete()
{
_actionLog.Insert(0, $"[{DateTime.Now:HH:mm:ss}] 🗑️ Delete requested");
}
private void HandleAction(string action)
{
_actionLog.Insert(0, $"[{DateTime.Now:HH:mm:ss}] Action: {action}");
}
private async Task HighlightViaJs()
{
await JS.InvokeVoidAsync("highlightElement", "js-highlight-target", "#fff3cd");
}
private void ToggleHighlight()
{
_isHighlighted = !_isHighlighted;
}
}
Migration Quick Reference
| Web Forms Pattern | Blazor Equivalent | Notes |
|---|---|---|
RegisterStartupScript() |
OnAfterRenderAsync + IJSRuntime |
Guard with if (firstRender) |
RegisterClientScriptInclude() |
<script src="..."> in layout |
Static includes go in App.razor |
RegisterClientScriptBlock() |
Named JS function + IJSRuntime |
Move inline JS to .js files |
ScriptManager.SetFocus() |
ElementReference.FocusAsync() |
No JS interop needed |
GetPostBackEventReference() |
@onclick / EventCallback |
Direct method calls replace __doPostBack |
IPostBackEventHandler |
[Parameter] EventCallback |
Strongly-typed events replace string arguments |
if (!IsPostBack) |
if (firstRender) |
In OnAfterRenderAsync |