diff --git a/Directory.Packages.props b/Directory.Packages.props
index c2f185ffbd..bba4a14a78 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -55,6 +55,7 @@
+
diff --git a/src/Altinn.App.Core/Features/IFormDataValidator.cs b/src/Altinn.App.Core/Features/IFormDataValidator.cs
index c9221afac8..7caa3f2eec 100644
--- a/src/Altinn.App.Core/Features/IFormDataValidator.cs
+++ b/src/Altinn.App.Core/Features/IFormDataValidator.cs
@@ -30,13 +30,19 @@ public interface IFormDataValidator
///
string ValidationSource => $"{this.GetType().FullName}-{DataType}";
- ///
- /// If you override this to return true, the validator will only run on process/next, and not continuously.
- /// will never get called
- ///
- ///
+ ///
+ ///
+ /// will run on incremental changes using .
+ ///
bool NoIncrementalValidation => false;
+ ///
+ ///
+ /// Defaults to full data. When true, the pipeline provides a cleaned data accessor to
+ /// both and for consistent visibility.
+ ///
+ bool ShouldRunAfterRemovingHiddenData => false;
+
///
/// The actual validation function
///
diff --git a/src/Altinn.App.Core/Features/IInstanceDataAccessor.cs b/src/Altinn.App.Core/Features/IInstanceDataAccessor.cs
index 37435d5798..0a1bb43fd1 100644
--- a/src/Altinn.App.Core/Features/IInstanceDataAccessor.cs
+++ b/src/Altinn.App.Core/Features/IInstanceDataAccessor.cs
@@ -1,3 +1,4 @@
+using Altinn.App.Core.Helpers;
using Altinn.App.Core.Models;
using Altinn.Platform.Storage.Interface.Models;
@@ -32,6 +33,18 @@ public interface IInstanceDataAccessor
/// when identifier does not exist in instance.Data with an applogic data type
Task GetFormDataWrapper(DataElementIdentifier dataElementIdentifier);
+ ///
+ /// Get a that provides access to the cleaned data (where all fields marked as "hidden" are removed).
+ ///
+ /// The strategy for "hiddenRow" on group components
+ IInstanceDataAccessor GetCleanAccessor(RowRemovalOption rowRemovalOption = RowRemovalOption.SetToNull);
+
+ ///
+ /// Get a that provides access to the
+ /// storage persisted before any in-memory changes in this request.
+ ///
+ IInstanceDataAccessor GetPreviousDataAccessor();
+
///
/// Gets the raw binary data from a DataElement.
///
diff --git a/src/Altinn.App.Core/Features/IValidator.cs b/src/Altinn.App.Core/Features/IValidator.cs
index a5b93955ab..17c463311f 100644
--- a/src/Altinn.App.Core/Features/IValidator.cs
+++ b/src/Altinn.App.Core/Features/IValidator.cs
@@ -39,6 +39,15 @@ public interface IValidator
///
bool NoIncrementalValidation => false;
+ ///
+ /// Indicates whether this validator should run against a cleaned view of the data where fields marked as hidden are removed.
+ ///
+ ///
+ /// Defaults to false. When true, the validation pipeline will supply a cleaned accessor for both
+ /// and , ensuring consistent visibility semantics.
+ ///
+ bool ShouldRunAfterRemovingHiddenData => false;
+
///
/// Run this validator and return all the issues this validator is aware of.
///
diff --git a/src/Altinn.App.Core/Features/Telemetry/Telemetry.Validation.cs b/src/Altinn.App.Core/Features/Telemetry/Telemetry.Validation.cs
index b309f800ff..a5678dba1f 100644
--- a/src/Altinn.App.Core/Features/Telemetry/Telemetry.Validation.cs
+++ b/src/Altinn.App.Core/Features/Telemetry/Telemetry.Validation.cs
@@ -47,7 +47,8 @@ private static void InitValidation(InitContext context)
ActivitySource
.StartActivity($"{Prefix}.RunValidator")
?.SetTag(InternalLabels.ValidatorType, validator.GetType().Name)
- .SetTag(InternalLabels.ValidatorSource, validator.ValidationSource);
+ .SetTag(InternalLabels.ValidatorSource, validator.ValidationSource)
+ .SetTag(InternalLabels.ValidatorRemoveHiddenData, validator.ShouldRunAfterRemovingHiddenData);
internal static class Validation
{
diff --git a/src/Altinn.App.Core/Features/Telemetry/Telemetry.cs b/src/Altinn.App.Core/Features/Telemetry/Telemetry.cs
index 9f6c218167..5570042179 100644
--- a/src/Altinn.App.Core/Features/Telemetry/Telemetry.cs
+++ b/src/Altinn.App.Core/Features/Telemetry/Telemetry.cs
@@ -228,6 +228,7 @@ internal static class InternalLabels
internal const string AuthorizerTaskId = "authorization.authorizer.task.id";
internal const string ValidatorType = "validator.type";
internal const string ValidatorSource = "validator.source";
+ internal const string ValidatorRemoveHiddenData = "validator.remove_hidden_data";
internal const string ValidatorHasRelevantChanges = "validator.has_relevant_changes";
internal const string ValidatorChangedElementsIds = "validator.changed_elements_ids";
internal const string ValidatorIssueCount = "validation.issue_count";
diff --git a/src/Altinn.App.Core/Features/Validation/Default/LegacyIInstanceValidatorFormDataValidator.cs b/src/Altinn.App.Core/Features/Validation/Default/LegacyIInstanceValidatorFormDataValidator.cs
index d2d9ebf4cc..e20e66bd61 100644
--- a/src/Altinn.App.Core/Features/Validation/Default/LegacyIInstanceValidatorFormDataValidator.cs
+++ b/src/Altinn.App.Core/Features/Validation/Default/LegacyIInstanceValidatorFormDataValidator.cs
@@ -42,7 +42,7 @@ public string ValidationSource
{
var type = _instanceValidator?.GetType() ?? GetType();
Debug.Assert(type.FullName is not null, "FullName does not return null on class/struct types");
- return type.FullName;
+ return type.FullName + "_FormData";
}
}
diff --git a/src/Altinn.App.Core/Features/Validation/Default/LegacyIInstanceValidatorTaskValidator.cs b/src/Altinn.App.Core/Features/Validation/Default/LegacyIInstanceValidatorTaskValidator.cs
index 89ff5ff318..8d449a1205 100644
--- a/src/Altinn.App.Core/Features/Validation/Default/LegacyIInstanceValidatorTaskValidator.cs
+++ b/src/Altinn.App.Core/Features/Validation/Default/LegacyIInstanceValidatorTaskValidator.cs
@@ -43,7 +43,7 @@ public string ValidationSource
{
var type = _instanceValidator.GetType();
Debug.Assert(type.FullName is not null, "FullName does not return null on class/struct types");
- return type.FullName;
+ return type.FullName + "_Task";
}
}
diff --git a/src/Altinn.App.Core/Features/Validation/GenericFormDataValidator.cs b/src/Altinn.App.Core/Features/Validation/GenericFormDataValidator.cs
index a420be5b94..7a5fca5462 100644
--- a/src/Altinn.App.Core/Features/Validation/GenericFormDataValidator.cs
+++ b/src/Altinn.App.Core/Features/Validation/GenericFormDataValidator.cs
@@ -32,6 +32,9 @@ protected GenericFormDataValidator(string dataType)
///
public virtual bool NoIncrementalValidation => false;
+ ///
+ public virtual bool ShouldRunAfterRemovingHiddenData => false;
+
// ReSharper disable once StaticMemberInGenericType
private static readonly AsyncLocal> _validationIssues = new();
diff --git a/src/Altinn.App.Core/Features/Validation/Wrappers/FormDataValidatorWrapper.cs b/src/Altinn.App.Core/Features/Validation/Wrappers/FormDataValidatorWrapper.cs
index 0f0825fe9e..24dd0d53bd 100644
--- a/src/Altinn.App.Core/Features/Validation/Wrappers/FormDataValidatorWrapper.cs
+++ b/src/Altinn.App.Core/Features/Validation/Wrappers/FormDataValidatorWrapper.cs
@@ -35,6 +35,9 @@ IDataElementAccessChecker dataElementAccessChecker
///
public bool NoIncrementalValidation => _formDataValidator.NoIncrementalValidation;
+ ///
+ public bool ShouldRunAfterRemovingHiddenData => _formDataValidator.ShouldRunAfterRemovingHiddenData;
+
///
/// Run all legacy instances for the given .
///
diff --git a/src/Altinn.App.Core/Internal/Data/CleanInstanceDataAccessor.cs b/src/Altinn.App.Core/Internal/Data/CleanInstanceDataAccessor.cs
new file mode 100644
index 0000000000..ab572c7ea7
--- /dev/null
+++ b/src/Altinn.App.Core/Internal/Data/CleanInstanceDataAccessor.cs
@@ -0,0 +1,148 @@
+using Altinn.App.Core.Configuration;
+using Altinn.App.Core.Features;
+using Altinn.App.Core.Helpers;
+using Altinn.App.Core.Internal.App;
+using Altinn.App.Core.Internal.Expressions;
+using Altinn.App.Core.Internal.Texts;
+using Altinn.App.Core.Models;
+using Altinn.App.Core.Models.Layout;
+using Altinn.Platform.Storage.Interface.Models;
+
+namespace Altinn.App.Core.Internal.Data;
+
+internal class CleanInstanceDataAccessor : IInstanceDataAccessor
+{
+ private readonly IInstanceDataAccessor _dataAccessor;
+ private readonly string? _taskId;
+ private readonly IAppResources _appResources;
+ private readonly FrontEndSettings _frontEndSettings;
+ private readonly RowRemovalOption _rowRemovalOption;
+ private readonly string? _language;
+ private readonly ITranslationService _translationService;
+ private readonly Telemetry? _telemetry;
+
+ public CleanInstanceDataAccessor(
+ IInstanceDataAccessor dataAccessor,
+ string? taskId,
+ IAppResources appResources,
+ ITranslationService translationService,
+ FrontEndSettings frontEndSettings,
+ RowRemovalOption rowRemovalOption,
+ string? language,
+ Telemetry? telemetry
+ )
+ {
+ _dataAccessor = dataAccessor;
+ _taskId = taskId;
+ _appResources = appResources;
+ _frontEndSettings = frontEndSettings;
+ _rowRemovalOption = rowRemovalOption;
+ _language = language;
+ _telemetry = telemetry;
+ _translationService = translationService;
+
+ LayoutModel? layouts = taskId is not null ? appResources.GetLayoutModelForTask(taskId) : null;
+ if (layouts is null)
+ {
+ _hiddenFieldsTask = new(() => Task.FromResult(new List()));
+ }
+ else
+ {
+ var state = new LayoutEvaluatorState(
+ dataAccessor,
+ layouts,
+ translationService,
+ frontEndSettings,
+ gatewayAction: null,
+ language
+ );
+ _hiddenFieldsTask = new(() =>
+ {
+ using var activity = telemetry?.StartRemoveHiddenDataForValidation();
+ return LayoutEvaluator.GetHiddenFieldsForRemoval(state);
+ });
+ }
+ }
+
+ private readonly DataElementCache _cleanCache = new();
+
+ private readonly Lazy>> _hiddenFieldsTask;
+
+ public Instance Instance => _dataAccessor.Instance;
+
+ public IReadOnlyCollection DataTypes => _dataAccessor.DataTypes;
+
+ public async Task