Skip to content

feat: ViewState + PostBack shim - Phase 1 core implementation#503

Open
csharpfritz wants to merge 6 commits intoFritzAndFriends:devfrom
csharpfritz:feature/viewstate-postback-shim
Open

feat: ViewState + PostBack shim - Phase 1 core implementation#503
csharpfritz wants to merge 6 commits intoFritzAndFriends:devfrom
csharpfritz:feature/viewstate-postback-shim

Conversation

@csharpfritz
Copy link
Collaborator

Summary

Phase 1 of the ViewState + PostBack shim infrastructure for near-zero-change ASCX migration.

Included

  • ViewStateDictionary - IDictionary<string,object?>, null-safe indexer, JSON serialization, IDataProtector encryption for SSR
  • Mode-adaptive IsPostBack - SSR checks HTTP POST, Interactive tracks lifecycle
  • WebFormsRenderMode enum + CurrentRenderMode property
  • RenderViewStateField for SSR hidden field emission
  • [Obsolete] removed from ViewState (now a real migration feature)
  • 73 new tests, 2588 total passing

Breaking changes

  • ViewState indexer returns null for missing keys (was KeyNotFoundException)
  • IsPostBack no longer always false
  • [Obsolete] removed from ViewState

See dev-docs/architecture/ViewState-PostBack-Shim-Proposal.md for the full 5-phase plan.

csharpfritz and others added 6 commits March 24, 2026 11:13
Implements the core ViewState/PostBack infrastructure per the approved
architecture proposal:

- ViewStateDictionary: IDictionary<string, object?> implementation with
  null-safe indexer (Web Forms compat), GetValueOrDefault<T>/Set<T>
  convenience methods, IsDirty tracking, IDataProtector-based
  Serialize/Deserialize, JsonElement type coercion for round-trip fidelity

- BaseWebFormsComponent: ViewState upgraded from Dictionary<string,object>
  to ViewStateDictionary, [Obsolete] removed, IsPostBack with mode-adaptive
  logic (SSR: HTTP method check, Interactive: _hasInitialized flag),
  CurrentRenderMode/IsHttpContextAvailable properties, RenderViewStateField
  for SSR hidden field emission, IDataProtectionProvider injection,
  ViewState deserialization from form POST in OnInitializedAsync

- WebFormsPageBase: ViewState upgraded to ViewStateDictionary, [Obsolete]
  removed, IsPostBack with same mode-adaptive logic, OnInitialized override
  to track _hasInitialized

- WebFormsRenderMode enum: StaticSSR, InteractiveServer

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…WebFormsRenderMode

ViewStateDictionaryTests.cs (48 tests):
- Basic dictionary operations (indexer, ContainsKey, Remove, Clear, Count)
- Null safety (missing key returns null, null value storage)
- Type coercion (int/bool cast, Set<T>/GetValueOrDefault<T>)
- IsDirty tracking (creation, set, add, remove, clear, MarkClean)
- Serialization roundtrip with EphemeralDataProtectionProvider
- JSON type coercion after deserialization (int, bool, string, double, DateTime)
- Edge cases (100K strings, special characters in keys)
- IDictionary<string, object?> interface compliance
- Web Forms migration pattern validation (ViewState-backed property)
- LoadFrom merge behavior

IsPostBackTests.cs (14 tests):
- BaseWebFormsComponent: interactive mode (false during init, true after)
- BaseWebFormsComponent: SSR GET=false, POST=true
- WebFormsPageBase: same pattern for both modes
- Guard pattern (!IsPostBack) block execution/skip

WebFormsRenderModeTests.cs (7 tests):
- Enum has StaticSSR and InteractiveServer (exactly 2 values)
- CurrentRenderMode returns StaticSSR with HttpContext
- CurrentRenderMode returns InteractiveServer without HttpContext
- IsHttpContextAvailable mirrors HttpContext presence

Also adds InternalsVisibleTo for test project access to internal
ViewStateDictionary members (IsDirty, MarkClean, Serialize, Deserialize).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- ViewStateDictionary: IDictionary<string, object?> with null-safe indexer,
  type coercion (JsonElement  T), dirty tracking, IDataProtector
  serialize/deserialize for SSR hidden field persistence
- IsPostBack: mode-adaptive (SSR: HTTP method check, Interactive: lifecycle)
- WebFormsRenderMode enum (StaticSSR, InteractiveServer)
- RenderViewStateField for SSR hidden field emission
- DataProtectionProvider resolved lazily (optional, backward-compatible)
- Removed [Obsolete] from ViewState (now a real feature)
- Updated legacy tests for new behavior (null for missing keys, IsPostBack
  true after init in Interactive mode)
- 73 new contract tests + 2588 total tests passing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…cision consolidation

Session: 2026-03-24T15-30-viewstate-phase1-complete
Requested by: Jeffrey T. Fritz

Changes:
- Added orchestration log: 2026-03-24T15-30-coordinator.md (ViewState test fix & commit)
- Added session log: 2026-03-24T15-30-viewstate-phase1-complete.md (full Phase 1 summary)
- Merged 7 inbox decisions  decisions.md (ViewState/PostBack architecture, NuGet assets, user directives, AfterDepartmentPortal setup)
- Deleted inbox files (copilot-directive-*, cyclops-viewstate-impl, forge-nuget-asset-strategy, forge-viewstate-postback-architecture, jubilee-runnable-demo, rogue-viewstate-tests)
- Updated cross-agent history: Cyclops, Rogue, Forge (team updates appended with ViewState Phase 1 outcomes)

Related commits:
- 1bf5cde: ViewState Phase 1 implementation (2588/2588 tests passing)
- f7119a0: Cyclops Phase 1 impl
- 879678e: Rogue tests
- 6ca64de: Rogue history
- be2794a: formatting fix

All Phase 1 work consolidated. Phase 2 (SSR persistence, AutoPostBack, analyzers, docs) planned for 7 weeks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
[Fact]
public void ContainsKey_MissingKey_ReturnsFalse()
{
var dict = new ViewStateDictionary();

Check failure

Code scanning / CodeQL

Container contents are never initialized Error test

The contents of this container are never initialized.
[Fact]
public void Indexer_MissingKey_ReturnsNull()
{
var dict = new ViewStateDictionary();

Check failure

Code scanning / CodeQL

Container contents are never initialized Error test

The contents of this container are never initialized.
[Fact]
public void IsDirty_FalseOnCreation()
{
var dict = new ViewStateDictionary();

Check failure

Code scanning / CodeQL

Container contents are never initialized Error test

The contents of this container are never initialized.
[Fact]
public void Add_DuplicateKey_Throws()
{
var dict = new ViewStateDictionary();

Check failure

Code scanning / CodeQL

Container contents are never accessed Error test

The contents of this container are never accessed.
[Fact]
public void TryGetValue_MissingKey_ReturnsFalse()
{
var dict = new ViewStateDictionary();

Check failure

Code scanning / CodeQL

Container contents are never initialized Error test

The contents of this container are never initialized.
{
var dict = new ViewStateDictionary();

(dict is IDictionary<string, object?>).ShouldBeTrue();

Check warning

Code scanning / CodeQL

Useless type test Warning test

There is no need to test whether an instance of
ViewStateDictionary
is also an instance of
IDictionary<String,Object>
- it always is.
dict["b"] = 2;

var count = 0;
foreach (var kvp in dict)

Check warning

Code scanning / CodeQL

Useless assignment to local variable Warning test

This assignment to
kvp
is useless, since its value is never read.
public void CurrentRenderMode_WithoutHttpContext_ReturnsInteractiveServer()
{
var mock = new Mock<IHttpContextAccessor>();
mock.Setup(x => x.HttpContext).Returns((HttpContext?)null);

Check warning

Code scanning / CodeQL

Useless upcast Warning test

There is no need to upcast from
null
to
HttpContext
- the conversion can be done implicitly.
public void IsHttpContextAvailable_WithoutHttpContext_ReturnsFalse()
{
var mock = new Mock<IHttpContextAccessor>();
mock.Setup(x => x.HttpContext).Returns((HttpContext?)null);

Check warning

Code scanning / CodeQL

Useless upcast Warning test

There is no need to upcast from
null
to
HttpContext
- the conversion can be done implicitly.

dict["nullable"] = null;

dict.ContainsKey("nullable").ShouldBeTrue();

Check notice

Code scanning / CodeQL

Inefficient use of ContainsKey Note test

Inefficient use of 'ContainsKey' and
indexer
.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant