From fc5f49779c054e2707e1d65e6deb1dc20843e4e3 Mon Sep 17 00:00:00 2001 From: Trevor Forrester Date: Mon, 13 Jul 2026 20:41:26 +1000 Subject: [PATCH 1/3] Fix BlogPermissionsGrid ViewState exception Prevent restoring dynamically generated permission-grid child ViewState after Blog permission changes. --- .../Security/Controls/BlogPermissionsGrid.cs | 71 +++++++++++-------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/Server/Core/Security/Controls/BlogPermissionsGrid.cs b/Server/Core/Security/Controls/BlogPermissionsGrid.cs index 3ec30c5e..43a1d2fc 100644 --- a/Server/Core/Security/Controls/BlogPermissionsGrid.cs +++ b/Server/Core/Security/Controls/BlogPermissionsGrid.cs @@ -423,49 +423,57 @@ protected override ArrayList GetUsers() /// ----------------------------------------------------------------------------- protected override void LoadViewState(object savedState) { + if (savedState == null) + { + return; + } - if (savedState != null) + var myState = savedState as object[]; + if (myState == null || myState.Length < 4) { - // Load State from the array of objects that was saved with SaveViewState. + return; + } - object[] myState = (object[])savedState; + // Do not restore the generated DataGrid child-control viewstate. + // The role/user rows are rebuilt from the current DNN role and + // permission data and may not have the same positional structure + // as the previous request. Restoring their state by index causes + // ASP.NET's "control tree must match" exception. - // Load Base Controls ViewState - if (myState[0] != null) - { - base.LoadViewState(myState[0]); - } + if (myState[1] != null) + { + _BlogID = Convert.ToInt32(myState[1]); + } - // Load BlogID - if (myState[1] != null) - { - BlogID = Convert.ToInt32(myState[1]); - } + if (myState[2] != null) + { + _currentUserId = Convert.ToInt32(myState[2]); + } - if (myState[2] != null) - { - CurrentUserId = Convert.ToInt32(myState[2]); - } + if (myState[3] != null) + { + var arrPermissions = new ArrayList(); + string state = Convert.ToString(myState[3]); - // Load BlogPermissions - if (myState[3] != null) + if (!string.IsNullOrEmpty(state)) { - var arrPermissions = new ArrayList(); - string state = Convert.ToString(myState[3]); - if (!string.IsNullOrEmpty(state)) + string[] permissionKeys = state.Split( + new[] { "##" }, + StringSplitOptions.RemoveEmptyEntries); + + foreach (string key in permissionKeys) { - // First Break the String into individual Keys - string[] permissionKeys = state.Split("##".ToCharArray()); - foreach (string key in permissionKeys) + string[] settings = key.Split('|'); + + if (settings.Length >= 7) { - string[] Settings = key.Split('|'); - ParsePermissionKeys(Settings, arrPermissions); + ParsePermissionKeys(settings, arrPermissions); } } - _BlogPermissions = new BlogPermissionCollection(arrPermissions); } - } + _BlogPermissions = new BlogPermissionCollection(arrPermissions); + } } /// ----------------------------------------------------------------------------- @@ -483,7 +491,10 @@ protected override object SaveViewState() var allStates = new object[4]; - allStates[0] = base.SaveViewState(); + // Do not persist the generated child-grid viewstate. The grid is + // rebuilt from roles and permissions on every request, and its + // child ordering can change after a permission edit. + allStates[0] = null; allStates[1] = BlogID; allStates[2] = CurrentUserId; From 7565178a55cd00abe0e960cb6e04b9ed2c497334 Mon Sep 17 00:00:00 2001 From: Trevor Forrester Date: Tue, 14 Jul 2026 16:53:10 +1000 Subject: [PATCH 2/3] Fix BlogPermissionsGrid ViewState mismatch after permission changes ## Summary This pull request fixes a ViewState exception that occurs when editing Blog settings after Blog permissions have been changed. The issue is caused by `BlogPermissionsGrid` saving and restoring the ViewState of dynamically generated child controls. After permission changes, the generated control tree can differ between requests, causing ASP.NET to throw: ``` Failed to load viewstate. The control tree into which viewstate is being loaded must match the control tree that was used to save viewstate during the previous request. ``` ## Root cause `BlogPermissionsGrid` persisted the base child-control ViewState: ```csharp allStates[0] = base.SaveViewState(); ``` and later restored it using: ```csharp base.LoadViewState(myState[0]); ``` Because the permissions grid is dynamically generated, the child-control structure can legitimately change after permission updates, making the stored ViewState invalid. ## Fix - Do not persist dynamically generated child-control ViewState. - Continue to persist: - Blog ID - Current User ID - Blog permission collection - Rebuild the permissions grid on each request. ## Testing Verified successfully on DNN Platform 10.3.2 / .NET Framework 4.8. Tested by: - Editing existing Blogs - Creating a second Blog - Repeated Blog permission changes - Updating Blog settings - Creating posts - Editing posts - Uploading images to posts The fix was also validated on a production site running the unmodified Blog 6.7.1 release by replacing only `DotNetNuke.Modules.Blog.Core.dll`. --- Server/Core/Security/Controls/BlogPermissionsGrid.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Server/Core/Security/Controls/BlogPermissionsGrid.cs b/Server/Core/Security/Controls/BlogPermissionsGrid.cs index 43a1d2fc..e6f6d7b5 100644 --- a/Server/Core/Security/Controls/BlogPermissionsGrid.cs +++ b/Server/Core/Security/Controls/BlogPermissionsGrid.cs @@ -434,11 +434,8 @@ protected override void LoadViewState(object savedState) return; } - // Do not restore the generated DataGrid child-control viewstate. - // The role/user rows are rebuilt from the current DNN role and - // permission data and may not have the same positional structure - // as the previous request. Restoring their state by index causes - // ASP.NET's "control tree must match" exception. + // Do not restore dynamically generated child-control ViewState. + // The permissions grid is rebuilt on each request. if (myState[1] != null) { @@ -491,9 +488,7 @@ protected override object SaveViewState() var allStates = new object[4]; - // Do not persist the generated child-grid viewstate. The grid is - // rebuilt from roles and permissions on every request, and its - // child ordering can change after a permission edit. + // Do not persist dynamically generated child-control ViewState. allStates[0] = null; allStates[1] = BlogID; allStates[2] = CurrentUserId; From 5217b65091af38b11080728e4422f6c52bab4ff2 Mon Sep 17 00:00:00 2001 From: Trevor Forrester Date: Tue, 14 Jul 2026 17:10:47 +1000 Subject: [PATCH 3/3] Fix ViewState exception when editing Blog settings after permission changes Tested successfully on DNN Platform 10.3.2 / .NET Framework 4.8 with SQL Server. Verified by: editing existing Blogs creating a second Blog repeated permission changes changing Blog settings creating and editing posts uploading images validating on a production site running the stock Blog 6.7.1 release --- Server/Core/Security/Controls/BlogPermissionsGrid.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/Core/Security/Controls/BlogPermissionsGrid.cs b/Server/Core/Security/Controls/BlogPermissionsGrid.cs index e6f6d7b5..0137eb3e 100644 --- a/Server/Core/Security/Controls/BlogPermissionsGrid.cs +++ b/Server/Core/Security/Controls/BlogPermissionsGrid.cs @@ -435,7 +435,7 @@ protected override void LoadViewState(object savedState) } // Do not restore dynamically generated child-control ViewState. - // The permissions grid is rebuilt on each request. + // The permissions grid is rebuilt on each request from the persisted permission data. if (myState[1] != null) { @@ -488,7 +488,7 @@ protected override object SaveViewState() var allStates = new object[4]; - // Do not persist dynamically generated child-control ViewState. + // Persist only the permission data, not the generated child-control ViewState. allStates[0] = null; allStates[1] = BlogID; allStates[2] = CurrentUserId;