From 1dbadd4c99d8c07ea6f3664a54c5850332f4a708 Mon Sep 17 00:00:00 2001 From: PERHOF Date: Tue, 9 Dec 2025 10:29:05 +0100 Subject: [PATCH 01/28] added prefill from Dan to PrefillSI.cs --- .../Implementation/PrefillSI.cs | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/Altinn.App.Core/Implementation/PrefillSI.cs b/src/Altinn.App.Core/Implementation/PrefillSI.cs index 7e5a1722d9..2331fe29b5 100644 --- a/src/Altinn.App.Core/Implementation/PrefillSI.cs +++ b/src/Altinn.App.Core/Implementation/PrefillSI.cs @@ -3,6 +3,7 @@ using Altinn.App.Core.Features; using Altinn.App.Core.Features.Auth; using Altinn.App.Core.Internal.App; +using Altinn.App.Core.Internal.Dan; using Altinn.App.Core.Internal.Prefill; using Altinn.App.Core.Internal.Registers; using Altinn.Platform.Profile.Models; @@ -20,10 +21,12 @@ public class PrefillSI : IPrefill private readonly IAppResources _appResourcesService; private readonly IRegisterClient _registerClient; private readonly IAuthenticationContext _authenticationContext; + private readonly IDanClient _danClient; private readonly Telemetry? _telemetry; private static readonly string _erKey = "ER"; private static readonly string _dsfKey = "DSF"; private static readonly string _userProfileKey = "UserProfile"; + private static readonly string _danKey = "DAN"; private static readonly string _allowOverwriteKey = "allowOverwrite"; private bool _allowOverwrite = false; @@ -40,6 +43,7 @@ public PrefillSI( IAppResources appResourcesService, IAuthenticationContext authenticationContext, IServiceProvider serviceProvider, + IDanClient danClient, Telemetry? telemetry = null ) { @@ -47,6 +51,7 @@ public PrefillSI( _appResourcesService = appResourcesService; _registerClient = serviceProvider.GetRequiredService(); _authenticationContext = authenticationContext; + _danClient = danClient; _telemetry = telemetry; } @@ -66,7 +71,8 @@ public async Task PrefillDataModel( string partyId, string dataModelName, object dataModel, - Dictionary? externalPrefill = null + Dictionary? externalPrefill = null, + string? dataset = null ) { using var activity = _telemetry?.StartPrefillDataModelActivity(partyId); @@ -206,6 +212,40 @@ when await systemUser.LoadDetails() is { } details && details.Party.PartyId == p } } } + + //remove hardcoding after testing + dataset = "UnitBasicInformation"; + // Prefill from Dan + JToken? danConfiguration = prefillConfiguration.SelectToken(_danKey); + if (danConfiguration != null && dataset != null) + { + var mappingDict = danConfiguration["datasets"] + .FirstOrDefault(d => (string)d["name"] == dataset) + ?["mappings"]; + + Dictionary danDictionary = mappingDict.ToDictionary( + k => ((JObject)k).Properties().First().Name, + v => v.Values().First().Value() + ); + + if (danDictionary.Count > 0) + { + //remove hardcoding after testing + var subject = "312655241"; + var danDataset = await _danClient.GetDataset(dataset, subject); + if (danDataset != null) + { + JObject danJsonObject = JObject.FromObject(danDataset); + _logger.LogInformation($"Started prefill from {_danKey}"); + LoopThroughDictionaryAndAssignValuesToDataModel(danDictionary, danJsonObject, dataModel); + } + else + { + string errorMessage = $"Could not prefill from {_danKey}, data is not defined."; + _logger.LogError(errorMessage); + } + } + } } /// From 0d7bfa58499a958cd4823335464e502987a1c4c4 Mon Sep 17 00:00:00 2001 From: PERHOF Date: Tue, 9 Dec 2025 10:30:11 +0100 Subject: [PATCH 02/28] updated interface with correct parameters --- src/Altinn.App.Core/Internal/Prefill/IPrefill.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Altinn.App.Core/Internal/Prefill/IPrefill.cs b/src/Altinn.App.Core/Internal/Prefill/IPrefill.cs index 8f41eeea32..ca4a683139 100644 --- a/src/Altinn.App.Core/Internal/Prefill/IPrefill.cs +++ b/src/Altinn.App.Core/Internal/Prefill/IPrefill.cs @@ -24,6 +24,7 @@ Task PrefillDataModel( string partyId, string dataModelName, object dataModel, - Dictionary? externalPrefill = null + Dictionary? externalPrefill = null, + string? dataset = null ); } From 76eef152ceba6be3ebc6abe9ef175ce25a5183d9 Mon Sep 17 00:00:00 2001 From: PERHOF Date: Tue, 9 Dec 2025 10:58:34 +0100 Subject: [PATCH 03/28] added danClient --- .../Configuration/DanSettings.cs | 8 ++++ .../Infrastructure/Clients/Dan/DanClient.cs | 46 +++++++++++++++++++ .../Internal/Dan/IDanClient.cs | 6 +++ 3 files changed, 60 insertions(+) create mode 100644 src/Altinn.App.Core/Configuration/DanSettings.cs create mode 100644 src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs create mode 100644 src/Altinn.App.Core/Internal/Dan/IDanClient.cs diff --git a/src/Altinn.App.Core/Configuration/DanSettings.cs b/src/Altinn.App.Core/Configuration/DanSettings.cs new file mode 100644 index 0000000000..1dbdb27cb8 --- /dev/null +++ b/src/Altinn.App.Core/Configuration/DanSettings.cs @@ -0,0 +1,8 @@ +namespace Altinn.App.Core.Configuration; + +public class DanSettings +{ + public string BaseUrl { get; set; } + public string SubscriptionKey { get; set; } + public string Scope { get; set; } +} diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs new file mode 100644 index 0000000000..1ae3b29a6c --- /dev/null +++ b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs @@ -0,0 +1,46 @@ +using System.Net.Http.Headers; +using System.Net.Http.Json; +using Altinn.App.Core.Configuration; +using Altinn.App.Core.Internal.Auth; +using Altinn.App.Core.Internal.Dan; +using Altinn.App.Core.Models; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.Rest; + +namespace Altinn.App.Core.Features.Infrastructure.Clients.Dan; + +public class DanClient : IDanClient +{ + private HttpClient _httpClient; + private IOptions _settings; + private IAuthenticationTokenResolver _authenticationTokenResolver; + + public DanClient(HttpClient httpClient, IOptions settings, IServiceProvider serviceProvider) + { + _settings = settings; + _authenticationTokenResolver = serviceProvider.GetRequiredService(); + httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); + httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", settings.Value.SubscriptionKey); + httpClient.BaseAddress = new Uri(settings.Value.BaseUrl); + } + + public async Task> GetDataset(string dataset, string subject) + { + var token = await GetMaskinportenToken(); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Value); + + var result = await _httpClient.GetAsync($"datasets/{dataset}?subject={subject}&envelope=false"); + var resultJson = result.Content.ReadFromJsonAsync(); + return new Dictionary(); + } + + private async Task GetMaskinportenToken() + { + var token = await _authenticationTokenResolver.GetAccessToken( + AuthenticationMethod.Maskinporten(_settings.Value.Scope), + CancellationToken.None + ); + return token; + } +} diff --git a/src/Altinn.App.Core/Internal/Dan/IDanClient.cs b/src/Altinn.App.Core/Internal/Dan/IDanClient.cs new file mode 100644 index 0000000000..6dc19cfc08 --- /dev/null +++ b/src/Altinn.App.Core/Internal/Dan/IDanClient.cs @@ -0,0 +1,6 @@ +namespace Altinn.App.Core.Internal.Dan; + +public interface IDanClient +{ + public Task> GetDataset(string dataset, string subject); +} From 0a232ce4dca5c51f0dd5cc535034217a5e1f060f Mon Sep 17 00:00:00 2001 From: PERHOF Date: Tue, 16 Dec 2025 08:24:33 +0100 Subject: [PATCH 04/28] fixed tests --- .../Controllers/StatelessDataControllerTests.cs | 1 + test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/Altinn.App.Api.Tests/Controllers/StatelessDataControllerTests.cs b/test/Altinn.App.Api.Tests/Controllers/StatelessDataControllerTests.cs index 856ccce199..e8dda39f43 100644 --- a/test/Altinn.App.Api.Tests/Controllers/StatelessDataControllerTests.cs +++ b/test/Altinn.App.Api.Tests/Controllers/StatelessDataControllerTests.cs @@ -341,6 +341,7 @@ public async Task Get_Returns_OK_with_appModel() auth.SelectedPartyId.ToString(CultureInfo.InvariantCulture), dataType, It.IsAny(), + null, null ) ); diff --git a/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs b/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs index 37266defc2..bb3c2dea4f 100644 --- a/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs +++ b/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs @@ -54,7 +54,8 @@ public async Task PrefillDataModel_AssignsValuesCorrectly() loggerMock.Object, appResourcesMock.Object, authenticationContextMock.Object, - sp + sp, + null ); prefillToTest.PrefillDataModel(dataModel, externalPrefill, continueOnError: false); From 468874426a6858711a9228889a21ddd71a392f47 Mon Sep 17 00:00:00 2001 From: PERHOF Date: Tue, 16 Dec 2025 11:16:28 +0100 Subject: [PATCH 05/28] DanClient returns dictionary --- .../Infrastructure/Clients/Dan/DanClient.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs index 1ae3b29a6c..e50642a683 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs @@ -1,12 +1,11 @@ using System.Net.Http.Headers; -using System.Net.Http.Json; using Altinn.App.Core.Configuration; using Altinn.App.Core.Internal.Auth; using Altinn.App.Core.Internal.Dan; using Altinn.App.Core.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Microsoft.Rest; +using Newtonsoft.Json; namespace Altinn.App.Core.Features.Infrastructure.Clients.Dan; @@ -18,6 +17,7 @@ public class DanClient : IDanClient public DanClient(HttpClient httpClient, IOptions settings, IServiceProvider serviceProvider) { + _httpClient = httpClient; _settings = settings; _authenticationTokenResolver = serviceProvider.GetRequiredService(); httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); @@ -30,8 +30,15 @@ public async Task> GetDataset(string dataset, string var token = await GetMaskinportenToken(); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Value); - var result = await _httpClient.GetAsync($"datasets/{dataset}?subject={subject}&envelope=false"); - var resultJson = result.Content.ReadFromJsonAsync(); + var result = await _httpClient.GetAsync( + $"directharvest/{dataset}?subject={subject}&envelope=false&requestor=991825827" + ); + if (result.IsSuccessStatusCode) + { + var resultJson = result.Content.ReadAsStringAsync().Result; + var dictionary = JsonConvert.DeserializeObject>(resultJson); + return dictionary; + } return new Dictionary(); } From e3b555f26aefaf150344765676cc70fc19dfb148 Mon Sep 17 00:00:00 2001 From: PERHOF Date: Tue, 16 Dec 2025 12:48:36 +0100 Subject: [PATCH 06/28] removed hardcoding of subject --- src/Altinn.App.Core/Implementation/PrefillSI.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Altinn.App.Core/Implementation/PrefillSI.cs b/src/Altinn.App.Core/Implementation/PrefillSI.cs index 2331fe29b5..46d53a5323 100644 --- a/src/Altinn.App.Core/Implementation/PrefillSI.cs +++ b/src/Altinn.App.Core/Implementation/PrefillSI.cs @@ -230,10 +230,10 @@ when await systemUser.LoadDetails() is { } details && details.Party.PartyId == p if (danDictionary.Count > 0) { - //remove hardcoding after testing - var subject = "312655241"; + //use ssn as default, use orgnumber if ssn is not set + var subject = !string.IsNullOrWhiteSpace(party.SSN) ? party.SSN : party.OrgNumber; var danDataset = await _danClient.GetDataset(dataset, subject); - if (danDataset != null) + if (danDataset.Any()) { JObject danJsonObject = JObject.FromObject(danDataset); _logger.LogInformation($"Started prefill from {_danKey}"); From 2f02bc44c7a355e4c52ef7ba264267fc9a87aecb Mon Sep 17 00:00:00 2001 From: PERHOF Date: Tue, 16 Dec 2025 13:44:19 +0100 Subject: [PATCH 07/28] use SwapKeyValuesForPrefill so documentation is accurate --- src/Altinn.App.Core/Implementation/PrefillSI.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Altinn.App.Core/Implementation/PrefillSI.cs b/src/Altinn.App.Core/Implementation/PrefillSI.cs index 46d53a5323..1c9a5495e4 100644 --- a/src/Altinn.App.Core/Implementation/PrefillSI.cs +++ b/src/Altinn.App.Core/Implementation/PrefillSI.cs @@ -237,7 +237,11 @@ when await systemUser.LoadDetails() is { } details && details.Party.PartyId == p { JObject danJsonObject = JObject.FromObject(danDataset); _logger.LogInformation($"Started prefill from {_danKey}"); - LoopThroughDictionaryAndAssignValuesToDataModel(danDictionary, danJsonObject, dataModel); + LoopThroughDictionaryAndAssignValuesToDataModel( + SwapKeyValuesForPrefill(danDictionary), + danJsonObject, + dataModel + ); } else { From dde9af63468a0f532a27fd367d9dd47441976125 Mon Sep 17 00:00:00 2001 From: PERHOF Date: Wed, 17 Dec 2025 11:26:19 +0100 Subject: [PATCH 08/28] added xml comments to classes, methods and properties --- .../Configuration/DanSettings.cs | 20 +++++++++++++++--- .../Implementation/PrefillSI.cs | 5 +++-- .../Infrastructure/Clients/Dan/DanClient.cs | 21 +++++++++++++++++-- .../Internal/Dan/IDanClient.cs | 9 ++++++++ .../Internal/Prefill/IPrefill.cs | 1 + 5 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/Altinn.App.Core/Configuration/DanSettings.cs b/src/Altinn.App.Core/Configuration/DanSettings.cs index 1dbdb27cb8..bdddfc2c91 100644 --- a/src/Altinn.App.Core/Configuration/DanSettings.cs +++ b/src/Altinn.App.Core/Configuration/DanSettings.cs @@ -1,8 +1,22 @@ namespace Altinn.App.Core.Configuration; +/// +/// Settings for DanClient +/// public class DanSettings { - public string BaseUrl { get; set; } - public string SubscriptionKey { get; set; } - public string Scope { get; set; } + /// + /// Base url for Dan API + /// + public required string BaseUrl { get; set; } + + /// + /// Api subscription key + /// + public required string SubscriptionKey { get; set; } + + /// + /// Maskinporten scope + /// + public required string Scope { get; set; } } diff --git a/src/Altinn.App.Core/Implementation/PrefillSI.cs b/src/Altinn.App.Core/Implementation/PrefillSI.cs index 1c9a5495e4..e5fa10c4fc 100644 --- a/src/Altinn.App.Core/Implementation/PrefillSI.cs +++ b/src/Altinn.App.Core/Implementation/PrefillSI.cs @@ -37,6 +37,7 @@ public class PrefillSI : IPrefill /// The app's resource service /// The authentication context /// The service provider + /// The Dan client /// Telemetry for traces and metrics. public PrefillSI( ILogger logger, @@ -219,7 +220,7 @@ when await systemUser.LoadDetails() is { } details && details.Party.PartyId == p JToken? danConfiguration = prefillConfiguration.SelectToken(_danKey); if (danConfiguration != null && dataset != null) { - var mappingDict = danConfiguration["datasets"] + var mappingDict = (danConfiguration["datasets"]) .FirstOrDefault(d => (string)d["name"] == dataset) ?["mappings"]; @@ -233,7 +234,7 @@ when await systemUser.LoadDetails() is { } details && details.Party.PartyId == p //use ssn as default, use orgnumber if ssn is not set var subject = !string.IsNullOrWhiteSpace(party.SSN) ? party.SSN : party.OrgNumber; var danDataset = await _danClient.GetDataset(dataset, subject); - if (danDataset.Any()) + if (danDataset.Count > 0) { JObject danJsonObject = JObject.FromObject(danDataset); _logger.LogInformation($"Started prefill from {_danKey}"); diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs index e50642a683..eb961a4740 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs @@ -1,5 +1,6 @@ using System.Net.Http.Headers; using Altinn.App.Core.Configuration; +using Altinn.App.Core.Features; using Altinn.App.Core.Internal.Auth; using Altinn.App.Core.Internal.Dan; using Altinn.App.Core.Models; @@ -7,14 +8,23 @@ using Microsoft.Extensions.Options; using Newtonsoft.Json; -namespace Altinn.App.Core.Features.Infrastructure.Clients.Dan; +namespace Altinn.App.Core.Infrastructure.Clients.Dan; +/// +/// Client for interacting with the Dan API. +/// public class DanClient : IDanClient { private HttpClient _httpClient; private IOptions _settings; private IAuthenticationTokenResolver _authenticationTokenResolver; + /// + /// Constructor + /// + /// + /// + /// public DanClient(HttpClient httpClient, IOptions settings, IServiceProvider serviceProvider) { _httpClient = httpClient; @@ -25,6 +35,12 @@ public DanClient(HttpClient httpClient, IOptions settings, IService httpClient.BaseAddress = new Uri(settings.Value.BaseUrl); } + /// + /// Returns dataset from Dan API. + /// + /// Dataset from Dan + /// Usually ssn or orgNumber + /// public async Task> GetDataset(string dataset, string subject) { var token = await GetMaskinportenToken(); @@ -37,7 +53,8 @@ public async Task> GetDataset(string dataset, string { var resultJson = result.Content.ReadAsStringAsync().Result; var dictionary = JsonConvert.DeserializeObject>(resultJson); - return dictionary; + if (dictionary != null) + return dictionary; } return new Dictionary(); } diff --git a/src/Altinn.App.Core/Internal/Dan/IDanClient.cs b/src/Altinn.App.Core/Internal/Dan/IDanClient.cs index 6dc19cfc08..374f69a8d4 100644 --- a/src/Altinn.App.Core/Internal/Dan/IDanClient.cs +++ b/src/Altinn.App.Core/Internal/Dan/IDanClient.cs @@ -1,6 +1,15 @@ namespace Altinn.App.Core.Internal.Dan; +/// +/// DanClient interface +/// public interface IDanClient { + /// + /// Method for getting a selected dataset from Dan Api + /// + /// + /// + /// public Task> GetDataset(string dataset, string subject); } diff --git a/src/Altinn.App.Core/Internal/Prefill/IPrefill.cs b/src/Altinn.App.Core/Internal/Prefill/IPrefill.cs index ca4a683139..de71b2931c 100644 --- a/src/Altinn.App.Core/Internal/Prefill/IPrefill.cs +++ b/src/Altinn.App.Core/Internal/Prefill/IPrefill.cs @@ -20,6 +20,7 @@ public interface IPrefill /// The data model name /// The data model object /// External given prefill + /// Selected dataset from Dan Task PrefillDataModel( string partyId, string dataModelName, From 9e539743bf95af8646b61254eb8a8ba9c06e0b21 Mon Sep 17 00:00:00 2001 From: PERHOF Date: Tue, 6 Jan 2026 15:25:47 +0100 Subject: [PATCH 09/28] added query-jmespath to api call and helper methods # Conflicts: # Directory.Packages.props --- src/Altinn.App.Core/Altinn.App.Core.csproj | 2 + .../Configuration/DanSettings.cs | 3 +- .../Implementation/PrefillSI.cs | 108 +++++++++++++----- .../Infrastructure/Clients/Dan/DanClient.cs | 70 +++++++++++- .../Internal/Dan/IDanClient.cs | 7 +- .../Internal/Prefill/IPrefill.cs | 4 +- ...ouldNotChange_Unintentionally.verified.txt | 28 ++++- 7 files changed, 178 insertions(+), 44 deletions(-) diff --git a/src/Altinn.App.Core/Altinn.App.Core.csproj b/src/Altinn.App.Core/Altinn.App.Core.csproj index 94b3ad9364..f6dca16217 100644 --- a/src/Altinn.App.Core/Altinn.App.Core.csproj +++ b/src/Altinn.App.Core/Altinn.App.Core.csproj @@ -15,12 +15,14 @@ + + diff --git a/src/Altinn.App.Core/Configuration/DanSettings.cs b/src/Altinn.App.Core/Configuration/DanSettings.cs index bdddfc2c91..9c66417c9f 100644 --- a/src/Altinn.App.Core/Configuration/DanSettings.cs +++ b/src/Altinn.App.Core/Configuration/DanSettings.cs @@ -11,9 +11,10 @@ public class DanSettings public required string BaseUrl { get; set; } /// - /// Api subscription key + /// Api subscription keys /// public required string SubscriptionKey { get; set; } + public required string SubscriptionKeySkipsRegistrene { get; set; } /// /// Maskinporten scope diff --git a/src/Altinn.App.Core/Implementation/PrefillSI.cs b/src/Altinn.App.Core/Implementation/PrefillSI.cs index e5fa10c4fc..19748a5d64 100644 --- a/src/Altinn.App.Core/Implementation/PrefillSI.cs +++ b/src/Altinn.App.Core/Implementation/PrefillSI.cs @@ -1,5 +1,6 @@ using System.Globalization; using System.Reflection; +using System.Text.Json; using Altinn.App.Core.Features; using Altinn.App.Core.Features.Auth; using Altinn.App.Core.Internal.App; @@ -8,6 +9,7 @@ using Altinn.App.Core.Internal.Registers; using Altinn.Platform.Profile.Models; using Altinn.Platform.Register.Models; +using DevLab.JmesPath; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; @@ -72,8 +74,7 @@ public async Task PrefillDataModel( string partyId, string dataModelName, object dataModel, - Dictionary? externalPrefill = null, - string? dataset = null + Dictionary? externalPrefill = null ) { using var activity = _telemetry?.StartPrefillDataModelActivity(partyId); @@ -214,40 +215,48 @@ when await systemUser.LoadDetails() is { } details && details.Party.PartyId == p } } - //remove hardcoding after testing - dataset = "UnitBasicInformation"; // Prefill from Dan JToken? danConfiguration = prefillConfiguration.SelectToken(_danKey); - if (danConfiguration != null && dataset != null) + if (danConfiguration != null) { - var mappingDict = (danConfiguration["datasets"]) - .FirstOrDefault(d => (string)d["name"] == dataset) - ?["mappings"]; + var jmes = new JmesPath(); + var datasets = jmes.Transform(danConfiguration.ToString(), "datasets[].name"); + //We need to remove the brackets and quotes from the datasets string. + var datasetTrimmed = datasets.Substring(2, datasets.Length - 4).Replace("\"", string.Empty); - Dictionary danDictionary = mappingDict.ToDictionary( - k => ((JObject)k).Properties().First().Name, - v => v.Values().First().Value() - ); + List datasetList = datasetTrimmed.Split(',').ToList(); - if (danDictionary.Count > 0) + if (datasetList != null) { - //use ssn as default, use orgnumber if ssn is not set - var subject = !string.IsNullOrWhiteSpace(party.SSN) ? party.SSN : party.OrgNumber; - var danDataset = await _danClient.GetDataset(dataset, subject); - if (danDataset.Count > 0) + foreach (var dataset in datasetList) { - JObject danJsonObject = JObject.FromObject(danDataset); - _logger.LogInformation($"Started prefill from {_danKey}"); - LoopThroughDictionaryAndAssignValuesToDataModel( - SwapKeyValuesForPrefill(danDictionary), - danJsonObject, - dataModel - ); - } - else - { - string errorMessage = $"Could not prefill from {_danKey}, data is not defined."; - _logger.LogError(errorMessage); + var subject = !string.IsNullOrWhiteSpace(party.SSN) ? party.SSN : party.OrgNumber; + subject = "312655241"; // TODO: remove + + if (dataset.Equals("Skipsregistrene", StringComparison.OrdinalIgnoreCase)) // TODO: remove + subject = "977340691"; // TODO: remove + + var query = GetQuery(danConfiguration.ToString(), dataset); + var queryToString = SwapKeyValuesForPrefill(query); + var fullQuery = string.Join(",", queryToString.Select(kvp => $"{kvp.Key}: {kvp.Value}")); + + var danDataset = await _danClient.GetDataset(dataset, subject, fullQuery); + if (danDataset.Count > 0) + { + var propertiesOfGivenDataset = GetDatasetProperties(danConfiguration.ToString(), dataset); + JObject danJsonObject = JObject.FromObject(danDataset); + _logger.LogInformation($"Started prefill from {_danKey}"); + LoopThroughDictionaryAndAssignValuesToDataModel( + SwapKeyValuesForPrefill(propertiesOfGivenDataset), + danJsonObject, + dataModel + ); + } + else + { + string errorMessage = $"Could not prefill from {_danKey}, data is not defined."; + _logger.LogError(errorMessage); + } } } } @@ -375,4 +384,45 @@ private static Dictionary SwapKeyValuesForPrefill(Dictionary x.Value, x => x.Key); } + + private static Dictionary GetDatasetProperties(string danConfiguration, string dataset) + { + var jmes = new JmesPath(); + + var resultJson = jmes.Transform( + danConfiguration, + $"datasets[?name=='{dataset}'].mappings[].{{key: keys(@)[0], value: values(@)[0]}}" + ); + + var keyValuePairs = JsonSerializer.Deserialize>>(resultJson); + return keyValuePairs.ToDictionary(item => item["key"], item => item["value"]); + } + + private static Dictionary GetQuery(string danConfiguration, string dataset) + { + var jmes = new JmesPath(); + + var query = $"datasets[?name=='{dataset}'].mappings[]"; + var fields = jmes.Transform(danConfiguration, query); + + JArray array = JArray.Parse(fields); + + JObject merged = new JObject(); + foreach (JObject obj in array) + { + merged.Merge(obj); + } + + if (merged != null) + { + Dictionary mergedDictionary = merged.ToObject>(); + return mergedDictionary; + /* + var swappedKeyValues = SwapKeyValuesForPrefill(mergedDictionary); + return string.Join(",", swappedKeyValues.Select(kvp => $"{kvp.Key}: {kvp.Value}")); + */ + } + + return null; + } } diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs index eb961a4740..147a883807 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs @@ -1,4 +1,6 @@ using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; using Altinn.App.Core.Configuration; using Altinn.App.Core.Features; using Altinn.App.Core.Internal.Auth; @@ -7,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Newtonsoft.Json; +using JsonException = Newtonsoft.Json.JsonException; namespace Altinn.App.Core.Infrastructure.Clients.Dan; @@ -31,7 +34,7 @@ public DanClient(HttpClient httpClient, IOptions settings, IService _settings = settings; _authenticationTokenResolver = serviceProvider.GetRequiredService(); httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); - httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", settings.Value.SubscriptionKey); + //httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", settings.Value.SubscriptionKeySkipsRegistrene); httpClient.BaseAddress = new Uri(settings.Value.BaseUrl); } @@ -40,19 +43,38 @@ public DanClient(HttpClient httpClient, IOptions settings, IService /// /// Dataset from Dan /// Usually ssn or orgNumber + /// jmesPath - usd to filter out just the fields we need /// - public async Task> GetDataset(string dataset, string subject) + public async Task> GetDataset(string dataset, string subject, string jmesPathExpression) { + _httpClient.DefaultRequestHeaders.Clear(); var token = await GetMaskinportenToken(); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Value); + _httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", SetSubscriptionKey(dataset)); - var result = await _httpClient.GetAsync( - $"directharvest/{dataset}?subject={subject}&envelope=false&requestor=991825827" + var body = new { Subject = subject }; + var myContent = JsonConvert.SerializeObject(body); + HttpContent content = new StringContent(myContent, Encoding.UTF8, "application/json"); + + var result = await _httpClient.PostAsync( + $"directharvest/{dataset}?envelope=false&requestor=991825827&query=[].{{{jmesPathExpression}}}||{{{jmesPathExpression}}}", + content ); + if (result.IsSuccessStatusCode) { + var dictionary = new Dictionary(); var resultJson = result.Content.ReadAsStringAsync().Result; - var dictionary = JsonConvert.DeserializeObject>(resultJson); + + //some datasets might return an array. The array need to be serialized differently than a single object + if (IsJsonArray(resultJson)) + { + dictionary = await ConvertListToDictionary(resultJson); + } + else + { + dictionary = JsonConvert.DeserializeObject>(resultJson); + } if (dictionary != null) return dictionary; } @@ -67,4 +89,42 @@ private async Task GetMaskinportenToken() ); return token; } + + private async Task> ConvertListToDictionary(string jsonString) + { + var list = JsonConvert.DeserializeObject>>(jsonString); + + var mergedDictionary = list.SelectMany(d => d) + .GroupBy(kvp => kvp.Key) + .ToDictionary(g => g.Key, g => string.Join(",", g.Select(x => x.Value))); + + return mergedDictionary; + } + + private bool IsJsonArray(string jsonString) + { + try + { + using var doc = JsonDocument.Parse(jsonString); + return doc.RootElement.ValueKind == JsonValueKind.Array; + } + catch (JsonException) + { + return false; + } + } + + //Todo: remove after testing + private string SetSubscriptionKey(string dataset) + { + switch (dataset.ToUpper()) + { + case "UNITBASICINFORMATION": + return _settings.Value.SubscriptionKey; + case "SKIPSREGISTRENE": + return _settings.Value.SubscriptionKeySkipsRegistrene; + default: + return ""; + } + } } diff --git a/src/Altinn.App.Core/Internal/Dan/IDanClient.cs b/src/Altinn.App.Core/Internal/Dan/IDanClient.cs index 374f69a8d4..2c36186b6b 100644 --- a/src/Altinn.App.Core/Internal/Dan/IDanClient.cs +++ b/src/Altinn.App.Core/Internal/Dan/IDanClient.cs @@ -8,8 +8,9 @@ public interface IDanClient /// /// Method for getting a selected dataset from Dan Api /// - /// - /// + /// Name of the dataset + /// Usually ssn or OrgNumber + /// jmesPathExpression /// - public Task> GetDataset(string dataset, string subject); + public Task> GetDataset(string dataset, string subject, string jmesPathExpression); } diff --git a/src/Altinn.App.Core/Internal/Prefill/IPrefill.cs b/src/Altinn.App.Core/Internal/Prefill/IPrefill.cs index de71b2931c..8f41eeea32 100644 --- a/src/Altinn.App.Core/Internal/Prefill/IPrefill.cs +++ b/src/Altinn.App.Core/Internal/Prefill/IPrefill.cs @@ -20,12 +20,10 @@ public interface IPrefill /// The data model name /// The data model object /// External given prefill - /// Selected dataset from Dan Task PrefillDataModel( string partyId, string dataModelName, object dataModel, - Dictionary? externalPrefill = null, - string? dataset = null + Dictionary? externalPrefill = null ); } diff --git a/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt b/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt index befc64ef92..23199a03c5 100644 --- a/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt +++ b/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt @@ -51,6 +51,13 @@ namespace Altinn.App.Core.Configuration public CacheSettings() { } public int ProfileCacheLifetimeSeconds { get; set; } } + public class DanSettings + { + public DanSettings() { } + public string BaseUrl { get; set; } + public string Scope { get; set; } + public string SubscriptionKey { get; set; } + } public class FrontEndSettings : System.Collections.Generic.Dictionary { public FrontEndSettings() { } @@ -1529,6 +1536,14 @@ namespace Altinn.App.Core.Features.FileAnalyzis System.Threading.Tasks.Task> Analyse(Altinn.Platform.Storage.Interface.Models.DataType dataType, System.IO.Stream fileStream, string? filename = null); } } +namespace Altinn.App.Core.Features.Infrastructure.Clients.Dan +{ + public class DanClient : Altinn.App.Core.Internal.Dan.IDanClient + { + public DanClient(System.Net.Http.HttpClient httpClient, Microsoft.Extensions.Options.IOptions settings, System.IServiceProvider serviceProvider) { } + public System.Threading.Tasks.Task> GetDataset(string dataset, string subject) { } + } +} namespace Altinn.App.Core.Features.Maskinporten.Exceptions { public sealed class MaskinportenAuthenticationException : Altinn.App.Core.Features.Maskinporten.Exceptions.MaskinportenException @@ -2365,9 +2380,9 @@ namespace Altinn.App.Core.Implementation } public class PrefillSI : Altinn.App.Core.Internal.Prefill.IPrefill { - public PrefillSI(Microsoft.Extensions.Logging.ILogger logger, Altinn.App.Core.Internal.App.IAppResources appResourcesService, Altinn.App.Core.Features.Auth.IAuthenticationContext authenticationContext, System.IServiceProvider serviceProvider, Altinn.App.Core.Features.Telemetry? telemetry = null) { } + public PrefillSI(Microsoft.Extensions.Logging.ILogger logger, Altinn.App.Core.Internal.App.IAppResources appResourcesService, Altinn.App.Core.Features.Auth.IAuthenticationContext authenticationContext, System.IServiceProvider serviceProvider, Altinn.App.Core.Internal.Dan.IDanClient danClient, Altinn.App.Core.Features.Telemetry? telemetry = null) { } public void PrefillDataModel(object dataModel, System.Collections.Generic.Dictionary externalPrefill, bool continueOnError = false) { } - public System.Threading.Tasks.Task PrefillDataModel(string partyId, string dataModelName, object dataModel, System.Collections.Generic.Dictionary? externalPrefill = null) { } + public System.Threading.Tasks.Task PrefillDataModel(string partyId, string dataModelName, object dataModel, System.Collections.Generic.Dictionary? externalPrefill = null, string? dataset = null) { } } [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class UserTokenProvider : Altinn.App.Core.Internal.Auth.IUserTokenProvider @@ -2989,6 +3004,13 @@ namespace Altinn.App.Core.Internal.Auth string GetUserToken(); } } +namespace Altinn.App.Core.Internal.Dan +{ + public interface IDanClient + { + System.Threading.Tasks.Task> GetDataset(string dataset, string subject); + } +} namespace Altinn.App.Core.Internal.Data { public static class FormDataWrapperFactory @@ -3309,7 +3331,7 @@ namespace Altinn.App.Core.Internal.Prefill public interface IPrefill { void PrefillDataModel(object dataModel, System.Collections.Generic.Dictionary externalPrefill, bool continueOnError = false); - System.Threading.Tasks.Task PrefillDataModel(string partyId, string dataModelName, object dataModel, System.Collections.Generic.Dictionary? externalPrefill = null); + System.Threading.Tasks.Task PrefillDataModel(string partyId, string dataModelName, object dataModel, System.Collections.Generic.Dictionary? externalPrefill = null, string? dataset = null); } } namespace Altinn.App.Core.Internal.Process.Authorization From fcc1d4261ae48791fd826a9faec0a7ff6aec7e49 Mon Sep 17 00:00:00 2001 From: PERHOF Date: Wed, 7 Jan 2026 15:52:27 +0100 Subject: [PATCH 10/28] Only use the json key as jmespath --- .../Implementation/PrefillSI.cs | 68 +++---------------- .../Infrastructure/Clients/Dan/DanClient.cs | 22 +++++- .../Internal/Dan/IDanClient.cs | 2 +- 3 files changed, 32 insertions(+), 60 deletions(-) diff --git a/src/Altinn.App.Core/Implementation/PrefillSI.cs b/src/Altinn.App.Core/Implementation/PrefillSI.cs index 19748a5d64..e1dde60f8b 100644 --- a/src/Altinn.App.Core/Implementation/PrefillSI.cs +++ b/src/Altinn.App.Core/Implementation/PrefillSI.cs @@ -1,6 +1,5 @@ using System.Globalization; using System.Reflection; -using System.Text.Json; using Altinn.App.Core.Features; using Altinn.App.Core.Features.Auth; using Altinn.App.Core.Internal.App; @@ -9,7 +8,6 @@ using Altinn.App.Core.Internal.Registers; using Altinn.Platform.Profile.Models; using Altinn.Platform.Register.Models; -using DevLab.JmesPath; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; @@ -219,35 +217,32 @@ when await systemUser.LoadDetails() is { } details && details.Party.PartyId == p JToken? danConfiguration = prefillConfiguration.SelectToken(_danKey); if (danConfiguration != null) { - var jmes = new JmesPath(); - var datasets = jmes.Transform(danConfiguration.ToString(), "datasets[].name"); - //We need to remove the brackets and quotes from the datasets string. - var datasetTrimmed = datasets.Substring(2, datasets.Length - 4).Replace("\"", string.Empty); - - List datasetList = datasetTrimmed.Split(',').ToList(); - + var datasetList = danConfiguration.SelectToken("datasets"); if (datasetList != null) { foreach (var dataset in datasetList) { + var datasetName = dataset.SelectToken("name").ToString(); + var subject = !string.IsNullOrWhiteSpace(party.SSN) ? party.SSN : party.OrgNumber; + //TODO: remove after local testing - this is just hard coded subject id to get data from Dan subject = "312655241"; // TODO: remove - if (dataset.Equals("Skipsregistrene", StringComparison.OrdinalIgnoreCase)) // TODO: remove + if (datasetName.Equals("Skipsregistrene", StringComparison.OrdinalIgnoreCase)) // TODO: remove subject = "977340691"; // TODO: remove - var query = GetQuery(danConfiguration.ToString(), dataset); - var queryToString = SwapKeyValuesForPrefill(query); - var fullQuery = string.Join(",", queryToString.Select(kvp => $"{kvp.Key}: {kvp.Value}")); + var fields = dataset.SelectToken("mappings"); + var danPrefill = fields + .SelectMany(obj => obj.Children()) + .ToDictionary(prop => prop.Name, prop => prop.Value.ToString()); - var danDataset = await _danClient.GetDataset(dataset, subject, fullQuery); + var danDataset = await _danClient.GetDataset(datasetName, subject, fields.ToString()); if (danDataset.Count > 0) { - var propertiesOfGivenDataset = GetDatasetProperties(danConfiguration.ToString(), dataset); JObject danJsonObject = JObject.FromObject(danDataset); _logger.LogInformation($"Started prefill from {_danKey}"); LoopThroughDictionaryAndAssignValuesToDataModel( - SwapKeyValuesForPrefill(propertiesOfGivenDataset), + SwapKeyValuesForPrefill(danPrefill), danJsonObject, dataModel ); @@ -384,45 +379,4 @@ private static Dictionary SwapKeyValuesForPrefill(Dictionary x.Value, x => x.Key); } - - private static Dictionary GetDatasetProperties(string danConfiguration, string dataset) - { - var jmes = new JmesPath(); - - var resultJson = jmes.Transform( - danConfiguration, - $"datasets[?name=='{dataset}'].mappings[].{{key: keys(@)[0], value: values(@)[0]}}" - ); - - var keyValuePairs = JsonSerializer.Deserialize>>(resultJson); - return keyValuePairs.ToDictionary(item => item["key"], item => item["value"]); - } - - private static Dictionary GetQuery(string danConfiguration, string dataset) - { - var jmes = new JmesPath(); - - var query = $"datasets[?name=='{dataset}'].mappings[]"; - var fields = jmes.Transform(danConfiguration, query); - - JArray array = JArray.Parse(fields); - - JObject merged = new JObject(); - foreach (JObject obj in array) - { - merged.Merge(obj); - } - - if (merged != null) - { - Dictionary mergedDictionary = merged.ToObject>(); - return mergedDictionary; - /* - var swappedKeyValues = SwapKeyValuesForPrefill(mergedDictionary); - return string.Join(",", swappedKeyValues.Select(kvp => $"{kvp.Key}: {kvp.Value}")); - */ - } - - return null; - } } diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs index 147a883807..155beb6253 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Options; using Newtonsoft.Json; using JsonException = Newtonsoft.Json.JsonException; +using JsonSerializer = System.Text.Json.JsonSerializer; namespace Altinn.App.Core.Infrastructure.Clients.Dan; @@ -45,7 +46,7 @@ public DanClient(HttpClient httpClient, IOptions settings, IService /// Usually ssn or orgNumber /// jmesPath - usd to filter out just the fields we need /// - public async Task> GetDataset(string dataset, string subject, string jmesPathExpression) + public async Task> GetDataset(string dataset, string subject, string fields) { _httpClient.DefaultRequestHeaders.Clear(); var token = await GetMaskinportenToken(); @@ -56,8 +57,17 @@ public async Task> GetDataset(string dataset, string var myContent = JsonConvert.SerializeObject(body); HttpContent content = new StringContent(myContent, Encoding.UTF8, "application/json"); + var jmesPathExpression = GetQuery(fields); + var first = $"{jmesPathExpression.First()} : {jmesPathExpression.First()}"; + foreach (var jsonKey in jmesPathExpression.Skip(1)) + { + first += $",{jsonKey} : {jsonKey}"; + } + + var query = "[].{" + first + "}||{" + first + "}"; + var result = await _httpClient.PostAsync( - $"directharvest/{dataset}?envelope=false&requestor=991825827&query=[].{{{jmesPathExpression}}}||{{{jmesPathExpression}}}", + $"directharvest/{dataset}?envelope=false&requestor=991825827&query={query}", content ); @@ -114,6 +124,14 @@ private bool IsJsonArray(string jsonString) } } + private List GetQuery(string json) + { + var list = JsonSerializer.Deserialize>>(json); + + var keys = list.Where(l => l.Any()).Select(l => l.Keys.First()).ToList(); + return keys; + } + //Todo: remove after testing private string SetSubscriptionKey(string dataset) { diff --git a/src/Altinn.App.Core/Internal/Dan/IDanClient.cs b/src/Altinn.App.Core/Internal/Dan/IDanClient.cs index 2c36186b6b..98da0ae905 100644 --- a/src/Altinn.App.Core/Internal/Dan/IDanClient.cs +++ b/src/Altinn.App.Core/Internal/Dan/IDanClient.cs @@ -12,5 +12,5 @@ public interface IDanClient /// Usually ssn or OrgNumber /// jmesPathExpression /// - public Task> GetDataset(string dataset, string subject, string jmesPathExpression); + public Task> GetDataset(string dataset, string subject, string fields); } From 8a5d15f1cba0eca16fdcbcdece0be4ab75ce60df Mon Sep 17 00:00:00 2001 From: PERHOF Date: Thu, 8 Jan 2026 08:12:02 +0100 Subject: [PATCH 11/28] cleaned up in code --- src/Altinn.App.Core/Implementation/PrefillSI.cs | 5 ----- .../Infrastructure/Clients/Dan/DanClient.cs | 11 +++++------ 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Altinn.App.Core/Implementation/PrefillSI.cs b/src/Altinn.App.Core/Implementation/PrefillSI.cs index e1dde60f8b..f132b57141 100644 --- a/src/Altinn.App.Core/Implementation/PrefillSI.cs +++ b/src/Altinn.App.Core/Implementation/PrefillSI.cs @@ -225,11 +225,6 @@ when await systemUser.LoadDetails() is { } details && details.Party.PartyId == p var datasetName = dataset.SelectToken("name").ToString(); var subject = !string.IsNullOrWhiteSpace(party.SSN) ? party.SSN : party.OrgNumber; - //TODO: remove after local testing - this is just hard coded subject id to get data from Dan - subject = "312655241"; // TODO: remove - - if (datasetName.Equals("Skipsregistrene", StringComparison.OrdinalIgnoreCase)) // TODO: remove - subject = "977340691"; // TODO: remove var fields = dataset.SelectToken("mappings"); var danPrefill = fields diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs index 155beb6253..39a1c3e05d 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs @@ -35,7 +35,6 @@ public DanClient(HttpClient httpClient, IOptions settings, IService _settings = settings; _authenticationTokenResolver = serviceProvider.GetRequiredService(); httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); - //httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", settings.Value.SubscriptionKeySkipsRegistrene); httpClient.BaseAddress = new Uri(settings.Value.BaseUrl); } @@ -57,14 +56,14 @@ public async Task> GetDataset(string dataset, string var myContent = JsonConvert.SerializeObject(body); HttpContent content = new StringContent(myContent, Encoding.UTF8, "application/json"); - var jmesPathExpression = GetQuery(fields); - var first = $"{jmesPathExpression.First()} : {jmesPathExpression.First()}"; - foreach (var jsonKey in jmesPathExpression.Skip(1)) + var fieldsToFill = GetQuery(fields); + var baseQuery = $"{fieldsToFill.First()} : {fieldsToFill.First()}"; + foreach (var jsonKey in fieldsToFill.Skip(1)) { - first += $",{jsonKey} : {jsonKey}"; + baseQuery += $",{jsonKey} : {jsonKey}"; } - var query = "[].{" + first + "}||{" + first + "}"; + var query = "[].{" + baseQuery + "}||{" + baseQuery + "}"; var result = await _httpClient.PostAsync( $"directharvest/{dataset}?envelope=false&requestor=991825827&query={query}", From bc7a9149ce287b75520e5937f45dd7fd250cbbb7 Mon Sep 17 00:00:00 2001 From: PERHOF Date: Thu, 8 Jan 2026 14:06:29 +0100 Subject: [PATCH 12/28] Fixed tests --- .../Extensions/ServiceCollectionExtensions.cs | 4 +++ .../StatelessDataControllerTests.cs | 1 - test/Altinn.App.Api.Tests/Program.cs | 1 + ...ouldNotChange_Unintentionally.verified.txt | 29 ++++++++++--------- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs index 0e4dc047f4..96b00001d6 100644 --- a/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs +++ b/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs @@ -25,6 +25,7 @@ using Altinn.App.Core.Infrastructure.Clients.AccessManagement; using Altinn.App.Core.Infrastructure.Clients.Authentication; using Altinn.App.Core.Infrastructure.Clients.Authorization; +using Altinn.App.Core.Infrastructure.Clients.Dan; using Altinn.App.Core.Infrastructure.Clients.Events; using Altinn.App.Core.Infrastructure.Clients.KeyVault; using Altinn.App.Core.Infrastructure.Clients.Pdf; @@ -36,6 +37,7 @@ using Altinn.App.Core.Internal.App; using Altinn.App.Core.Internal.AppModel; using Altinn.App.Core.Internal.Auth; +using Altinn.App.Core.Internal.Dan; using Altinn.App.Core.Internal.Data; using Altinn.App.Core.Internal.Events; using Altinn.App.Core.Internal.Expressions; @@ -96,6 +98,7 @@ IWebHostEnvironment env services.Configure(configuration.GetSection("GeneralSettings")); services.Configure(configuration.GetSection("PlatformSettings")); services.Configure(configuration.GetSection("CacheSettings")); + services.Configure(configuration.GetSection("DanClientSettings")); AddApplicationIdentifier(services); @@ -103,6 +106,7 @@ IWebHostEnvironment env services.AddHttpClient(); services.AddHttpClient(); services.AddHttpClient(); + services.AddHttpClient(); services.AddHttpClient(); services.AddHttpClient(); services.AddHttpClient(); diff --git a/test/Altinn.App.Api.Tests/Controllers/StatelessDataControllerTests.cs b/test/Altinn.App.Api.Tests/Controllers/StatelessDataControllerTests.cs index e8dda39f43..856ccce199 100644 --- a/test/Altinn.App.Api.Tests/Controllers/StatelessDataControllerTests.cs +++ b/test/Altinn.App.Api.Tests/Controllers/StatelessDataControllerTests.cs @@ -341,7 +341,6 @@ public async Task Get_Returns_OK_with_appModel() auth.SelectedPartyId.ToString(CultureInfo.InvariantCulture), dataType, It.IsAny(), - null, null ) ); diff --git a/test/Altinn.App.Api.Tests/Program.cs b/test/Altinn.App.Api.Tests/Program.cs index a2db05bc5d..35db26e247 100644 --- a/test/Altinn.App.Api.Tests/Program.cs +++ b/test/Altinn.App.Api.Tests/Program.cs @@ -67,6 +67,7 @@ builder.Services.Configure(settings => settings.DisableLocaltestValidation = true); builder.Services.Configure(settings => settings.DisableAppConfigurationCache = true); builder.Services.Configure(settings => settings.IsTest = true); +builder.Services.Configure(settings => settings.BaseUrl = "http://localhost:7071/v1/"); builder.Configuration.GetSection("GeneralSettings:IsTest").Value = "true"; // AppConfigurationCache.Disable = true; diff --git a/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt b/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt index 23199a03c5..e5a36ebfef 100644 --- a/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt +++ b/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt @@ -54,9 +54,10 @@ namespace Altinn.App.Core.Configuration public class DanSettings { public DanSettings() { } - public string BaseUrl { get; set; } - public string Scope { get; set; } - public string SubscriptionKey { get; set; } + public required string BaseUrl { get; set; } + public required string Scope { get; set; } + public required string SubscriptionKey { get; set; } + public required string SubscriptionKeySkipsRegistrene { get; set; } } public class FrontEndSettings : System.Collections.Generic.Dictionary { @@ -1536,14 +1537,6 @@ namespace Altinn.App.Core.Features.FileAnalyzis System.Threading.Tasks.Task> Analyse(Altinn.Platform.Storage.Interface.Models.DataType dataType, System.IO.Stream fileStream, string? filename = null); } } -namespace Altinn.App.Core.Features.Infrastructure.Clients.Dan -{ - public class DanClient : Altinn.App.Core.Internal.Dan.IDanClient - { - public DanClient(System.Net.Http.HttpClient httpClient, Microsoft.Extensions.Options.IOptions settings, System.IServiceProvider serviceProvider) { } - public System.Threading.Tasks.Task> GetDataset(string dataset, string subject) { } - } -} namespace Altinn.App.Core.Features.Maskinporten.Exceptions { public sealed class MaskinportenAuthenticationException : Altinn.App.Core.Features.Maskinporten.Exceptions.MaskinportenException @@ -2382,7 +2375,7 @@ namespace Altinn.App.Core.Implementation { public PrefillSI(Microsoft.Extensions.Logging.ILogger logger, Altinn.App.Core.Internal.App.IAppResources appResourcesService, Altinn.App.Core.Features.Auth.IAuthenticationContext authenticationContext, System.IServiceProvider serviceProvider, Altinn.App.Core.Internal.Dan.IDanClient danClient, Altinn.App.Core.Features.Telemetry? telemetry = null) { } public void PrefillDataModel(object dataModel, System.Collections.Generic.Dictionary externalPrefill, bool continueOnError = false) { } - public System.Threading.Tasks.Task PrefillDataModel(string partyId, string dataModelName, object dataModel, System.Collections.Generic.Dictionary? externalPrefill = null, string? dataset = null) { } + public System.Threading.Tasks.Task PrefillDataModel(string partyId, string dataModelName, object dataModel, System.Collections.Generic.Dictionary? externalPrefill = null) { } } [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class UserTokenProvider : Altinn.App.Core.Internal.Auth.IUserTokenProvider @@ -2411,6 +2404,14 @@ namespace Altinn.App.Core.Infrastructure.Clients.Authorization public System.Threading.Tasks.Task ValidateSelectedParty(int userId, int partyId) { } } } +namespace Altinn.App.Core.Infrastructure.Clients.Dan +{ + public class DanClient : Altinn.App.Core.Internal.Dan.IDanClient + { + public DanClient(System.Net.Http.HttpClient httpClient, Microsoft.Extensions.Options.IOptions settings, System.IServiceProvider serviceProvider) { } + public System.Threading.Tasks.Task> GetDataset(string dataset, string subject, string fields) { } + } +} namespace Altinn.App.Core.Infrastructure.Clients.Events { public class EventsClient : Altinn.App.Core.Internal.Events.IEventsClient @@ -3008,7 +3009,7 @@ namespace Altinn.App.Core.Internal.Dan { public interface IDanClient { - System.Threading.Tasks.Task> GetDataset(string dataset, string subject); + System.Threading.Tasks.Task> GetDataset(string dataset, string subject, string fields); } } namespace Altinn.App.Core.Internal.Data @@ -3331,7 +3332,7 @@ namespace Altinn.App.Core.Internal.Prefill public interface IPrefill { void PrefillDataModel(object dataModel, System.Collections.Generic.Dictionary externalPrefill, bool continueOnError = false); - System.Threading.Tasks.Task PrefillDataModel(string partyId, string dataModelName, object dataModel, System.Collections.Generic.Dictionary? externalPrefill = null, string? dataset = null); + System.Threading.Tasks.Task PrefillDataModel(string partyId, string dataModelName, object dataModel, System.Collections.Generic.Dictionary? externalPrefill = null); } } namespace Altinn.App.Core.Internal.Process.Authorization From aab9fe7d0f6e61c3ece669d061ae83e29854d96b Mon Sep 17 00:00:00 2001 From: PERHOF Date: Mon, 12 Jan 2026 09:14:20 +0100 Subject: [PATCH 13/28] added comments and cleaned up in code --- .../Configuration/DanSettings.cs | 1 - .../Infrastructure/Clients/Dan/DanClient.cs | 23 ++++--------------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/src/Altinn.App.Core/Configuration/DanSettings.cs b/src/Altinn.App.Core/Configuration/DanSettings.cs index 9c66417c9f..3ea82b8409 100644 --- a/src/Altinn.App.Core/Configuration/DanSettings.cs +++ b/src/Altinn.App.Core/Configuration/DanSettings.cs @@ -14,7 +14,6 @@ public class DanSettings /// Api subscription keys /// public required string SubscriptionKey { get; set; } - public required string SubscriptionKeySkipsRegistrene { get; set; } /// /// Maskinporten scope diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs index 39a1c3e05d..5b5b09d004 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs @@ -50,7 +50,7 @@ public async Task> GetDataset(string dataset, string _httpClient.DefaultRequestHeaders.Clear(); var token = await GetMaskinportenToken(); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Value); - _httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", SetSubscriptionKey(dataset)); + _httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", _settings.Value.SubscriptionKey); var body = new { Subject = subject }; var myContent = JsonConvert.SerializeObject(body); @@ -60,15 +60,14 @@ public async Task> GetDataset(string dataset, string var baseQuery = $"{fieldsToFill.First()} : {fieldsToFill.First()}"; foreach (var jsonKey in fieldsToFill.Skip(1)) { + //if there is more than one field to fetch, add it to the query baseQuery += $",{jsonKey} : {jsonKey}"; } + //ensures that th query returns a list if endpoint returns a list and an object when endpoint returns a single object var query = "[].{" + baseQuery + "}||{" + baseQuery + "}"; - var result = await _httpClient.PostAsync( - $"directharvest/{dataset}?envelope=false&requestor=991825827&query={query}", - content - ); + var result = await _httpClient.PostAsync($"directharvest/{dataset}?envelope=false&query={query}", content); if (result.IsSuccessStatusCode) { @@ -130,18 +129,4 @@ private List GetQuery(string json) var keys = list.Where(l => l.Any()).Select(l => l.Keys.First()).ToList(); return keys; } - - //Todo: remove after testing - private string SetSubscriptionKey(string dataset) - { - switch (dataset.ToUpper()) - { - case "UNITBASICINFORMATION": - return _settings.Value.SubscriptionKey; - case "SKIPSREGISTRENE": - return _settings.Value.SubscriptionKeySkipsRegistrene; - default: - return ""; - } - } } From 3d94d7a014f4bdc826697171a445ec4f52deffea Mon Sep 17 00:00:00 2001 From: PERHOF Date: Mon, 12 Jan 2026 10:47:56 +0100 Subject: [PATCH 14/28] fixed null references and warnings --- .../Implementation/PrefillSI.cs | 46 ++++++++++--------- .../Infrastructure/Clients/Dan/DanClient.cs | 30 ++++++------ .../Internal/Dan/IDanClient.cs | 2 +- 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/Altinn.App.Core/Implementation/PrefillSI.cs b/src/Altinn.App.Core/Implementation/PrefillSI.cs index f132b57141..8dc1ef7364 100644 --- a/src/Altinn.App.Core/Implementation/PrefillSI.cs +++ b/src/Altinn.App.Core/Implementation/PrefillSI.cs @@ -222,30 +222,34 @@ when await systemUser.LoadDetails() is { } details && details.Party.PartyId == p { foreach (var dataset in datasetList) { - var datasetName = dataset.SelectToken("name").ToString(); - + var datasetName = dataset.SelectToken("name")?.ToString(); var subject = !string.IsNullOrWhiteSpace(party.SSN) ? party.SSN : party.OrgNumber; - var fields = dataset.SelectToken("mappings"); - var danPrefill = fields - .SelectMany(obj => obj.Children()) - .ToDictionary(prop => prop.Name, prop => prop.Value.ToString()); - - var danDataset = await _danClient.GetDataset(datasetName, subject, fields.ToString()); - if (danDataset.Count > 0) - { - JObject danJsonObject = JObject.FromObject(danDataset); - _logger.LogInformation($"Started prefill from {_danKey}"); - LoopThroughDictionaryAndAssignValuesToDataModel( - SwapKeyValuesForPrefill(danPrefill), - danJsonObject, - dataModel - ); - } - else + if (fields != null) { - string errorMessage = $"Could not prefill from {_danKey}, data is not defined."; - _logger.LogError(errorMessage); + var danPrefill = fields + .SelectMany(obj => obj.Children()) + .ToDictionary(prop => prop.Name, prop => prop.Value.ToString()); + + if (datasetName != null) + { + var danDataset = await _danClient.GetDataset(datasetName, subject, fields.ToString()); + if (danDataset.Count > 0) + { + JObject danJsonObject = JObject.FromObject(danDataset); + _logger.LogInformation($"Started prefill from {_danKey}"); + LoopThroughDictionaryAndAssignValuesToDataModel( + SwapKeyValuesForPrefill(danPrefill), + danJsonObject, + dataModel + ); + } + else + { + string errorMessage = $"Could not prefill from {_danKey}, data is not defined."; + _logger.LogError(errorMessage); + } + } } } } diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs index 5b5b09d004..a02829df5d 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs @@ -43,7 +43,7 @@ public DanClient(HttpClient httpClient, IOptions settings, IService /// /// Dataset from Dan /// Usually ssn or orgNumber - /// jmesPath - usd to filter out just the fields we need + /// The fields we fetch from the api /// public async Task> GetDataset(string dataset, string subject, string fields) { @@ -74,7 +74,7 @@ public async Task> GetDataset(string dataset, string var dictionary = new Dictionary(); var resultJson = result.Content.ReadAsStringAsync().Result; - //some datasets might return an array. The array need to be serialized differently than a single object + //some datasets might return an array. The array needs to be serialized differently than a single object if (IsJsonArray(resultJson)) { dictionary = await ConvertListToDictionary(resultJson); @@ -98,18 +98,20 @@ private async Task GetMaskinportenToken() return token; } - private async Task> ConvertListToDictionary(string jsonString) + private static Task> ConvertListToDictionary(string jsonString) { var list = JsonConvert.DeserializeObject>>(jsonString); - - var mergedDictionary = list.SelectMany(d => d) - .GroupBy(kvp => kvp.Key) - .ToDictionary(g => g.Key, g => string.Join(",", g.Select(x => x.Value))); - - return mergedDictionary; + if (list != null) + return Task.FromResult( + list.SelectMany(d => d) + .GroupBy(kvp => kvp.Key) + .ToDictionary(g => g.Key, g => string.Join(",", g.Select(x => x.Value))) + ); + + return Task.FromResult(new Dictionary()); } - private bool IsJsonArray(string jsonString) + private static bool IsJsonArray(string jsonString) { try { @@ -122,11 +124,11 @@ private bool IsJsonArray(string jsonString) } } - private List GetQuery(string json) + private static List GetQuery(string json) { var list = JsonSerializer.Deserialize>>(json); - - var keys = list.Where(l => l.Any()).Select(l => l.Keys.First()).ToList(); - return keys; + if (list != null) + return list.Where(l => l.Count != 0).Select(l => l.Keys.First()).ToList(); + return new List(); } } diff --git a/src/Altinn.App.Core/Internal/Dan/IDanClient.cs b/src/Altinn.App.Core/Internal/Dan/IDanClient.cs index 98da0ae905..fe0e6255e1 100644 --- a/src/Altinn.App.Core/Internal/Dan/IDanClient.cs +++ b/src/Altinn.App.Core/Internal/Dan/IDanClient.cs @@ -10,7 +10,7 @@ public interface IDanClient /// /// Name of the dataset /// Usually ssn or OrgNumber - /// jmesPathExpression + /// fields to fetch from endpoint /// public Task> GetDataset(string dataset, string subject, string fields); } From ae93171c782e3b01829a145f7c3f2b510c5e21bc Mon Sep 17 00:00:00 2001 From: PERHOF Date: Tue, 13 Jan 2026 13:34:25 +0100 Subject: [PATCH 15/28] cleaned up and removed unnecessary code --- .../Infrastructure/Clients/Dan/DanClient.cs | 39 +++++-------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs index a02829df5d..39d1a8df08 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs @@ -1,12 +1,7 @@ -using System.Net.Http.Headers; -using System.Text; +using System.Text; using System.Text.Json; using Altinn.App.Core.Configuration; -using Altinn.App.Core.Features; -using Altinn.App.Core.Internal.Auth; using Altinn.App.Core.Internal.Dan; -using Altinn.App.Core.Models; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Newtonsoft.Json; using JsonException = Newtonsoft.Json.JsonException; @@ -19,23 +14,20 @@ namespace Altinn.App.Core.Infrastructure.Clients.Dan; /// public class DanClient : IDanClient { - private HttpClient _httpClient; - private IOptions _settings; - private IAuthenticationTokenResolver _authenticationTokenResolver; + private readonly HttpClient _httpClient; + private readonly IOptions _settings; /// /// Constructor /// - /// + /// /// - /// - public DanClient(HttpClient httpClient, IOptions settings, IServiceProvider serviceProvider) + public DanClient(IHttpClientFactory factory, IOptions settings) { - _httpClient = httpClient; + _httpClient = factory.CreateClient("DanClient"); _settings = settings; - _authenticationTokenResolver = serviceProvider.GetRequiredService(); - httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); - httpClient.BaseAddress = new Uri(settings.Value.BaseUrl); + _httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); + _httpClient.BaseAddress = new Uri(settings.Value.BaseUrl); } /// @@ -48,8 +40,6 @@ public DanClient(HttpClient httpClient, IOptions settings, IService public async Task> GetDataset(string dataset, string subject, string fields) { _httpClient.DefaultRequestHeaders.Clear(); - var token = await GetMaskinportenToken(); - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Value); _httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", _settings.Value.SubscriptionKey); var body = new { Subject = subject }; @@ -64,10 +54,10 @@ public async Task> GetDataset(string dataset, string baseQuery += $",{jsonKey} : {jsonKey}"; } - //ensures that th query returns a list if endpoint returns a list and an object when endpoint returns a single object + //ensures that the query returns a list if endpoint returns a list and an object when endpoint returns a single object var query = "[].{" + baseQuery + "}||{" + baseQuery + "}"; - var result = await _httpClient.PostAsync($"directharvest/{dataset}?envelope=false&query={query}", content); + var result = await _httpClient.PostAsync($"directharvest/{dataset}?envelope=false&reuseToken=true&query={query}", content); if (result.IsSuccessStatusCode) { @@ -89,15 +79,6 @@ public async Task> GetDataset(string dataset, string return new Dictionary(); } - private async Task GetMaskinportenToken() - { - var token = await _authenticationTokenResolver.GetAccessToken( - AuthenticationMethod.Maskinporten(_settings.Value.Scope), - CancellationToken.None - ); - return token; - } - private static Task> ConvertListToDictionary(string jsonString) { var list = JsonConvert.DeserializeObject>>(jsonString); From 77d68fe8e92ddcec7da731ab3f7102008554ce39 Mon Sep 17 00:00:00 2001 From: PERHOF Date: Tue, 13 Jan 2026 13:37:20 +0100 Subject: [PATCH 16/28] removed unused code --- src/Altinn.App.Core/Configuration/DanSettings.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Altinn.App.Core/Configuration/DanSettings.cs b/src/Altinn.App.Core/Configuration/DanSettings.cs index 3ea82b8409..21d1224b9f 100644 --- a/src/Altinn.App.Core/Configuration/DanSettings.cs +++ b/src/Altinn.App.Core/Configuration/DanSettings.cs @@ -14,9 +14,4 @@ public class DanSettings /// Api subscription keys /// public required string SubscriptionKey { get; set; } - - /// - /// Maskinporten scope - /// - public required string Scope { get; set; } } From 96719e73c7c9373a05f1f48478763d75f6771b4b Mon Sep 17 00:00:00 2001 From: PERHOF Date: Wed, 14 Jan 2026 08:21:57 +0100 Subject: [PATCH 17/28] Removed unused packages --- src/Altinn.App.Core/Altinn.App.Core.csproj | 3 --- src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs | 5 ++++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Altinn.App.Core/Altinn.App.Core.csproj b/src/Altinn.App.Core/Altinn.App.Core.csproj index f6dca16217..1e6347348e 100644 --- a/src/Altinn.App.Core/Altinn.App.Core.csproj +++ b/src/Altinn.App.Core/Altinn.App.Core.csproj @@ -15,14 +15,11 @@ - - - diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs index 39d1a8df08..3c5c02caf9 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs @@ -57,7 +57,10 @@ public async Task> GetDataset(string dataset, string //ensures that the query returns a list if endpoint returns a list and an object when endpoint returns a single object var query = "[].{" + baseQuery + "}||{" + baseQuery + "}"; - var result = await _httpClient.PostAsync($"directharvest/{dataset}?envelope=false&reuseToken=true&query={query}", content); + var result = await _httpClient.PostAsync( + $"directharvest/{dataset}?envelope=false&reuseToken=true&query={query}", + content + ); if (result.IsSuccessStatusCode) { From e33ca336e466511bb37e177892e17d52a2e72069 Mon Sep 17 00:00:00 2001 From: PERHOF Date: Wed, 14 Jan 2026 10:39:04 +0100 Subject: [PATCH 18/28] move headers to constructor --- .../Infrastructure/Clients/Dan/DanClient.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs index 3c5c02caf9..a25f522606 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs @@ -26,6 +26,9 @@ public DanClient(IHttpClientFactory factory, IOptions settings) { _httpClient = factory.CreateClient("DanClient"); _settings = settings; + + _httpClient.DefaultRequestHeaders.Clear(); + _httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", _settings.Value.SubscriptionKey); _httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); _httpClient.BaseAddress = new Uri(settings.Value.BaseUrl); } @@ -39,9 +42,6 @@ public DanClient(IHttpClientFactory factory, IOptions settings) /// public async Task> GetDataset(string dataset, string subject, string fields) { - _httpClient.DefaultRequestHeaders.Clear(); - _httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", _settings.Value.SubscriptionKey); - var body = new { Subject = subject }; var myContent = JsonConvert.SerializeObject(body); HttpContent content = new StringContent(myContent, Encoding.UTF8, "application/json"); @@ -65,7 +65,7 @@ public async Task> GetDataset(string dataset, string if (result.IsSuccessStatusCode) { var dictionary = new Dictionary(); - var resultJson = result.Content.ReadAsStringAsync().Result; + var resultJson = await result.Content.ReadAsStringAsync(); //some datasets might return an array. The array needs to be serialized differently than a single object if (IsJsonArray(resultJson)) From b7900dc7cbd63bc01a13addca01bbcaaa56958af Mon Sep 17 00:00:00 2001 From: PERHOF Date: Wed, 14 Jan 2026 11:00:33 +0100 Subject: [PATCH 19/28] Added DanClientMock and fixed tests --- test/Altinn.App.Api.Tests/Mocks/DanClientMock.cs | 11 +++++++++++ test/Altinn.App.Api.Tests/Program.cs | 2 ++ .../Implementation/PrefillSITest.cs | 4 +++- ...icApi_ShouldNotChange_Unintentionally.verified.txt | 4 +--- 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 test/Altinn.App.Api.Tests/Mocks/DanClientMock.cs diff --git a/test/Altinn.App.Api.Tests/Mocks/DanClientMock.cs b/test/Altinn.App.Api.Tests/Mocks/DanClientMock.cs new file mode 100644 index 0000000000..f15df6694e --- /dev/null +++ b/test/Altinn.App.Api.Tests/Mocks/DanClientMock.cs @@ -0,0 +1,11 @@ +using Altinn.App.Core.Internal.Dan; + +namespace Altinn.App.Api.Tests.Mocks; + +public class DanClientMock : IDanClient +{ + public Task> GetDataset(string dataset, string subject, string fields) + { + throw new NotImplementedException(); + } +} diff --git a/test/Altinn.App.Api.Tests/Program.cs b/test/Altinn.App.Api.Tests/Program.cs index 35db26e247..c75c4a131f 100644 --- a/test/Altinn.App.Api.Tests/Program.cs +++ b/test/Altinn.App.Api.Tests/Program.cs @@ -11,6 +11,7 @@ using Altinn.App.Core.Internal.App; using Altinn.App.Core.Internal.AppModel; using Altinn.App.Core.Internal.Auth; +using Altinn.App.Core.Internal.Dan; using Altinn.App.Core.Internal.Data; using Altinn.App.Core.Internal.Events; using Altinn.App.Core.Internal.Instances; @@ -116,6 +117,7 @@ void ConfigureMockServices(IServiceCollection services, ConfigurationManager con services.AddTransient>(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.PostConfigureAll(options => { diff --git a/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs b/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs index bb3c2dea4f..00e7477c63 100644 --- a/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs +++ b/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs @@ -1,6 +1,7 @@ using Altinn.App.Core.Features.Auth; using Altinn.App.Core.Implementation; using Altinn.App.Core.Internal.App; +using Altinn.App.Core.Internal.Dan; using Altinn.App.Core.Internal.Registers; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -47,6 +48,7 @@ public async Task PrefillDataModel_AssignsValuesCorrectly() var authenticationContextMock = new Mock(); var services = new ServiceCollection(); var registryClientMock = new Mock(); + var danClientMock = new Mock(); services.AddSingleton(registryClientMock.Object); await using var sp = services.BuildStrictServiceProvider(); @@ -55,7 +57,7 @@ public async Task PrefillDataModel_AssignsValuesCorrectly() appResourcesMock.Object, authenticationContextMock.Object, sp, - null + danClientMock.Object ); prefillToTest.PrefillDataModel(dataModel, externalPrefill, continueOnError: false); diff --git a/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt b/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt index e5a36ebfef..bbabe7330a 100644 --- a/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt +++ b/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt @@ -55,9 +55,7 @@ namespace Altinn.App.Core.Configuration { public DanSettings() { } public required string BaseUrl { get; set; } - public required string Scope { get; set; } public required string SubscriptionKey { get; set; } - public required string SubscriptionKeySkipsRegistrene { get; set; } } public class FrontEndSettings : System.Collections.Generic.Dictionary { @@ -2408,7 +2406,7 @@ namespace Altinn.App.Core.Infrastructure.Clients.Dan { public class DanClient : Altinn.App.Core.Internal.Dan.IDanClient { - public DanClient(System.Net.Http.HttpClient httpClient, Microsoft.Extensions.Options.IOptions settings, System.IServiceProvider serviceProvider) { } + public DanClient(System.Net.Http.IHttpClientFactory factory, Microsoft.Extensions.Options.IOptions settings) { } public System.Threading.Tasks.Task> GetDataset(string dataset, string subject, string fields) { } } } From f59f86d3e56cb234521bc30e4a0bedcd1fda077a Mon Sep 17 00:00:00 2001 From: PERHOF Date: Wed, 14 Jan 2026 11:44:51 +0100 Subject: [PATCH 20/28] Return empty dictionary if fields are not specified --- src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs index a25f522606..5eee7485a6 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs @@ -47,6 +47,9 @@ public async Task> GetDataset(string dataset, string HttpContent content = new StringContent(myContent, Encoding.UTF8, "application/json"); var fieldsToFill = GetQuery(fields); + if (fieldsToFill.Count == 0) + return new Dictionary(); + var baseQuery = $"{fieldsToFill.First()} : {fieldsToFill.First()}"; foreach (var jsonKey in fieldsToFill.Skip(1)) { From 775f4c7f5836aa2f4c695c5aae9c90ec0facbad2 Mon Sep 17 00:00:00 2001 From: PERHOF Date: Wed, 14 Jan 2026 10:39:31 +0100 Subject: [PATCH 21/28] added null check for subject and dataset name --- src/Altinn.App.Core/Implementation/PrefillSI.cs | 15 +++++++++++++++ test/Altinn.App.Api.Tests/Program.cs | 7 +++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Altinn.App.Core/Implementation/PrefillSI.cs b/src/Altinn.App.Core/Implementation/PrefillSI.cs index 8dc1ef7364..2effa5fe2b 100644 --- a/src/Altinn.App.Core/Implementation/PrefillSI.cs +++ b/src/Altinn.App.Core/Implementation/PrefillSI.cs @@ -224,6 +224,16 @@ when await systemUser.LoadDetails() is { } details && details.Party.PartyId == p { var datasetName = dataset.SelectToken("name")?.ToString(); var subject = !string.IsNullOrWhiteSpace(party.SSN) ? party.SSN : party.OrgNumber; + + if (string.IsNullOrEmpty(subject)) + { + _logger.LogError( + "Could not prefill from {DanKey}, no valid subject (SSN or OrgNumber) found for party", + _danKey + ); + continue; + } + var fields = dataset.SelectToken("mappings"); if (fields != null) { @@ -250,6 +260,11 @@ when await systemUser.LoadDetails() is { } details && details.Party.PartyId == p _logger.LogError(errorMessage); } } + else + { + string errorMessage = $"Could not prefill from {_danKey}, dataset name is not defined."; + _logger.LogError(errorMessage); + } } } } diff --git a/test/Altinn.App.Api.Tests/Program.cs b/test/Altinn.App.Api.Tests/Program.cs index c75c4a131f..9f7ba6fdf4 100644 --- a/test/Altinn.App.Api.Tests/Program.cs +++ b/test/Altinn.App.Api.Tests/Program.cs @@ -68,8 +68,11 @@ builder.Services.Configure(settings => settings.DisableLocaltestValidation = true); builder.Services.Configure(settings => settings.DisableAppConfigurationCache = true); builder.Services.Configure(settings => settings.IsTest = true); -builder.Services.Configure(settings => settings.BaseUrl = "http://localhost:7071/v1/"); -builder.Configuration.GetSection("GeneralSettings:IsTest").Value = "true"; +builder.Services.Configure(settings => +{ + settings.BaseUrl = "http://localhost:7071/v1/"; + settings.SubscriptionKey = "test-subscription-key"; +});builder.Configuration.GetSection("GeneralSettings:IsTest").Value = "true"; // AppConfigurationCache.Disable = true; From 94ade4f826de7b503bbae43c1a1e3fe56d6d2219 Mon Sep 17 00:00:00 2001 From: PERHOF Date: Wed, 14 Jan 2026 13:28:16 +0100 Subject: [PATCH 22/28] moved dan DI to correct method DI for settings if they exist Made DanClient optional moved danclient in DI --- .../Extensions/ServiceCollectionExtensions.cs | 8 ++++++-- src/Altinn.App.Core/Implementation/PrefillSI.cs | 6 +++--- test/Altinn.App.Api.Tests/Program.cs | 5 +++-- ...PublicApi_ShouldNotChange_Unintentionally.verified.txt | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs index 96b00001d6..3c5b0b0c97 100644 --- a/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs +++ b/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs @@ -98,7 +98,6 @@ IWebHostEnvironment env services.Configure(configuration.GetSection("GeneralSettings")); services.Configure(configuration.GetSection("PlatformSettings")); services.Configure(configuration.GetSection("CacheSettings")); - services.Configure(configuration.GetSection("DanClientSettings")); AddApplicationIdentifier(services); @@ -106,7 +105,6 @@ IWebHostEnvironment env services.AddHttpClient(); services.AddHttpClient(); services.AddHttpClient(); - services.AddHttpClient(); services.AddHttpClient(); services.AddHttpClient(); services.AddHttpClient(); @@ -199,6 +197,12 @@ IWebHostEnvironment env services.Configure(configuration.GetSection("AccessTokenSettings")); services.Configure(configuration.GetSection(nameof(FrontEndSettings))); services.Configure(configuration.GetSection(nameof(PdfGeneratorSettings))); + var danSettings = configuration.GetSection("DanClientSettings"); + if (danSettings.Exists()) + { + services.AddHttpClient(); + services.Configure(danSettings); + } services.AddRuntimeEnvironment(); if (env.IsDevelopment()) diff --git a/src/Altinn.App.Core/Implementation/PrefillSI.cs b/src/Altinn.App.Core/Implementation/PrefillSI.cs index 2effa5fe2b..58352d4dd9 100644 --- a/src/Altinn.App.Core/Implementation/PrefillSI.cs +++ b/src/Altinn.App.Core/Implementation/PrefillSI.cs @@ -21,7 +21,7 @@ public class PrefillSI : IPrefill private readonly IAppResources _appResourcesService; private readonly IRegisterClient _registerClient; private readonly IAuthenticationContext _authenticationContext; - private readonly IDanClient _danClient; + private readonly IDanClient? _danClient; private readonly Telemetry? _telemetry; private static readonly string _erKey = "ER"; private static readonly string _dsfKey = "DSF"; @@ -44,7 +44,7 @@ public PrefillSI( IAppResources appResourcesService, IAuthenticationContext authenticationContext, IServiceProvider serviceProvider, - IDanClient danClient, + IDanClient? danClient = null, Telemetry? telemetry = null ) { @@ -262,7 +262,7 @@ when await systemUser.LoadDetails() is { } details && details.Party.PartyId == p } else { - string errorMessage = $"Could not prefill from {_danKey}, dataset name is not defined."; + string errorMessage = $"Could not prefill from {_danKey}, dataset name is not defined."; _logger.LogError(errorMessage); } } diff --git a/test/Altinn.App.Api.Tests/Program.cs b/test/Altinn.App.Api.Tests/Program.cs index 9f7ba6fdf4..c533c8bec5 100644 --- a/test/Altinn.App.Api.Tests/Program.cs +++ b/test/Altinn.App.Api.Tests/Program.cs @@ -72,7 +72,8 @@ { settings.BaseUrl = "http://localhost:7071/v1/"; settings.SubscriptionKey = "test-subscription-key"; -});builder.Configuration.GetSection("GeneralSettings:IsTest").Value = "true"; +}); +builder.Configuration.GetSection("GeneralSettings:IsTest").Value = "true"; // AppConfigurationCache.Disable = true; @@ -120,7 +121,7 @@ void ConfigureMockServices(IServiceCollection services, ConfigurationManager con services.AddTransient>(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); + services.AddSingleton(); services.PostConfigureAll(options => { diff --git a/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt b/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt index bbabe7330a..22766a75ac 100644 --- a/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt +++ b/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt @@ -2371,7 +2371,7 @@ namespace Altinn.App.Core.Implementation } public class PrefillSI : Altinn.App.Core.Internal.Prefill.IPrefill { - public PrefillSI(Microsoft.Extensions.Logging.ILogger logger, Altinn.App.Core.Internal.App.IAppResources appResourcesService, Altinn.App.Core.Features.Auth.IAuthenticationContext authenticationContext, System.IServiceProvider serviceProvider, Altinn.App.Core.Internal.Dan.IDanClient danClient, Altinn.App.Core.Features.Telemetry? telemetry = null) { } + public PrefillSI(Microsoft.Extensions.Logging.ILogger logger, Altinn.App.Core.Internal.App.IAppResources appResourcesService, Altinn.App.Core.Features.Auth.IAuthenticationContext authenticationContext, System.IServiceProvider serviceProvider, Altinn.App.Core.Internal.Dan.IDanClient? danClient = null, Altinn.App.Core.Features.Telemetry? telemetry = null) { } public void PrefillDataModel(object dataModel, System.Collections.Generic.Dictionary externalPrefill, bool continueOnError = false) { } public System.Threading.Tasks.Task PrefillDataModel(string partyId, string dataModelName, object dataModel, System.Collections.Generic.Dictionary? externalPrefill = null) { } } From 90e3014a72b5cb5afb1b63bb117734e6eb7689fd Mon Sep 17 00:00:00 2001 From: PERHOF Date: Wed, 14 Jan 2026 14:42:24 +0100 Subject: [PATCH 23/28] removed mocked client --- test/Altinn.App.Api.Tests/Mocks/DanClientMock.cs | 11 ----------- test/Altinn.App.Api.Tests/Program.cs | 3 ++- 2 files changed, 2 insertions(+), 12 deletions(-) delete mode 100644 test/Altinn.App.Api.Tests/Mocks/DanClientMock.cs diff --git a/test/Altinn.App.Api.Tests/Mocks/DanClientMock.cs b/test/Altinn.App.Api.Tests/Mocks/DanClientMock.cs deleted file mode 100644 index f15df6694e..0000000000 --- a/test/Altinn.App.Api.Tests/Mocks/DanClientMock.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Altinn.App.Core.Internal.Dan; - -namespace Altinn.App.Api.Tests.Mocks; - -public class DanClientMock : IDanClient -{ - public Task> GetDataset(string dataset, string subject, string fields) - { - throw new NotImplementedException(); - } -} diff --git a/test/Altinn.App.Api.Tests/Program.cs b/test/Altinn.App.Api.Tests/Program.cs index c533c8bec5..a637705d7c 100644 --- a/test/Altinn.App.Api.Tests/Program.cs +++ b/test/Altinn.App.Api.Tests/Program.cs @@ -8,6 +8,7 @@ using Altinn.App.Core.Configuration; using Altinn.App.Core.Features; using Altinn.App.Core.Features.Cache; +using Altinn.App.Core.Infrastructure.Clients.Dan; using Altinn.App.Core.Internal.App; using Altinn.App.Core.Internal.AppModel; using Altinn.App.Core.Internal.Auth; @@ -121,7 +122,7 @@ void ConfigureMockServices(IServiceCollection services, ConfigurationManager con services.AddTransient>(); services.AddTransient(); services.AddTransient(); - services.AddSingleton(); + services.AddSingleton(); services.PostConfigureAll(options => { From b92b403322b7289e9f186aa01108c7566fa6feab Mon Sep 17 00:00:00 2001 From: PERHOF Date: Wed, 14 Jan 2026 15:04:46 +0100 Subject: [PATCH 24/28] removed unneccessary code and added null check --- src/Altinn.App.Core/Implementation/PrefillSI.cs | 2 +- test/Altinn.App.Api.Tests/Program.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Altinn.App.Core/Implementation/PrefillSI.cs b/src/Altinn.App.Core/Implementation/PrefillSI.cs index 58352d4dd9..d3f0e513bd 100644 --- a/src/Altinn.App.Core/Implementation/PrefillSI.cs +++ b/src/Altinn.App.Core/Implementation/PrefillSI.cs @@ -215,7 +215,7 @@ when await systemUser.LoadDetails() is { } details && details.Party.PartyId == p // Prefill from Dan JToken? danConfiguration = prefillConfiguration.SelectToken(_danKey); - if (danConfiguration != null) + if (danConfiguration != null && _danClient != null) { var datasetList = danConfiguration.SelectToken("datasets"); if (datasetList != null) diff --git a/test/Altinn.App.Api.Tests/Program.cs b/test/Altinn.App.Api.Tests/Program.cs index a637705d7c..b8f504c580 100644 --- a/test/Altinn.App.Api.Tests/Program.cs +++ b/test/Altinn.App.Api.Tests/Program.cs @@ -122,7 +122,6 @@ void ConfigureMockServices(IServiceCollection services, ConfigurationManager con services.AddTransient>(); services.AddTransient(); services.AddTransient(); - services.AddSingleton(); services.PostConfigureAll(options => { From 838af72533c78b322673bc78796ca258261d95ef Mon Sep 17 00:00:00 2001 From: PERHOF Date: Thu, 15 Jan 2026 10:48:16 +0100 Subject: [PATCH 25/28] Added test for prefill of Dan data --- .../Implementation/PrefillSITest.cs | 49 +++++++++++++++++++ .../Fixtures/MockedServiceCollection.cs | 4 ++ 2 files changed, 53 insertions(+) diff --git a/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs b/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs index 00e7477c63..52746bcda1 100644 --- a/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs +++ b/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs @@ -3,6 +3,7 @@ using Altinn.App.Core.Internal.App; using Altinn.App.Core.Internal.Dan; using Altinn.App.Core.Internal.Registers; +using Altinn.Platform.Register.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Moq; @@ -71,4 +72,52 @@ public async Task PrefillDataModel_AssignsValuesCorrectly() Assert.Equal("S'oderberg og Partners", dataModel.Prefill.YrkesskadeforsikringNavn); Assert.Equal("2023-12-31T12:00:00.000+01:00", dataModel.Prefill.YrkesskadeforsikringGyldigTilDato); } + + [Fact] + public async Task PrefillDataModel_Should_Fill_With_Data_From_Dan() + { + // Arrange + var dataModel = new PrefillTestDataModel(); + + var loggerMock = new Mock>(); + var appResourcesMock = new Mock(); + var authenticationContextMock = new Mock(); + var services = new ServiceCollection(); + var registryClientMock = new Mock(); + var danClientMock = new Mock(); + services.AddSingleton(registryClientMock.Object); + await using var sp = services.BuildStrictServiceProvider(); + + var prefillToTest = new PrefillSI( + loggerMock.Object, + appResourcesMock.Object, + authenticationContextMock.Object, + sp, + danClientMock.Object + ); + + var modelName = "model"; + var partyId = "1234"; + + var party = new Party() { PartyId = 1234, SSN = "12341234" }; + + appResourcesMock.Setup(ar => ar.GetPrefillJson(It.IsAny())).Returns(GetJsonConfig()); + registryClientMock + .Setup(m => m.GetPartyUnchecked(It.IsAny(), It.IsAny())) + .ReturnsAsync(party); + + //Act + prefillToTest.PrefillDataModel(partyId, modelName, dataModel); + + //Assert + danClientMock.Verify( + m => m.GetDataset(It.IsAny(), It.IsAny(), It.IsAny()), + Times.AtLeastOnce + ); + } + + private string GetJsonConfig() + { + return "{\n \"$schema\" : \"https://altinncdn.no/schemas/json/prefill/prefill.schema.v1.json\",\n \"allowOverwrite\" : true,\n \"DAN\" : {\n \"datasets\" : [ {\n \"name\" : \"UnitBasicInformation\",\n \"mappings\" : [ {\n \"BusinessAddressCity\" : \"Email\"\n }, {\n \"SectorCode\" : \"OrganizationNumber\"\n } ]\n } ]\n }\n}"; + } } diff --git a/test/Altinn.App.Tests.Common/Fixtures/MockedServiceCollection.cs b/test/Altinn.App.Tests.Common/Fixtures/MockedServiceCollection.cs index 05c78079ac..b0d055e22b 100644 --- a/test/Altinn.App.Tests.Common/Fixtures/MockedServiceCollection.cs +++ b/test/Altinn.App.Tests.Common/Fixtures/MockedServiceCollection.cs @@ -6,10 +6,12 @@ using Altinn.App.Core.Configuration; using Altinn.App.Core.Features; using Altinn.App.Core.Helpers.Serialization; +using Altinn.App.Core.Infrastructure.Clients.Dan; using Altinn.App.Core.Infrastructure.Clients.Storage; using Altinn.App.Core.Internal.App; using Altinn.App.Core.Internal.AppModel; using Altinn.App.Core.Internal.Auth; +using Altinn.App.Core.Internal.Dan; using Altinn.App.Core.Internal.Data; using Altinn.App.Core.Internal.Instances; using Altinn.App.Core.Internal.Texts; @@ -98,6 +100,8 @@ public void TryAddCommonServices() _services.AddHttpClient().ConfigurePrimaryHttpMessageHandler(() => Storage); _services.AddHttpClient().ConfigurePrimaryHttpMessageHandler(() => Storage); + _services.TryAddSingleton(); + _services.TryAddSingleton(); _services.AddLogging(builder => { From fd65707a24a601484d1947947351c6a935cd3e38 Mon Sep 17 00:00:00 2001 From: PERHOF Date: Thu, 15 Jan 2026 13:26:14 +0100 Subject: [PATCH 26/28] =?UTF-8?q?various=20fixes=20recommended=20p=C3=A5?= =?UTF-8?q?=20codeQL=20and=20coderabbit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Infrastructure/Clients/Dan/DanClient.cs | 34 ++++++++----------- .../Implementation/PrefillSITest.cs | 2 +- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs index 5eee7485a6..46c8e8c8ef 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Dan/DanClient.cs @@ -44,7 +44,6 @@ public async Task> GetDataset(string dataset, string { var body = new { Subject = subject }; var myContent = JsonConvert.SerializeObject(body); - HttpContent content = new StringContent(myContent, Encoding.UTF8, "application/json"); var fieldsToFill = GetQuery(fields); if (fieldsToFill.Count == 0) @@ -59,28 +58,25 @@ public async Task> GetDataset(string dataset, string //ensures that the query returns a list if endpoint returns a list and an object when endpoint returns a single object var query = "[].{" + baseQuery + "}||{" + baseQuery + "}"; - - var result = await _httpClient.PostAsync( - $"directharvest/{dataset}?envelope=false&reuseToken=true&query={query}", - content - ); - - if (result.IsSuccessStatusCode) + using (var content = new StringContent(myContent, Encoding.UTF8, "application/json")) { - var dictionary = new Dictionary(); - var resultJson = await result.Content.ReadAsStringAsync(); + var result = await _httpClient.PostAsync( + $"directharvest/{dataset}?envelope=false&reuseToken=true&query={query}", + content + ); - //some datasets might return an array. The array needs to be serialized differently than a single object - if (IsJsonArray(resultJson)) - { - dictionary = await ConvertListToDictionary(resultJson); - } - else + if (result.IsSuccessStatusCode) { - dictionary = JsonConvert.DeserializeObject>(resultJson); + Dictionary? dictionary; + + var resultJson = await result.Content.ReadAsStringAsync(); + + dictionary = IsJsonArray(resultJson) + ? await ConvertListToDictionary(resultJson) + : JsonConvert.DeserializeObject>(resultJson); + + return dictionary ?? new Dictionary(); } - if (dictionary != null) - return dictionary; } return new Dictionary(); } diff --git a/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs b/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs index 52746bcda1..8143c06f00 100644 --- a/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs +++ b/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs @@ -107,7 +107,7 @@ public async Task PrefillDataModel_Should_Fill_With_Data_From_Dan() .ReturnsAsync(party); //Act - prefillToTest.PrefillDataModel(partyId, modelName, dataModel); + await prefillToTest.PrefillDataModel(partyId, modelName, dataModel); //Assert danClientMock.Verify( From 2d7bd80ae519a66e8486e3719585a0dd7ada868a Mon Sep 17 00:00:00 2001 From: PERHOF Date: Mon, 19 Jan 2026 09:50:47 +0100 Subject: [PATCH 27/28] fixed test --- .../Implementation/PrefillSITest.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs b/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs index 8143c06f00..99a1ef9b36 100644 --- a/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs +++ b/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs @@ -15,6 +15,12 @@ public class PrefillTestDataModel public TestPrefillFields? Prefill { get; set; } } +public class PrefillDanTestDataModel +{ + public string Email { get; set; } + public string OrganizationNumber { get; set; } +} + public class TestPrefillFields { public string? EraSourceEnvironment { get; set; } @@ -77,7 +83,7 @@ public async Task PrefillDataModel_AssignsValuesCorrectly() public async Task PrefillDataModel_Should_Fill_With_Data_From_Dan() { // Arrange - var dataModel = new PrefillTestDataModel(); + var dataModel = new PrefillDanTestDataModel(); var loggerMock = new Mock>(); var appResourcesMock = new Mock(); @@ -99,12 +105,22 @@ public async Task PrefillDataModel_Should_Fill_With_Data_From_Dan() var modelName = "model"; var partyId = "1234"; + // danData should match the data in the GetJsonConfig method + var danData = new Dictionary + { + { "BusinessAddressCity", "Email" }, + { "SectorCode", "OrganizationNumber" }, + }; + var party = new Party() { PartyId = 1234, SSN = "12341234" }; appResourcesMock.Setup(ar => ar.GetPrefillJson(It.IsAny())).Returns(GetJsonConfig()); registryClientMock .Setup(m => m.GetPartyUnchecked(It.IsAny(), It.IsAny())) .ReturnsAsync(party); + danClientMock + .Setup(m => m.GetDataset(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(danData); //Act await prefillToTest.PrefillDataModel(partyId, modelName, dataModel); From 39d11d5fc60062873a8ac2a17cba700bd5fedb9c Mon Sep 17 00:00:00 2001 From: PERHOF Date: Mon, 19 Jan 2026 10:05:48 +0100 Subject: [PATCH 28/28] Made fields in test model nullable --- test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs b/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs index 99a1ef9b36..204188eb69 100644 --- a/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs +++ b/test/Altinn.App.Core.Tests/Implementation/PrefillSITest.cs @@ -17,8 +17,8 @@ public class PrefillTestDataModel public class PrefillDanTestDataModel { - public string Email { get; set; } - public string OrganizationNumber { get; set; } + public string? Email { get; set; } + public string? OrganizationNumber { get; set; } } public class TestPrefillFields