Skip to content

Commit 3e7c76c

Browse files
csharpfritzCopilot
andcommitted
docs: add 'migration shim, not destination' philosophy to ViewState
- Add warning admonition and comparison table to ViewStateAndPostBack.md showing how BWFC ViewState differs from Web Forms ViewState (opt-in, per-component, dirty-tracked, encrypted by default) - Add 'Graduating Off ViewState' section with before/after examples showing how to refactor toward native Blazor patterns - Update XML doc comments on ViewStateDictionary and BaseWebFormsComponent to reinforce the migration shim messaging Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2473136 commit 3e7c76c

3 files changed

Lines changed: 99 additions & 6 deletions

File tree

docs/UtilityFeatures/ViewStateAndPostBack.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# ViewState and PostBack Shim
22

3+
!!! warning "Migration Shim — Not a Destination"
4+
ViewState and IsPostBack are **migration compatibility features**. They exist so your Web Forms code-behind logic compiles and runs correctly in Blazor with minimal changes. Once your application is running, you should **refactor toward native Blazor patterns**`[Parameter]` properties, component fields, and cascading values. See [Graduating Off ViewState](#graduating-off-viewstate) below.
5+
36
## Overview
47

58
The ViewState and PostBack shim features enable seamless migration of ASP.NET Web Forms applications to Blazor by emulating the familiar `ViewState` dictionary and `IsPostBack` pattern. These features bridge the gap between traditional stateless HTTP POST workflows and Blazor's component-based stateful architecture.
@@ -11,6 +14,20 @@ The ViewState and PostBack shim features enable seamless migration of ASP.NET We
1114
- **Hidden Field Persistence** — Automatic round-tripping of ViewState through protected form fields in SSR
1215
- **Form State Continuity** — Seamless state management across SSR and ServerInteractive transitions
1316

17+
### How This Differs from Web Forms ViewState
18+
19+
The original Web Forms ViewState was a source of well-known problems: page bloat, security vulnerabilities, and invisible performance costs. Our implementation is fundamentally different:
20+
21+
| Concern | Web Forms ViewState | BWFC ViewState Shim |
22+
|---------|-------------------|-------------------|
23+
| **Default behavior** | On for every control, always serialized | **Off by default** — opt-in per component |
24+
| **Scope** | Single `__VIEWSTATE` blob for the entire page | **Per-component** isolated fields (`__bwfc_viewstate_{ID}`) |
25+
| **Serialization** | Every render, even unchanged controls | **Dirty tracking** — skips serialization when nothing changed |
26+
| **Security** | Unencrypted until .NET 4.5.2 MAC patch | **Encrypted + signed by default** (ASP.NET Core Data Protection, AES-256) |
27+
| **Format** | Opaque binary (`LosFormatter`) | **JSON** — human-readable, debuggable |
28+
| **Visibility** | No insight into payload size | **Size warnings** logged when threshold exceeded |
29+
| **Interactive mode** | N/A | **In-memory only** — no serialization overhead |
30+
1431
---
1532

1633
## ViewStateDictionary
@@ -607,6 +624,78 @@ public partial class ProductPage : System.Web.UI.Page
607624

608625
---
609626

627+
## Graduating Off ViewState
628+
629+
ViewState gets your Web Forms code running in Blazor. The next step is refactoring to native Blazor patterns. Here's how to migrate each common ViewState usage:
630+
631+
### Simple Values → Component Fields
632+
633+
=== "ViewState (Migration)"
634+
635+
```csharp
636+
// Web Forms pattern preserved during migration
637+
public int SelectedDepartmentId
638+
{
639+
get => ViewState.GetValueOrDefault<int>("SelectedDepartmentId");
640+
set => ViewState.Set("SelectedDepartmentId", value);
641+
}
642+
```
643+
644+
=== "Native Blazor (Target)"
645+
646+
```csharp
647+
// Refactored: simple field, no serialization overhead
648+
private int _selectedDepartmentId;
649+
```
650+
651+
### First-Load Guards → OnInitialized
652+
653+
=== "ViewState (Migration)"
654+
655+
```csharp
656+
protected override void OnInitialized()
657+
{
658+
if (!IsPostBack)
659+
{
660+
ViewState["Products"] = LoadProducts();
661+
}
662+
}
663+
```
664+
665+
=== "Native Blazor (Target)"
666+
667+
```csharp
668+
// OnInitialized already runs once per component instance
669+
protected override void OnInitialized()
670+
{
671+
_products = LoadProducts();
672+
}
673+
```
674+
675+
### Cross-Component State → Cascading Values or DI Services
676+
677+
=== "ViewState (Migration)"
678+
679+
```csharp
680+
// Parent stores state in ViewState, child reads it
681+
ViewState["SelectedCategory"] = category;
682+
```
683+
684+
=== "Native Blazor (Target)"
685+
686+
```razor
687+
<!-- Parent cascades value to children -->
688+
<CascadingValue Value="@_selectedCategory">
689+
@ChildContent
690+
</CascadingValue>
691+
```
692+
693+
### When ViewState Is Still Appropriate
694+
695+
ViewState remains useful for **SSR form round-trips** where you need state to survive an HTTP POST without JavaScript. This is a legitimate pattern in SSR Blazor — similar to hidden fields in any web framework. The key difference from Web Forms: you're choosing to use it, not having it imposed on every control.
696+
697+
---
698+
610699
## See Also
611700

612701
- [WebFormsPage](WebFormsPage.md) — Page-level wrapper combining naming scope and theming

src/BlazorWebFormsComponents/BaseWebFormsComponent.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,11 @@ void ParentWrappingBuildRenderTree(RenderTreeBuilder builder)
152152
/// In ServerInteractive mode, persists for the component's lifetime (in-memory).
153153
/// In SSR mode, round-trips via a protected hidden form field.
154154
///
155-
/// <para><b>Migration note:</b> This enables Web Forms ViewState-backed property
156-
/// patterns to work unchanged. For new Blazor code, prefer [Parameter] properties
157-
/// and component fields.</para>
155+
/// <para><b>Migration shim — not a destination.</b> This exists so Web Forms
156+
/// <c>ViewState["key"]</c> patterns compile and run correctly during migration.
157+
/// Once running, refactor to <c>[Parameter]</c> properties, component fields, or
158+
/// cascading values. Unlike Web Forms, this is opt-in, per-component, dirty-tracked,
159+
/// and encrypted by default.</para>
158160
/// </summary>
159161
public ViewStateDictionary ViewState { get; } = new();
160162

src/BlazorWebFormsComponents/ViewStateDictionary.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ namespace BlazorWebFormsComponents;
1414
/// In ServerInteractive mode, persists for the component's lifetime (in-memory).
1515
/// In SSR mode, round-trips via a protected hidden form field.
1616
///
17-
/// <para><b>Migration note:</b> This enables Web Forms ViewState-backed property
18-
/// patterns to work unchanged. For new Blazor code, prefer [Parameter] properties
19-
/// and component fields.</para>
17+
/// <para><b>Migration shim — not a destination.</b> This exists so Web Forms
18+
/// <c>ViewState["key"]</c> patterns compile and run correctly in Blazor during
19+
/// migration. Once running, refactor to <c>[Parameter]</c> properties, component
20+
/// fields, or cascading values. Unlike Web Forms ViewState, this implementation
21+
/// is opt-in, per-component, dirty-tracked, and encrypted by default.</para>
2022
/// </summary>
2123
public class ViewStateDictionary : IDictionary<string, object?>
2224
{

0 commit comments

Comments
 (0)