├── .csharpierignore
├── .csharpierrc.yaml
├── src
├── Altinn.Codelists
│ ├── GlobalUsing.cs
│ ├── SSB
│ │ ├── readme.md
│ │ ├── Clients
│ │ │ ├── ClassificationSettings.cs
│ │ │ ├── ClassificationCodes.cs
│ │ │ ├── ClassificationCode.cs
│ │ │ ├── ClassificationsHttpClientCached.cs
│ │ │ └── ClassificationsHttpClient.cs
│ │ ├── IClassificationsClient.cs
│ │ ├── Models
│ │ │ └── Classification.cs
│ │ ├── ClassificationOptions.cs
│ │ └── Extensions
│ │ │ └── ServiceCollectionExtensions.cs
│ ├── RestCountries
│ │ ├── Data
│ │ │ └── Resources.cs
│ │ ├── readme.md
│ │ ├── Models
│ │ │ ├── Filter.cs
│ │ │ ├── Name.cs
│ │ │ ├── Currency.cs
│ │ │ └── Country.cs
│ │ ├── ICountryClient.cs
│ │ ├── Extensions
│ │ │ └── ServiceCollectionExtensions.cs
│ │ └── Clients
│ │ │ └── CountriesClient.cs
│ ├── Posten
│ │ ├── IPostalCodesClient.cs
│ │ ├── PostenSettings.cs
│ │ ├── Clients
│ │ │ ├── PostalCodesHttpClient.cs
│ │ │ ├── PostalCodesCsvParser.cs
│ │ │ └── PostalCodeRecord.cs
│ │ ├── PostalCodesCodelistsProvider.cs
│ │ └── Extensions
│ │ │ └── ServiceCollectionExtensions.cs
│ ├── Kartverket
│ │ └── AdministrativeUnits
│ │ │ ├── AdministrativeUnitsSettings.cs
│ │ │ ├── IAdministrativeUnitsClient.cs
│ │ │ ├── Models
│ │ │ ├── County.cs
│ │ │ └── Municipality.cs
│ │ │ ├── CountiesCodelistProvider.cs
│ │ │ ├── MunicipalitiesCodelistProvider.cs
│ │ │ ├── Extensions
│ │ │ └── ServiceCollectionExtensions.cs
│ │ │ └── Clients
│ │ │ ├── AdministrativeUnitsHttpClient.cs
│ │ │ └── AdministrativeUnitsHttpClientCached.cs
│ ├── Extensions
│ │ ├── CollectionExtensions.cs
│ │ └── ServiceCollectionExtensions.cs
│ ├── Altinn.Codelists.csproj
│ └── Utilities
│ │ ├── PredicateBuilder.cs
│ │ └── EmbeddedResource.cs
├── Directory.Build.targets
└── Directory.Build.props
├── test
└── Altinn.Codelists.Tests
│ ├── RestCountries
│ ├── restCountries.http
│ └── Clients
│ │ └── CountriesClientTests.cs
│ ├── Kartverket
│ └── AdministrativeUnits
│ │ ├── Testdata
│ │ ├── fylke46_kommuner.json
│ │ ├── administrativeUnits.http
│ │ └── fylker.json
│ │ ├── Extensions
│ │ └── ServiceCollectionTests.cs
│ │ ├── CountiesCodelistProviderTests.cs
│ │ ├── Clients
│ │ ├── AdministrativeUnitsClientTests.cs
│ │ └── CountiesClientCachedTests.cs
│ │ ├── MunicipalitiesCodelistProviderTests.cs
│ │ └── Mocks
│ │ └── AdministrativeUnitsHttpClientMock.cs
│ ├── ModuleInitializer.cs
│ ├── GlobalUsing.cs
│ ├── SSB
│ ├── Testdata
│ │ ├── sex.json
│ │ ├── units.json
│ │ ├── classifications.http
│ │ ├── smallGame_Variant.json
│ │ ├── maritalStatus.json
│ │ ├── counties.json
│ │ └── baseAmountNationalInsurance.json
│ ├── CountiesCodelistProviderTests.cs
│ ├── CountriesCodelistProviderTests.cs
│ ├── MaritalStatusCodelistProviderTests.cs
│ ├── MunicipalitiesCodelistProviderTests.cs
│ ├── Extensions
│ │ └── ExtensionTests.cs
│ ├── BaseAmountNationalInsuranceCodelistProviderTests.cs
│ ├── UnitsCodelistProviderTests.cs
│ ├── SmallGameVariantTests.cs
│ ├── Clients
│ │ ├── ClassificationsHttpClientTests.cs
│ │ └── ClassificationsHttpClientCachedTests.cs
│ ├── SexCodelistProviderTests.cs
│ ├── ClassificationOptionsTests.cs
│ ├── OccupationsCodelistProviderTests.cs
│ ├── IndustryGroupingCodelistProviderTests.cs
│ └── Mocks
│ │ └── ClassificationsHttpClientMock.cs
│ ├── PublicApiTests.cs
│ ├── EmbeddedResource.cs
│ ├── Posten
│ ├── Clients
│ │ ├── PostalCodesHttpClientTests.cs
│ │ ├── PostenFixture.cs
│ │ └── mappings
│ │ │ └── wiremock_GET_postnummerregister-ansi.txt.json
│ └── PostalCodesCodelistProviderTests.cs
│ ├── EnsureNoCodelistIdCollisions.cs
│ └── Altinn.Codelists.Tests.csproj
├── global.json
├── renovate.json
├── .config
└── dotnet-tools.json
├── .github
├── release.yml
└── workflows
│ ├── dotnet-test.yml
│ ├── publish-release.yml
│ ├── codeql.yml
│ ├── test-and-analyze-fork.yml
│ └── test-and-analyze.yml
├── .vscode
├── launch.json
└── tasks.json
├── LICENSE
├── Directory.Packages.props
├── Directory.Build.props
├── Altinn.Codelists.sln
├── .gitattributes
├── .gitignore
├── .editorconfig
└── README.md
/.csharpierignore:
--------------------------------------------------------------------------------
1 | bin/
2 | obj/
3 | .git/
4 | *.xml
5 | *.csproj
6 | *.props
7 | *.targets
8 |
--------------------------------------------------------------------------------
/.csharpierrc.yaml:
--------------------------------------------------------------------------------
1 | printWidth: 120
2 | useTabs: false
3 | tabWidth: 4
4 | endOfLine: auto
5 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/GlobalUsing.cs:
--------------------------------------------------------------------------------
1 | global using System.Text;
2 | global using System.Text.Json;
3 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/RestCountries/restCountries.http:
--------------------------------------------------------------------------------
1 | # Response is saved raw into countries.json
2 | GET https://restcountries.com/v3.1/all
3 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/SSB/readme.md:
--------------------------------------------------------------------------------
1 | # SSB
2 | Various codelists provided by SSB public api's.
3 |
4 | Source: https://data.ssb.no/api/klass/v1/api-guide.html
5 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "8.0.416",
4 | "rollForward": "latestFeature",
5 | "allowPrerelease": false
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "local>Altinn/renovate-config"
5 | ],
6 | "ignoreDeps": ["Altinn.App.Core"]
7 | }
8 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/Testdata/fylke46_kommuner.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/altinn/codelists-lib-dotnet/main/test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/Testdata/fylke46_kommuner.json
--------------------------------------------------------------------------------
/src/Altinn.Codelists/RestCountries/Data/Resources.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Codelists.RestCountries.Data;
2 |
3 | internal static class Resources
4 | {
5 | internal const string CountriesJson = "Altinn.Codelists.RestCountries.Data.countries.json";
6 | }
7 |
--------------------------------------------------------------------------------
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "csharpier": {
6 | "version": "1.2.1",
7 | "commands": [
8 | "csharpier"
9 | ],
10 | "rollForward": false
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/ModuleInitializer.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace Altinn.Codelists.Tests;
4 |
5 | internal static class ModuleInitializer
6 | {
7 | [ModuleInitializer]
8 | public static void Initialize()
9 | {
10 | VerifierSettings.AutoVerify(includeBuildServer: false);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/GlobalUsing.cs:
--------------------------------------------------------------------------------
1 | global using System;
2 | global using System.Collections.Generic;
3 | global using System.Linq;
4 | global using System.Net.Http;
5 | global using System.Threading;
6 | global using System.Threading.Tasks;
7 | global using Microsoft.Extensions.Options;
8 | global using Xunit;
9 | global using Xunit.Abstractions;
10 |
--------------------------------------------------------------------------------
/src/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 | $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)'))
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Posten/IPostalCodesClient.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.Posten.Clients;
2 |
3 | namespace Altinn.Codelists.Posten;
4 |
5 | ///
6 | /// Client for getting the offical list of post codes in Norway.
7 | ///
8 | public interface IPostalCodesClient
9 | {
10 | ///
11 | /// Get all postal codes.
12 | ///
13 | ///
14 | Task> GetPostalCodes();
15 | }
16 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/SSB/Clients/ClassificationSettings.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Codelists.SSB.Clients;
2 |
3 | ///
4 | /// Options to control the behavior of
5 | ///
6 | public class ClassificationSettings
7 | {
8 | ///
9 | /// Base url to the api endpoint for classifications.
10 | ///
11 | public string BaseApiUrl { get; set; } = "https://data.ssb.no/api/klass/v1/classifications/";
12 | }
13 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/RestCountries/readme.md:
--------------------------------------------------------------------------------
1 | # REST Countries
2 | All countries of the world with various additional data, like: region, subregion, capital, currency, capital city etc.
3 |
4 | Source: https://restcountries.com/
5 | Last updated: 09.07.2022
6 |
7 | ## Updates
8 | The json data is stored within the nuget package due to it's size and to ensure it's always available.
9 | The update frequency is naturaly low and is done by fetching the source and storing the response json
10 | in the data folder as countries.json.
11 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/SSB/Clients/ClassificationCodes.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace Altinn.Codelists.SSB.Clients;
4 |
5 | ///
6 | /// List of classification codes from a specified classification.
7 | ///
8 | public class ClassificationCodes
9 | {
10 | ///
11 | /// List of codes for a given classification.
12 | ///
13 | [JsonPropertyName("codes")]
14 | public List Codes { get; set; } = new List();
15 | }
16 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Kartverket/AdministrativeUnits/AdministrativeUnitsSettings.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits;
2 |
3 | ///
4 | /// Options to control the behavior of
5 | ///
6 | public class AdministrativeUnitsSettings
7 | {
8 | ///
9 | /// Base url to the API endpoint for information on administrative units.
10 | ///
11 | public string BaseApiUrl { get; set; } = "https://ws.geonorge.no/kommuneinfo/v1/";
12 | }
13 |
--------------------------------------------------------------------------------
/.github/release.yml:
--------------------------------------------------------------------------------
1 | changelog:
2 | exclude:
3 | labels:
4 | - ignore-for-release
5 | categories:
6 | - title: Breaking Changes 🛠
7 | labels:
8 | - semver-major
9 | - breaking-change
10 | - title: New Features 🎉
11 | labels:
12 | - semver-minor
13 | - feature
14 | - title: Bugfixes 🐛
15 | labels:
16 | - semver-patch
17 | - bugfix
18 | - title: Dependency Upgrades 📦
19 | labels:
20 | - dependency
21 | - title: Other Changes
22 | labels:
23 | - "*"
24 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/RestCountries/Models/Filter.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Codelists.RestCountries.Models;
2 |
3 | ///
4 | /// Class for filtering countries.
5 | ///
6 | public class Filter
7 | {
8 | ///
9 | /// Filter on region eg. Africa, Americas, Asia, Europe, Oceania
10 | ///
11 | public string? Region { get; set; }
12 |
13 | ///
14 | /// Filter on subregion eg. South America, Southern Europe, Central America, Eastern Asia, etc.
15 | ///
16 | public string? SubRegion { get; set; }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Posten/PostenSettings.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Codelists.Posten;
2 |
3 | ///
4 | /// Options to control the behavior of
5 | ///
6 | public class PostenSettings
7 | {
8 | internal static readonly string DefaultBaseUrl = "https://www.bring.no";
9 | internal static readonly string DefaultPath = "/postnummerregister-ansi.txt";
10 |
11 | ///
12 | /// URL to Brings postnummerregister API
13 | ///
14 | public string Url { get; set; } = $"{DefaultBaseUrl}{DefaultPath}";
15 | }
16 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/Testdata/administrativeUnits.http:
--------------------------------------------------------------------------------
1 | # Install Visual Studio Code Extension https://github.com/Huachao/vscode-restclient
2 | # to test the api used by the AdministrativeUnitsHttpClient
3 | # The json files commentet above the requests are stored as test data.
4 |
5 | ###
6 | # fylker.json
7 | GET https://ws.geonorge.no/kommuneinfo/v1/fylker
8 |
9 | ###
10 | # kommuner.json
11 | GET https://ws.geonorge.no/kommuneinfo/v1/kommuner
12 |
13 | ###
14 | # fylke46_kommuner.json
15 | GET https://ws.geonorge.no/kommuneinfo/v1/fylker/46?filtrer=kommuner,fylkesnavn,fylkesnummer
16 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/RestCountries/Models/Name.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace Altinn.Codelists.RestCountries.Models;
4 |
5 | ///
6 | /// Holds information about the name of a country.
7 | ///
8 | public class Name(string common, string official)
9 | {
10 | ///
11 | /// The common name
12 | ///
13 | [JsonPropertyName("common")]
14 | public string Common { get; set; } = common;
15 |
16 | ///
17 | /// The official name
18 | ///
19 | [JsonPropertyName("official")]
20 | public string Official { get; set; } = official;
21 | }
22 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/SSB/IClassificationsClient.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.SSB.Clients;
2 |
3 | namespace Altinn.Codelists.SSB;
4 |
5 | ///
6 | /// Client to get classification codes.
7 | ///
8 | public interface IClassificationsClient
9 | {
10 | ///
11 | /// Gets the codes for the specified classification. If no date is specified, the current date is used.
12 | ///
13 | Task GetClassificationCodes(
14 | int classificationId,
15 | string language = "nb",
16 | DateOnly? atDate = null,
17 | string level = "",
18 | string variant = "",
19 | string selectCodes = ""
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/RestCountries/ICountryClient.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.RestCountries.Models;
2 |
3 | namespace Altinn.Codelists.RestCountries;
4 |
5 | ///
6 | /// Information on all countries of the world.
7 | ///
8 | public interface ICountryClient
9 | {
10 | ///
11 | /// Get all the countries of the world.
12 | ///
13 | Task> GetCountries();
14 |
15 | ///
16 | /// Get all countries matching the provided filters.
17 | /// Values within the same filter object are AND'ed, while
18 | /// values between filter objects are OR'ed.
19 | ///
20 | Task> GetCountries(IEnumerable filters);
21 | }
22 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/RestCountries/Models/Currency.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace Altinn.Codelists.RestCountries.Models;
4 |
5 | ///
6 | /// Holds information of a currency used within a country.
7 | ///
8 | public class Currency(string name, string symbol)
9 | {
10 | ///
11 | /// The name of the curreny eg. Norwegian krone, United States dollar, Pound sterling
12 | ///
13 | [JsonPropertyName("name")]
14 | public string Name { get; set; } = name;
15 |
16 | ///
17 | /// The symbol used to identify the currency eg. kr, $, £
18 | ///
19 | [JsonPropertyName("symbol")]
20 | public string Symbol { get; set; } = symbol;
21 | }
22 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/RestCountries/Extensions/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.RestCountries.Clients;
2 | using Microsoft.Extensions.DependencyInjection;
3 |
4 | namespace Altinn.Codelists.RestCountries;
5 |
6 | ///
7 | /// Extends the .
8 | ///
9 | public static class ServiceCollectionExtensions
10 | {
11 | ///
12 | /// Registers the interface
13 | ///
14 | public static IServiceCollection AddRestCountriesClient(this IServiceCollection services)
15 | {
16 | services.AddHttpClient();
17 | services.AddTransient();
18 |
19 | return services;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/Testdata/sex.json:
--------------------------------------------------------------------------------
1 | {
2 | "codes": [
3 | {
4 | "code": "1",
5 | "parentCode": null,
6 | "level": "1",
7 | "name": "Mann",
8 | "shortName": "",
9 | "presentationName": "",
10 | "validFrom": null,
11 | "validTo": null,
12 | "validFromInRequestedRange": "2022-07-10",
13 | "validToInRequestedRange": null,
14 | "notes": ""
15 | },
16 | {
17 | "code": "2",
18 | "parentCode": null,
19 | "level": "1",
20 | "name": "Kvinne",
21 | "shortName": "",
22 | "presentationName": "",
23 | "validFrom": null,
24 | "validTo": null,
25 | "validFromInRequestedRange": "2022-07-10",
26 | "validToInRequestedRange": null,
27 | "notes": ""
28 | }
29 | ]
30 | }
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Posten/Clients/PostalCodesHttpClient.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Options;
2 |
3 | namespace Altinn.Codelists.Posten.Clients;
4 |
5 | internal sealed class PostalCodesHttpClient(HttpClient _httpClient, IOptions _options)
6 | : IPostalCodesClient
7 | {
8 | private readonly Uri _uri = new(_options.Value.Url);
9 |
10 | public async Task> GetPostalCodes()
11 | {
12 | using var response = await _httpClient.GetAsync(_uri.ToString(), HttpCompletionOption.ResponseHeadersRead);
13 | await using var responseStream = await response.Content.ReadAsStreamAsync();
14 |
15 | var parser = new PostalCodesCsvParser(responseStream);
16 | List result = await parser.Parse();
17 |
18 | return result;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/Extensions/ServiceCollectionTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Extensions;
3 | using Microsoft.Extensions.DependencyInjection;
4 |
5 | namespace Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Extensions;
6 |
7 | public class ServiceCollectionTests
8 | {
9 | [Fact]
10 | public void AddAdministrativeUnits_ShouldResolveAppOptionProviders()
11 | {
12 | var services = new ServiceCollection();
13 | services.AddKartverketAdministrativeUnits();
14 | var provider = services.BuildServiceProvider();
15 |
16 | IEnumerable appOptionsServices = provider.GetServices();
17 |
18 | Assert.Equal(2, appOptionsServices.Count());
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Extensions/CollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Codelists.Extensions;
2 |
3 | ///
4 | /// A class which contains useful methods for processing collections.
5 | ///
6 | internal static class CollectionExtensions
7 | {
8 | ///
9 | /// Checks whether enumerable is null or empty.
10 | ///
11 | /// The type of the enumerable.
12 | /// The System.Collections.Generic.IEnumerable`1 to be checked.
13 | /// True if enumerable is null or empty, false otherwise.
14 | public static bool IsNullOrEmpty(this IEnumerable enumerable)
15 | {
16 | if (enumerable != null)
17 | {
18 | return !enumerable.Any();
19 | }
20 |
21 | return true;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Extensions/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Extensions;
2 | using Altinn.Codelists.Posten;
3 | using Altinn.Codelists.SSB.Extensions;
4 | using Microsoft.Extensions.DependencyInjection;
5 |
6 | namespace Altinn.Codelists.Extensions;
7 |
8 | ///
9 | /// Extends the .
10 | ///
11 | public static class ServiceCollectionExtensions
12 | {
13 | ///
14 | /// Registers the services required to get support for all codelists.
15 | ///
16 | public static IServiceCollection AddAltinnCodelists(this IServiceCollection services)
17 | {
18 | services.AddKartverketAdministrativeUnits();
19 | services.AddSSBClassifications();
20 | services.AddPosten();
21 |
22 | return services;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Kartverket/AdministrativeUnits/IAdministrativeUnitsClient.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Models;
2 |
3 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits;
4 |
5 | ///
6 | /// Information on Norways offical administrative units for counties and municipalities.
7 | ///
8 | public interface IAdministrativeUnitsClient
9 | {
10 | ///
11 | /// Get all the counties of Norway.
12 | ///
13 | public Task> GetCounties();
14 |
15 | ///
16 | /// Get all the counties of Norway.
17 | ///
18 | public Task> GetMunicipalities();
19 |
20 | ///
21 | /// Get all the municipalities within the specified county.
22 | ///
23 | public Task> GetMunicipalities(string countyNumber);
24 | }
25 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Kartverket/AdministrativeUnits/Models/County.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits.Models;
4 |
5 | ///
6 | /// Holds information about a county (fylke).
7 | ///
8 | public class County(string number, string name)
9 | {
10 | ///
11 | /// Unique identification number for the county.
12 | ///
13 | [JsonPropertyName("fylkesnummer")]
14 | public string Number { get; set; } = number;
15 |
16 | ///
17 | /// The name of the county in Norwegian.
18 | ///
19 | [JsonPropertyName("fylkesnavn")]
20 | public string Name { get; set; } = name;
21 |
22 | ///
23 | /// List of municipalities within the county
24 | ///
25 | [JsonPropertyName("kommuner")]
26 | public List Municipalities { get; set; } = new List();
27 | }
28 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/PublicApiTests.cs:
--------------------------------------------------------------------------------
1 | using PublicApiGenerator;
2 |
3 | namespace Altinn.Codelists.Tests;
4 |
5 | public class PublicApiTests
6 | {
7 | private static readonly string[] _excludedAttributes =
8 | [
9 | "System.Runtime.CompilerServices.RefSafetyRulesAttribute",
10 | "System.Diagnostics.DebuggerNonUserCodeAttribute",
11 | "System.ComponentModel.EditorBrowsableAttribute",
12 | "System.Runtime.CompilerServices.InternalsVisibleToAttribute",
13 | ];
14 |
15 | [Fact]
16 | public async Task PublicApi_ShouldNotChange_Unintentionally()
17 | {
18 | // Arrange
19 | var assembly = typeof(Altinn.Codelists.Extensions.ServiceCollectionExtensions).Assembly;
20 |
21 | // Act
22 | var publicApi = assembly.GeneratePublicApi(new ApiGeneratorOptions { ExcludeAttributes = _excludedAttributes });
23 |
24 | // Assert
25 | await Verify(publicApi);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Kartverket/AdministrativeUnits/CountiesCodelistProvider.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.App.Core.Models;
3 |
4 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits;
5 |
6 | ///
7 | /// Provides a codelist for counties of Norway.
8 | ///
9 | internal sealed class CountiesCodelistProvider(IAdministrativeUnitsClient _countiesHttpClient) : IAppOptionsProvider
10 | {
11 | ///
12 | public string Id => "fylker-kv";
13 |
14 | ///
15 | public async Task GetAppOptionsAsync(string? language, Dictionary keyValuePairs)
16 | {
17 | var counties = await _countiesHttpClient.GetCounties();
18 |
19 | var appOptions = new AppOptions()
20 | {
21 | Options = counties.Select(x => new AppOption() { Value = x.Number, Label = x.Name }).ToList(),
22 | };
23 |
24 | return appOptions;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/Testdata/fylker.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "fylkesnavn": "Oslo",
4 | "fylkesnummer": "03"
5 | },
6 | {
7 | "fylkesnavn": "Rogaland",
8 | "fylkesnummer": "11"
9 | },
10 | {
11 | "fylkesnavn": "Møre og Romsdal",
12 | "fylkesnummer": "15"
13 | },
14 | {
15 | "fylkesnavn": "Nordland",
16 | "fylkesnummer": "18"
17 | },
18 | {
19 | "fylkesnavn": "Viken",
20 | "fylkesnummer": "30"
21 | },
22 | {
23 | "fylkesnavn": "Innlandet",
24 | "fylkesnummer": "34"
25 | },
26 | {
27 | "fylkesnavn": "Vestfold og Telemark",
28 | "fylkesnummer": "38"
29 | },
30 | {
31 | "fylkesnavn": "Agder",
32 | "fylkesnummer": "42"
33 | },
34 | {
35 | "fylkesnavn": "Vestland",
36 | "fylkesnummer": "46"
37 | },
38 | {
39 | "fylkesnavn": "Trøndelag",
40 | "fylkesnummer": "50"
41 | },
42 | {
43 | "fylkesnavn": "Troms og Finnmark",
44 | "fylkesnummer": "54"
45 | }
46 | ]
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Kartverket/AdministrativeUnits/Models/Municipality.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits.Models;
4 |
5 | ///
6 | /// Holds information about a municipality (kommune).
7 | ///
8 | public class Municipality(string number, string name, string nameInNorwegian)
9 | {
10 | ///
11 | /// Unique identification number for the municipality.
12 | ///
13 | [JsonPropertyName("kommunenummer")]
14 | public string Number { get; set; } = number;
15 |
16 | ///
17 | /// The name of the municipality in Norwegian or Sami.
18 | ///
19 | [JsonPropertyName("kommunenavn")]
20 | public string Name { get; set; } = name;
21 |
22 | ///
23 | /// The name of the municipality in Norwegian.
24 | ///
25 | [JsonPropertyName("kommunenavnNorsk")]
26 | public string NameInNorwegian { get; set; } = nameInNorwegian;
27 | }
28 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/CountiesCodelistProviderTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.Kartverket.AdministrativeUnits;
3 | using Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Mocks;
4 |
5 | namespace Altinn.Codelists.Tests.Kartverket.AdministrativeUnits;
6 |
7 | public class CountiesCodelistProviderTests
8 | {
9 | [Fact]
10 | public async Task GetAppOptionsAsync_ShouldReturnListOfCounties()
11 | {
12 | var administrativeUnitsHttpClientMock = new AdministrativeUnitsHttpClientMock(
13 | Options.Create(new AdministrativeUnitsSettings())
14 | );
15 | IAppOptionsProvider appOptionsProvider = new CountiesCodelistProvider(administrativeUnitsHttpClientMock);
16 |
17 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary());
18 |
19 | Assert.NotNull(appOptions.Options);
20 | Assert.Equal(11, appOptions.Options.Count);
21 | Assert.Equal("Vestland", appOptions.Options.First(x => x.Value == "46").Label);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/CountiesCodelistProviderTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.SSB;
3 | using Altinn.Codelists.SSB.Clients;
4 | using Altinn.Codelists.SSB.Models;
5 | using Altinn.Codelists.Tests.SSB.Mocks;
6 |
7 | namespace Altinn.Codelists.Tests.SSB;
8 |
9 | public class CountiesCodelistProviderTests
10 | {
11 | [Fact]
12 | public async Task GetAppOptionsAsync_ShouldReturnListOfCodes()
13 | {
14 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings()));
15 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider(
16 | "fylker",
17 | Classification.Counties,
18 | httpClientMock
19 | );
20 |
21 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary());
22 |
23 | Assert.NotNull(appOptions.Options);
24 | Assert.Equal(12, appOptions.Options.Count);
25 | Assert.Equal("Vestland", appOptions.Options.First(x => x.Value == "46").Label);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/EmbeddedResource.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Reflection;
3 |
4 | namespace Altinn.Codelists.Tests;
5 |
6 | public static class EmbeddedResource
7 | {
8 | public static async Task LoadDataAsString(string resourceName)
9 | {
10 | var resourceStream = LoadDataAsStream(resourceName);
11 |
12 | using var reader = new StreamReader(resourceStream);
13 | string text = await reader.ReadToEndAsync();
14 |
15 | return text;
16 | }
17 |
18 | public static Stream LoadDataAsStream(string resourceName)
19 | {
20 | var assembly = Assembly.GetExecutingAssembly();
21 | Stream? resourceStream = assembly.GetManifestResourceStream(resourceName);
22 |
23 | if (resourceStream == null)
24 | {
25 | throw new InvalidOperationException(
26 | $"Unable to find resource {resourceName} embedded in assembly {assembly.FullName}."
27 | );
28 | }
29 |
30 | resourceStream.Seek(0, SeekOrigin.Begin);
31 |
32 | return resourceStream;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/CountriesCodelistProviderTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.SSB;
3 | using Altinn.Codelists.SSB.Clients;
4 | using Altinn.Codelists.SSB.Models;
5 | using Altinn.Codelists.Tests.SSB.Mocks;
6 |
7 | namespace Altinn.Codelists.Tests.SSB;
8 |
9 | public class CountriesStatusCodelistProviderTests
10 | {
11 | [Fact]
12 | public async Task GetAppOptionsAsync_ShouldReturnListOfCodes()
13 | {
14 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings()));
15 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider(
16 | "land",
17 | Classification.Countries,
18 | httpClientMock
19 | );
20 |
21 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary());
22 |
23 | Assert.NotNull(appOptions.Options);
24 | Assert.Equal(252, appOptions.Options.Count);
25 | Assert.Equal("Norge", appOptions.Options.First(x => x.Value == "NOR").Label);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | // Use IntelliSense to find out which attributes exist for C# debugging
6 | // Use hover for the description of the existing attributes
7 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | // If you have changed target frameworks, make sure to update the program path.
13 | "program": "${workspaceFolder}/test/Altinn.Codelists.Tests/bin/Debug/net6.0/Altinn.Codelists.Tests.dll",
14 | "args": [],
15 | "cwd": "${workspaceFolder}/test/Altinn.Codelists.Tests",
16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
17 | "console": "internalConsole",
18 | "stopAtEntry": false
19 | },
20 | {
21 | "name": ".NET Core Attach",
22 | "type": "coreclr",
23 | "request": "attach"
24 | }
25 | ]
26 | }
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/MaritalStatusCodelistProviderTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.SSB;
3 | using Altinn.Codelists.SSB.Clients;
4 | using Altinn.Codelists.SSB.Models;
5 | using Altinn.Codelists.Tests.SSB.Mocks;
6 |
7 | namespace Altinn.Codelists.Tests.SSB;
8 |
9 | public class MaritalStatusCodelistProviderTests
10 | {
11 | [Fact]
12 | public async Task GetAppOptionsAsync_ShouldReturnListOfCodes()
13 | {
14 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings()));
15 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider(
16 | "maritalStatus",
17 | Classification.MaritalStatus,
18 | httpClientMock
19 | );
20 |
21 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary());
22 |
23 | Assert.NotNull(appOptions.Options);
24 | Assert.Equal(9, appOptions.Options.Count);
25 | Assert.Equal("Gift", appOptions.Options.First(x => x.Value == "2").Label);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/MunicipalitiesCodelistProviderTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.SSB;
3 | using Altinn.Codelists.SSB.Clients;
4 | using Altinn.Codelists.SSB.Models;
5 | using Altinn.Codelists.Tests.SSB.Mocks;
6 |
7 | namespace Altinn.Codelists.Tests.SSB;
8 |
9 | public class MunicipalitiesCodelistProviderTests
10 | {
11 | [Fact]
12 | public async Task GetAppOptionsAsync_ShouldReturnListOfCodes()
13 | {
14 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings()));
15 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider(
16 | "kommuner",
17 | Classification.Municipalities,
18 | httpClientMock
19 | );
20 |
21 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary());
22 |
23 | Assert.NotNull(appOptions.Options);
24 | Assert.Equal(357, appOptions.Options.Count);
25 | Assert.Equal("Sogndal", appOptions.Options.First(x => x.Value == "4640").Label);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Altinn
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/Extensions/ExtensionTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.SSB;
3 | using Altinn.Codelists.SSB.Extensions;
4 | using Altinn.Codelists.SSB.Models;
5 | using Microsoft.Extensions.DependencyInjection;
6 |
7 | namespace Altinn.Codelists.Tests.SSB.Extensions;
8 |
9 | public class ExtensionTests
10 | {
11 | [Fact]
12 | public void AddSSBClassifications()
13 | {
14 | IServiceCollection services = new ServiceCollection();
15 | services.AddSSBClassificationCodelistProvider("sivilstand", Classification.MaritalStatus);
16 | services.AddSSBClassificationCodelistProvider("yrker", Classification.Occupations);
17 |
18 | IServiceProvider serviceProvider = services.BuildServiceProvider();
19 |
20 | IEnumerable classificationsClients =
21 | serviceProvider.GetServices();
22 |
23 | Assert.Single(classificationsClients);
24 |
25 | var optionsProviders = serviceProvider.GetServices();
26 | Assert.Equal(2, optionsProviders.Count());
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet-test.yml:
--------------------------------------------------------------------------------
1 | name: Build and Test on windows, macos and ubuntu
2 | on:
3 | push:
4 | branches: [ main ]
5 | pull_request:
6 | branches: [ main ]
7 | types: [opened, synchronize, reopened]
8 | workflow_dispatch:
9 | jobs:
10 | analyze:
11 | strategy:
12 | matrix:
13 | os: [macos-latest,windows-latest,ubuntu-latest] # TODO: Fix test to run on ubuntu-latest also
14 | name: Run dotnet build and test
15 | runs-on: ${{ matrix.os}}
16 | env:
17 | DOTNET_HOSTBUILDER__RELOADCONFIGONCHANGE: false
18 | steps:
19 | - name: Setup .NET
20 | uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5
21 | with:
22 | dotnet-version: |
23 | 8.0.x
24 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
25 | with:
26 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
27 | - name: Build
28 | run: |
29 | dotnet build Altinn.Codelists.sln -v m
30 | - name: Test
31 | run: |
32 | dotnet test Altinn.Codelists.sln -v m
33 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/BaseAmountNationalInsuranceCodelistProviderTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.SSB;
3 | using Altinn.Codelists.SSB.Clients;
4 | using Altinn.Codelists.SSB.Models;
5 | using Altinn.Codelists.Tests.SSB.Mocks;
6 |
7 | namespace Altinn.Codelists.Tests.SSB;
8 |
9 | public class BaseAmountNationalInsuranceCodelistProviderTests
10 | {
11 | [Fact]
12 | public async Task GetAppOptionsAsync_ShouldReturnListOfCodes()
13 | {
14 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings()));
15 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider(
16 | "grunnbeløpfolketrygden",
17 | Classification.BaseAmountNationalInsurance,
18 | httpClientMock
19 | );
20 |
21 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary());
22 |
23 | Assert.NotNull(appOptions.Options);
24 | Assert.Equal(32, appOptions.Options.Count);
25 | Assert.Equal("111 477 kroner", appOptions.Options.First(x => x.Value == "2022").Label);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/Posten/Clients/PostalCodesHttpClientTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.Posten;
2 | using Microsoft.Extensions.DependencyInjection;
3 |
4 | namespace Altinn.Codelists.Tests.Posten.Clients;
5 |
6 | public class PostalCodesHttpClientTests
7 | {
8 | [Fact]
9 | public async Task Get()
10 | {
11 | await using var fixture = await PostenFixture.Create(proxy: false);
12 | var server = fixture.Server;
13 | var baseUrl = server.Url;
14 | Assert.NotNull(baseUrl);
15 | var baseUri = new Uri(baseUrl);
16 | var uri = new Uri(baseUri, PostenSettings.DefaultPath);
17 |
18 | var services = new ServiceCollection();
19 | services.AddPostenClient();
20 | services.Configure(s => s.Url = uri.ToString());
21 | await using var serviceProvider = services.BuildServiceProvider(
22 | new ServiceProviderOptions { ValidateOnBuild = true, ValidateScopes = true }
23 | );
24 |
25 | var client = serviceProvider.GetRequiredService();
26 |
27 | var postalCodes = await client.GetPostalCodes();
28 |
29 | await Verify(postalCodes);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/test/Altinn.Codelists.Tests/Altinn.Codelists.Tests.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/test/Altinn.Codelists.Tests/Altinn.Codelists.Tests.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "--project",
36 | "${workspaceFolder}/test/Altinn.Codelists.Tests/Altinn.Codelists.Tests.csproj"
37 | ],
38 | "problemMatcher": "$msCompile"
39 | }
40 | ]
41 | }
--------------------------------------------------------------------------------
/Directory.Packages.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 | true
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/EnsureNoCodelistIdCollisions.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.Extensions;
3 | using Microsoft.Extensions.DependencyInjection;
4 |
5 | namespace Altinn.Codelists.Tests;
6 |
7 | public class EnsureNoCodelistIdCollisionsTest
8 | {
9 | [Fact]
10 | public void EnsureNoCodelistIdCollision()
11 | {
12 | IServiceCollection services = new ServiceCollection();
13 | services.AddAltinnCodelists();
14 | var serviceProvider = services.BuildServiceProvider();
15 | var appOptionsProviders = serviceProvider.GetServices().ToList();
16 |
17 | Assert.Empty(ValidateIdsAreUnique(appOptionsProviders));
18 | }
19 |
20 | public static string ValidateIdsAreUnique(List objects)
21 | where T : IAppOptionsProvider
22 | {
23 | HashSet seenIds = new();
24 |
25 | foreach (T obj in objects)
26 | {
27 | if (seenIds.Contains(obj.Id))
28 | {
29 | return $"{obj.Id} in {obj.GetType().FullName}";
30 | }
31 | seenIds.Add(obj.Id);
32 | }
33 |
34 | return string.Empty;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/SSB/Models/Classification.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Codelists.SSB.Models;
2 |
3 | ///
4 | /// Supported classifications as defined by SSB, ref. https://data.ssb.no/api/klass/v1/classifications
5 | ///
6 | public enum Classification
7 | {
8 | ///
9 | /// Standard for kjønn
10 | ///
11 | Sex = 2,
12 |
13 | ///
14 | /// Standard for næringsgruppering
15 | ///
16 | IndustryGrouping = 6,
17 |
18 | ///
19 | /// Standard for yrkesklassifisering
20 | ///
21 | Occupations = 7,
22 |
23 | ///
24 | /// Standard for sivilstand
25 | ///
26 | MaritalStatus = 19,
27 |
28 | ///
29 | /// Standard for grunnbeløpet i folketrygden (beløp pr. 1.5 )
30 | ///
31 | BaseAmountNationalInsurance = 20,
32 |
33 | ///
34 | /// Standard for fylker
35 | ///
36 | Counties = 104,
37 |
38 | ///
39 | /// Standard for kommuner
40 | ///
41 | Municipalities = 131,
42 |
43 | ///
44 | /// Standard for landkoder (alfa-3)
45 | ///
46 | Countries = 552,
47 | }
48 |
--------------------------------------------------------------------------------
/.github/workflows/publish-release.yml:
--------------------------------------------------------------------------------
1 | name: Pack and publish nugets
2 |
3 | on:
4 | release:
5 | types:
6 | - published
7 |
8 | jobs:
9 | build-pack:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
13 | with:
14 | fetch-depth: 0
15 |
16 | - name: Install dotnet
17 | uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5
18 | with:
19 | dotnet-version: |
20 | 8.0.x
21 | - name: Install deps
22 | run: |
23 | dotnet restore
24 | - name: Build
25 | run: |
26 | dotnet build --configuration Release --no-restore -p:Deterministic=true -p:BuildNumber=${{ github.run_number }}
27 | - name: Pack
28 | run: |
29 | dotnet pack Altinn.Codelists.sln --configuration Release --no-restore --no-build -p:BuildNumber=${{ github.run_number }} -p:Deterministic=true
30 | - name: Versions
31 | run: |
32 | dotnet --version
33 | - name: Publish
34 | run: |
35 | dotnet nuget push src/**/bin/Release/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}
36 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 | NU1901;NU1902;NU1903;NU1904
5 |
6 |
7 |
8 | latest
9 | true
10 | Recommended
11 | strict
12 | true
13 | all
14 |
15 |
16 |
17 |
18 | all
19 | runtime; build; native; contentfiles; analyzers
20 |
21 |
22 | all
23 | runtime; build; native; contentfiles; analyzers
24 |
25 |
26 | all
27 | runtime; build; native; contentfiles; analyzers
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Posten/PostalCodesCodelistsProvider.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.App.Core.Models;
3 | using Altinn.Codelists.Posten.Clients;
4 |
5 | namespace Altinn.Codelists.Posten;
6 |
7 | ///
8 | /// Post codes and corresponding names for Norway.
9 | ///
10 | internal sealed class PostalCodesCodelistsProvider : IAppOptionsProvider
11 | {
12 | private readonly IPostalCodesClient _postalCodesClient;
13 |
14 | ///
15 | /// Initializes a new instance of the class.
16 | ///
17 | public PostalCodesCodelistsProvider(IPostalCodesClient postalCodesClient)
18 | {
19 | _postalCodesClient = postalCodesClient;
20 | }
21 |
22 | ///
23 | public string Id => "poststed";
24 |
25 | ///
26 | public async Task GetAppOptionsAsync(string? language, Dictionary keyValuePairs)
27 | {
28 | List postalCodes = await _postalCodesClient.GetPostalCodes();
29 |
30 | var appOptions = new AppOptions
31 | {
32 | Options = postalCodes.Select(x => new AppOption() { Value = x.PostCode, Label = x.PostalName }).ToList(),
33 | };
34 |
35 | return appOptions;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Posten/Clients/PostalCodesCsvParser.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Codelists.Posten.Clients;
2 |
3 | ///
4 | /// Class for parsing the offical postal codes file in Norway.
5 | /// Record description: https://www.bring.no/tjenester/adressetjenester/postnummer/postnummertabeller-veiledning
6 | /// File used: https://www.bring.no/postnummerregister-ansi.txt
7 | /// Examample record: 6863 LEIKANGER 4640 SOGNDAL G
8 | ///
9 | internal sealed class PostalCodesCsvParser(Stream _csvStream)
10 | {
11 | ///
12 | /// Parses the stream provided in the constructor.
13 | ///
14 | public async Task> Parse()
15 | {
16 | List postalCodes = new();
17 |
18 | using StreamReader reader = new StreamReader(_csvStream, Encoding.Latin1, leaveOpen: true);
19 |
20 | while (!reader.EndOfStream)
21 | {
22 | string? line = await reader.ReadLineAsync();
23 |
24 | if (line != null)
25 | {
26 | string[] columns = line.Split('\t');
27 | PostalCodeRecord postalCode = new(columns[0], columns[1], columns[2], columns[3], columns[4]);
28 | postalCodes.Add(postalCode);
29 | }
30 | }
31 |
32 | return postalCodes;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Posten/Extensions/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.Posten.Clients;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using Microsoft.Extensions.DependencyInjection.Extensions;
5 |
6 | namespace Altinn.Codelists.Posten;
7 |
8 | ///
9 | /// Extends the .
10 | ///
11 | public static class ServiceCollectionExtensions
12 | {
13 | ///
14 | /// Registers the services required to get postal codes through and
15 | ///
16 | public static IServiceCollection AddPosten(this IServiceCollection services)
17 | {
18 | services.AddPostenClient();
19 | services.AddTransient();
20 |
21 | return services;
22 | }
23 |
24 | ///
25 | /// Registers
26 | ///
27 | ///
28 | public static IServiceCollection AddPostenClient(this IServiceCollection services)
29 | {
30 | services.AddHttpClient();
31 | services.AddOptions();
32 | services.TryAddTransient();
33 |
34 | return services;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/UnitsCodelistProviderTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.SSB;
3 | using Altinn.Codelists.SSB.Clients;
4 | using Altinn.Codelists.Tests.SSB.Mocks;
5 |
6 | namespace Altinn.Codelists.Tests.SSB;
7 |
8 | public class UnitsCodelistProviderTests
9 | {
10 | [Fact]
11 | public async Task GetAppOptionsAsync_SortedBySelectCodes_ShouldReturnListOfCodes()
12 | {
13 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings()));
14 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider(
15 | "målenheter",
16 | 303,
17 | httpClientMock,
18 | new Dictionary()
19 | {
20 | { "selectCodes", "13.03,13.04,13.02,15.06,03.01,09.03,02.02" },
21 | { "orderBy", "selectCodes" },
22 | },
23 | new ClassificationOptions { MapDescriptionFunc = (classificationCode) => classificationCode.ShortName }
24 | );
25 |
26 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary());
27 |
28 | Assert.NotNull(appOptions.Options);
29 | Assert.Equal(7, appOptions.Options.Count);
30 | Assert.Equal("kilogram", appOptions.Options[0].Label);
31 | Assert.Equal("stykk", appOptions.Options[^1].Label);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/SmallGameVariantTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.SSB;
3 | using Altinn.Codelists.SSB.Clients;
4 | using Altinn.Codelists.Tests.SSB.Mocks;
5 |
6 | namespace Altinn.Codelists.Tests.SSB;
7 |
8 | public class SmallGameVariantTests
9 | {
10 | [Fact]
11 | public async Task GetAppOptionsAsync_ShouldReturnListOfCodes()
12 | {
13 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings()));
14 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider(
15 | "småvilt",
16 | 74,
17 | httpClientMock,
18 | new Dictionary
19 | {
20 | {
21 | "variant",
22 | "Hønsefugler, spurvefugler, skarver og due 2023-03 - variant av Klassifisering av småvilt 2017-04"
23 | },
24 | },
25 | new ClassificationOptions { MapDescriptionFunc = (classificationCode) => classificationCode.Name }
26 | );
27 |
28 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary());
29 |
30 | Assert.NotNull(appOptions.Options);
31 | Assert.Equal(11, appOptions.Options.Count);
32 | Assert.Equal("Ravn", appOptions.Options.First(x => x.Value == "06").Label);
33 | Assert.Equal("Ravn", appOptions.Options.First(x => x.Value == "06").Description);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/Clients/ClassificationsHttpClientTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.SSB.Clients;
2 |
3 | namespace Altinn.Codelists.Tests.SSB.Clients;
4 |
5 | public class ClassificationsHttpClientTests
6 | {
7 | //[Fact(Skip = "Disabled. This actually calls out to the api and is primarily used to test during development.")]
8 | public async Task GetClassificationCodes_ShouldReturnAllClassificationCodes()
9 | {
10 | var options = Options.Create(new ClassificationSettings());
11 | var client = new ClassificationsHttpClient(options, new HttpClient());
12 |
13 | var classificationCodes = await client.GetClassificationCodes(19, "nn", DateOnly.FromDateTime(DateTime.Today));
14 |
15 | Assert.True(classificationCodes.Codes.Count > 2);
16 | }
17 |
18 | //[Fact(Skip = "Disabled. This actually calls out to the api and is primarily used to test during development.")]
19 | public async Task GetClassificationVariant_ShouldReturnAllClassificationCodeVariant()
20 | {
21 | var options = Options.Create(new ClassificationSettings());
22 | var client = new ClassificationsHttpClient(options, new HttpClient());
23 |
24 | var classificationCodes = await client.GetClassificationCodes(
25 | 74,
26 | "nn",
27 | new DateOnly(2023, 03, 01),
28 | "",
29 | "Hønsefugler, spurvefugler og due 2023-03 - variant av Klassifisering av småvilt 2017-04"
30 | );
31 |
32 | Assert.True(classificationCodes.Codes.Count > 2);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Kartverket/AdministrativeUnits/MunicipalitiesCodelistProvider.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.App.Core.Models;
3 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Models;
4 |
5 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits;
6 |
7 | ///
8 | /// Provides a codelist for municipalities of Norway.
9 | ///
10 | internal sealed class MunicipalitiesCodelistProvider(IAdministrativeUnitsClient _administrativeUnitsHttpClient)
11 | : IAppOptionsProvider
12 | {
13 | ///
14 | public string Id => "kommuner-kv";
15 |
16 | ///
17 | public async Task GetAppOptionsAsync(string? language, Dictionary keyValuePairs)
18 | {
19 | bool hasCountyParam = keyValuePairs.TryGetValue("fnr", out string? countyNumber);
20 |
21 | List municipalities =
22 | hasCountyParam && countyNumber != null
23 | ? await _administrativeUnitsHttpClient.GetMunicipalities(countyNumber)
24 | : await _administrativeUnitsHttpClient.GetMunicipalities();
25 |
26 | var appOptions = new AppOptions()
27 | {
28 | Options = municipalities.Select(x => new AppOption() { Value = x.Number, Label = x.Name }).ToList(),
29 | Parameters =
30 | hasCountyParam && countyNumber != null
31 | ? new Dictionary() { { "fnr", countyNumber } }
32 | : new Dictionary(),
33 | };
34 |
35 | return appOptions;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Altinn.Codelists.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | Altinn;Studio;App;Core
6 |
7 | This class library holds extra codelists for Altinn Studio apps.
8 |
9 |
10 | direct
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | <_Parameter1>PackageReference.%(PackageReference.Identity)
28 | <_Parameter2 Condition="'%(PackageReference.VersionOverride)' != ''">%(PackageReference.VersionOverride)
29 | <_Parameter2 Condition="'%(PackageReference.VersionOverride)' == ''">%(PackageReference.Version)
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Utilities/PredicateBuilder.cs:
--------------------------------------------------------------------------------
1 | using System.Linq.Expressions;
2 |
3 | namespace Altinn.Codelists.Utilities;
4 |
5 | ///
6 | /// Simple predicate builder
7 | ///
8 | internal static class PredicateBuilder
9 | {
10 | ///
11 | /// Creates a predicate that evaluates to true
12 | ///
13 | public static Expression> True()
14 | {
15 | return f => true;
16 | }
17 |
18 | ///
19 | /// Creates a predicate that evaluates to false
20 | ///
21 | public static Expression> False()
22 | {
23 | return f => false;
24 | }
25 |
26 | ///
27 | /// Combines the first predicate with the second using the logical "or".
28 | ///
29 | public static Expression> Or(this Expression> expr1, Expression> expr2)
30 | {
31 | var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast());
32 | return Expression.Lambda>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
33 | }
34 |
35 | ///
36 | /// Combines the first predicate with the second using the logical "and".
37 | ///
38 | public static Expression> And(
39 | this Expression> expr1,
40 | Expression> expr2
41 | )
42 | {
43 | var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast());
44 | return Expression.Lambda>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Posten/Clients/PostalCodeRecord.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Codelists.Posten.Clients;
2 |
3 | ///
4 | /// Information about a postal code ie. a record representing one line in the postal codes csv file.
5 | ///
6 | public record PostalCodeRecord
7 | {
8 | ///
9 | /// Creates an instance of
10 | ///
11 | public PostalCodeRecord(
12 | string postCode,
13 | string postalName,
14 | string municipalityNumber,
15 | string municipalityName,
16 | string category
17 | )
18 | {
19 | PostCode = postCode;
20 | PostalName = postalName;
21 | MunicipalityNumber = municipalityNumber;
22 | MunicipalityName = municipalityName;
23 | Category = category;
24 | }
25 |
26 | ///
27 | /// Post code
28 | ///
29 | public string PostCode { get; init; }
30 |
31 | ///
32 | /// Postal name
33 | ///
34 | public string PostalName { get; init; }
35 |
36 | ///
37 | /// Municipality number
38 | ///
39 | public string MunicipalityNumber { get; init; }
40 |
41 | ///
42 | /// Municipality name
43 | ///
44 | public string MunicipalityName { get; init; }
45 |
46 | ///
47 | /// Category.
48 | /// B = Both streeet addresses and P.O.Boxes
49 | /// F = Multiple areas of uses(Common)
50 | /// G = Street addresses
51 | /// P = P.O.Boxes
52 | /// S = Postcode for special service(not used for addresses)
53 | ///
54 | public string Category { get; init; }
55 | }
56 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/SexCodelistProviderTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.SSB;
3 | using Altinn.Codelists.SSB.Clients;
4 | using Altinn.Codelists.SSB.Models;
5 | using Altinn.Codelists.Tests.SSB.Mocks;
6 |
7 | namespace Altinn.Codelists.Tests.SSB;
8 |
9 | public class SexCodelistProviderTests
10 | {
11 | [Fact]
12 | public async Task GetAppOptionsAsync_EnumProvided_ShouldReturnListOfCodes()
13 | {
14 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings()));
15 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider(
16 | "sex",
17 | Classification.Sex,
18 | httpClientMock
19 | );
20 |
21 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary());
22 |
23 | Assert.NotNull(appOptions.Options);
24 | Assert.Equal(2, appOptions.Options.Count);
25 | Assert.Equal("Kvinne", appOptions.Options.First(x => x.Value == "2").Label);
26 | }
27 |
28 | [Fact]
29 | public async Task GetAppOptionsAsync_IdProvided_ShouldReturnListOfCodes()
30 | {
31 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings()));
32 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider("sex", 2, httpClientMock);
33 |
34 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary());
35 |
36 | Assert.NotNull(appOptions.Options);
37 | Assert.Equal(2, appOptions.Options.Count);
38 | Assert.Equal("Mann", appOptions.Options.First(x => x.Value == "1").Label);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/Posten/PostalCodesCodelistProviderTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.Posten;
3 | using Altinn.Codelists.Tests.Posten.Clients;
4 | using Microsoft.Extensions.DependencyInjection;
5 |
6 | namespace Altinn.Codelists.Tests.Posten;
7 |
8 | public class PostalCodesCodelistProviderTests
9 | {
10 | [Fact]
11 | public async Task Get()
12 | {
13 | await using var fixture = await PostenFixture.Create(proxy: false);
14 | var server = fixture.Server;
15 | var baseUrl = server.Url;
16 | Assert.NotNull(baseUrl);
17 | var baseUri = new Uri(baseUrl);
18 | var uri = new Uri(baseUri, PostenSettings.DefaultPath);
19 |
20 | var services = new ServiceCollection();
21 | services.AddPosten();
22 | services.Configure(s => s.Url = uri.ToString());
23 | await using var serviceProvider = services.BuildServiceProvider(
24 | new ServiceProviderOptions { ValidateOnBuild = true, ValidateScopes = true }
25 | );
26 |
27 | var client = serviceProvider.GetRequiredService();
28 | var optionsProvider = serviceProvider.GetRequiredService();
29 |
30 | var postalCodes = await client.GetPostalCodes();
31 | var options = await optionsProvider.GetAppOptionsAsync(null, []);
32 |
33 | var clientCodes = postalCodes.Select(p => p.PostCode).ToHashSet();
34 | Assert.NotNull(options.Options);
35 | var optionsCodes = options
36 | .Options.Select(o =>
37 | {
38 | Assert.NotNull(o.Value);
39 | return o.Value;
40 | })
41 | .ToHashSet();
42 |
43 | Assert.True(clientCodes.SetEquals(optionsCodes));
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Utilities/EmbeddedResource.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace Altinn.Codelists.Utilities;
4 |
5 | ///
6 | /// Helper class for embeded resources.
7 | ///
8 | internal static class EmbeddedResource
9 | {
10 | ///
11 | /// Finds an embeded resource, by name, within the executing assembly and reads it as string.
12 | ///
13 | ///
14 | ///
15 | public async static Task LoadDataAsString(string resourceName)
16 | {
17 | var resourceStream = LoadDataAsStream(resourceName);
18 |
19 | using var reader = new StreamReader(resourceStream);
20 | string text = await reader.ReadToEndAsync();
21 |
22 | return text;
23 | }
24 |
25 | ///
26 | /// Finds an embeded resource, by name, within the executing assembly and reads it as a
27 | ///
28 | /// The name of the resource including namespace.
29 | ///
30 | public static Stream LoadDataAsStream(string resourceName)
31 | {
32 | var assembly = Assembly.GetExecutingAssembly();
33 | Stream? resourceStream = assembly.GetManifestResourceStream(resourceName);
34 | try
35 | {
36 | if (resourceStream == null)
37 | {
38 | throw new InvalidOperationException(
39 | $"Unable to find resource {resourceName} embedded in assembly {assembly.FullName}."
40 | );
41 | }
42 |
43 | resourceStream.Seek(0, SeekOrigin.Begin);
44 |
45 | return resourceStream;
46 | }
47 | catch (Exception)
48 | {
49 | resourceStream?.Dispose();
50 | throw;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/Clients/AdministrativeUnitsClientTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.Kartverket.AdministrativeUnits;
2 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Clients;
3 |
4 | namespace Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Clients;
5 |
6 | public class AdministrativeUnitsClientTests
7 | {
8 | [Fact(Skip = "Disabled. This actually calls out to the api and is primarily used to test during development.")]
9 | public async Task GetCounties_NothingSpecified_ShouldReturnAllCounties()
10 | {
11 | var client = new AdministrativeUnitsHttpClient(
12 | Options.Create(new AdministrativeUnitsSettings()),
13 | new HttpClient()
14 | );
15 |
16 | var counties = await client.GetCounties();
17 |
18 | Assert.True(counties.Count > 2);
19 | }
20 |
21 | [Fact(Skip = "Disabled. This actually calls out to the api and is primarily used to test during development.")]
22 | public async Task GetMunicipalities_NothingSpecified_ShouldReturnAllMunicipalities()
23 | {
24 | var client = new AdministrativeUnitsHttpClient(
25 | Options.Create(new AdministrativeUnitsSettings()),
26 | new HttpClient()
27 | );
28 |
29 | var municipalities = await client.GetMunicipalities();
30 |
31 | Assert.True(municipalities.Count > 2);
32 | }
33 |
34 | [Fact(Skip = "Disabled. This actually calls out to the api and is primarily used to test during development.")]
35 | public async Task GetMunicipalities_CountySpecified_ShouldReturnMunicipalitiesWithinCounty()
36 | {
37 | var client = new AdministrativeUnitsHttpClient(
38 | Options.Create(new AdministrativeUnitsSettings()),
39 | new HttpClient()
40 | );
41 |
42 | var municipalities = await client.GetMunicipalities("46");
43 |
44 | Assert.True(municipalities.Count > 2);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Kartverket/AdministrativeUnits/Extensions/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Clients;
3 | using Microsoft.Extensions.Caching.Memory;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.DependencyInjection.Extensions;
6 |
7 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits.Extensions;
8 |
9 | ///
10 | /// Extends the .
11 | ///
12 | public static class ServiceCollectionExtensions
13 | {
14 | ///
15 | /// Registers the services required to get support for Kartverkets counties (fylker) and municipalities (kommuner) codelists.
16 | ///
17 | public static IServiceCollection AddKartverketAdministrativeUnits(this IServiceCollection services)
18 | {
19 | services.AddKartverketAdministrativeUnitsClient();
20 | services.AddTransient();
21 | services.AddTransient();
22 |
23 | return services;
24 | }
25 |
26 | ///
27 | /// Registers the client services required to get support for Kartverkets counties (fylker) and municipalities (kommuner) codelists.
28 | ///
29 | ///
30 | ///
31 | public static IServiceCollection AddKartverketAdministrativeUnitsClient(this IServiceCollection services)
32 | {
33 | services.AddMemoryCache();
34 | services.AddOptions();
35 | services.AddHttpClient();
36 | services.TryAddTransient(sp => new AdministrativeUnitsHttpClientCached(
37 | ActivatorUtilities.CreateInstance(sp),
38 | sp.GetRequiredService()
39 | ));
40 |
41 | return services;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Altinn.Codelists.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.1.32228.430
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Altinn.Codelists.Tests", "test\Altinn.Codelists.Tests\Altinn.Codelists.Tests.csproj", "{659F246C-3286-45EA-BD4C-84822EE6C841}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Altinn.Codelists", "src\Altinn.Codelists\Altinn.Codelists.csproj", "{9EBD611F-86DA-4D9A-93F2-5DFDBAAA3490}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6E1FAB62-D893-46C4-8510-306E62E9CDCD}"
11 | ProjectSection(SolutionItems) = preProject
12 | .editorconfig = .editorconfig
13 | EndProjectSection
14 | EndProject
15 | Global
16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
17 | Debug|Any CPU = Debug|Any CPU
18 | Release|Any CPU = Release|Any CPU
19 | EndGlobalSection
20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
21 | {659F246C-3286-45EA-BD4C-84822EE6C841}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 | {659F246C-3286-45EA-BD4C-84822EE6C841}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 | {659F246C-3286-45EA-BD4C-84822EE6C841}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {659F246C-3286-45EA-BD4C-84822EE6C841}.Release|Any CPU.Build.0 = Release|Any CPU
25 | {9EBD611F-86DA-4D9A-93F2-5DFDBAAA3490}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {9EBD611F-86DA-4D9A-93F2-5DFDBAAA3490}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {9EBD611F-86DA-4D9A-93F2-5DFDBAAA3490}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {9EBD611F-86DA-4D9A-93F2-5DFDBAAA3490}.Release|Any CPU.Build.0 = Release|Any CPU
29 | EndGlobalSection
30 | GlobalSection(SolutionProperties) = preSolution
31 | HideSolutionNode = FALSE
32 | EndGlobalSection
33 | GlobalSection(ExtensibilityGlobals) = postSolution
34 | SolutionGuid = {1736ED5E-5025-4F87-9881-48A863054B17}
35 | EndGlobalSection
36 | EndGlobal
37 |
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | @(ReleaseNoteLines, '%0a')
17 |
18 |
19 |
20 |
21 | Recommended
22 | enable
23 | enable
24 | true
25 | $([System.IO.Directory]::GetParent($(MSBuildThisFileDirectory)).Parent.FullName)
26 | preview.0
27 | v
28 | true
29 |
30 |
31 |
32 |
33 | $(MinVerMajor).$(MinVerMinor).$(MinVerPatch).$(BuildNumber)
34 | $(MinVerMajor).$(MinVerMinor).$(MinVerPatch).$(BuildNumber)
35 | $(MinVerMajor).$(MinVerMinor).$(MinVerPatch).$(BuildNumber)
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | true
45 |
46 |
47 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/MunicipalitiesCodelistProviderTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.Kartverket.AdministrativeUnits;
3 | using Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Mocks;
4 |
5 | namespace Altinn.Codelists.Tests.Kartverket.AdministrativeUnits;
6 |
7 | public class MunicipalitiesCodelistProviderTests
8 | {
9 | [Fact]
10 | public async Task GetAppOptionsAsync_NoCountySpecified_ShouldReturnListOfAllMunicipalities()
11 | {
12 | var administrativeUnitsHttpClientMock = new AdministrativeUnitsHttpClientMock(
13 | Options.Create(new AdministrativeUnitsSettings())
14 | );
15 | IAppOptionsProvider appOptionsProvider = new MunicipalitiesCodelistProvider(administrativeUnitsHttpClientMock);
16 |
17 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary());
18 |
19 | Assert.NotNull(appOptions.Options);
20 | Assert.Equal(356, appOptions.Options.Count);
21 | Assert.Equal("Sogndal", appOptions.Options.First(x => x.Value == "4640").Label);
22 | Assert.Equal("Brønnøy", appOptions.Options.First(x => x.Value == "1813").Label);
23 | }
24 |
25 | [Fact]
26 | public async Task GetAppOptionsAsync_CountySpecified_ShouldReturnListOfMunicipalitiesByCounty()
27 | {
28 | var administrativeUnitsHttpClientMock = new AdministrativeUnitsHttpClientMock(
29 | Options.Create(new AdministrativeUnitsSettings())
30 | );
31 | IAppOptionsProvider appOptionsProvider = new MunicipalitiesCodelistProvider(administrativeUnitsHttpClientMock);
32 |
33 | var appOptions = await appOptionsProvider.GetAppOptionsAsync(
34 | "nb",
35 | new Dictionary() { { "fnr", "46" } }
36 | );
37 |
38 | Assert.NotNull(appOptions.Options);
39 | Assert.Equal(43, appOptions.Options.Count);
40 | Assert.Equal("Sogndal", appOptions.Options.First(x => x.Value == "4640").Label);
41 | Assert.Null(appOptions.Options.FirstOrDefault(x => x.Value == "1813"));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/Testdata/units.json:
--------------------------------------------------------------------------------
1 | {
2 | "codes": [
3 | {
4 | "code": "02.02",
5 | "parentCode": "02",
6 | "level": "2",
7 | "name": "stykk",
8 | "shortName": "stk",
9 | "presentationName": "",
10 | "validFrom": null,
11 | "validTo": null,
12 | "notes": "Passer for statistikk der en teller ting, f.eks. så og så mange stykk av en viss varetype"
13 | },
14 | {
15 | "code": "03.01",
16 | "parentCode": "03",
17 | "level": "2",
18 | "name": "kvadratmeter",
19 | "shortName": "m²",
20 | "presentationName": "",
21 | "validFrom": null,
22 | "validTo": null,
23 | "notes": "En kvadratmeter er et areal på en ganger en meter."
24 | },
25 | {
26 | "code": "09.03",
27 | "parentCode": "09",
28 | "level": "2",
29 | "name": "meter",
30 | "shortName": "m",
31 | "presentationName": "",
32 | "validFrom": null,
33 | "validTo": null,
34 | "notes": "En meter er grunnenheten for lengde i SI-systemet."
35 | },
36 | {
37 | "code": "13.02",
38 | "parentCode": "13",
39 | "level": "2",
40 | "name": "gram",
41 | "shortName": "g",
42 | "presentationName": "",
43 | "validFrom": null,
44 | "validTo": null,
45 | "notes": "1 g= 1/1 000 kg."
46 | },
47 | {
48 | "code": "13.03",
49 | "parentCode": "13",
50 | "level": "2",
51 | "name": "kilogram",
52 | "shortName": "kg",
53 | "presentationName": "",
54 | "validFrom": null,
55 | "validTo": null,
56 | "notes": "Kilogram er grunnenheten for masse i SI-systemet."
57 | },
58 | {
59 | "code": "13.04",
60 | "parentCode": "13",
61 | "level": "2",
62 | "name": "tonn",
63 | "shortName": "t",
64 | "presentationName": "",
65 | "validFrom": null,
66 | "validTo": null,
67 | "notes": "1 t=1 000 kg."
68 | },
69 | {
70 | "code": "15.06",
71 | "parentCode": "15",
72 | "level": "2",
73 | "name": "kubikkmeter",
74 | "shortName": "m³",
75 | "presentationName": "",
76 | "validFrom": null,
77 | "validTo": null,
78 | "notes": "En kubikkmeter tilsvarer volumet i en kube der alle sidene er en meter."
79 | }
80 | ]
81 | }
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/Testdata/classifications.http:
--------------------------------------------------------------------------------
1 | ###
2 | # Oversikt: https://www.ssb.no/klass/
3 | GET https://data.ssb.no/api/klass/v1/classifications?size=150&language=en
4 |
5 | ###
6 | # Søk etter klassifikasjon
7 | GET https://data.ssb.no/api/klass/v1/classifications/search?query=land
8 |
9 | ###
10 | # Land (ISO alfa 3)
11 | GET https://data.ssb.no/api/klass/v1/classifications/552/codesAt?date=2023-03-03
12 | Accept: application/json;charset=utf-8
13 |
14 | ###
15 | # Fylker
16 | GET https://data.ssb.no/api/klass/v1/classifications/104/codes?from=2022-07-10
17 | Accept: application/json;charset=utf-8
18 |
19 | ###
20 | # Kommuner
21 | GET https://data.ssb.no/api/klass/v1/classifications/131/codesAt?date=2023-03-02
22 | Accept: application/json;charset=utf-8
23 |
24 | ###
25 | # Kjønn
26 | GET https://data.ssb.no/api/klass/v1/classifications/2/codes?from=2022-07-10&selectLevel=1
27 | Accept: application/json;charset=utf-8
28 |
29 | ###
30 | # Næringsgruppering
31 | GET https://data.ssb.no/api/klass/v1/classifications/6/codesAt?date=2023-02-11&language=en&selectLevel=1
32 | Accept: application/json;charset=utf-8
33 |
34 | ###
35 | GET https://data.ssb.no/api/klass/v1/classifications/6/codesAt?date=2023-02-11&language=nb
36 | Accept: application/json;charset=utf-8
37 |
38 | ###
39 | # Yrkesklassifisering
40 | GET https://data.ssb.no/api/klass/v1/classifications/7/codesAt?date=2023-02-11&language=nb
41 | Accept: application/json;charset=utf-8
42 |
43 | ###
44 | # Sivilstand
45 | GET https://data.ssb.no/api/klass/v1/classifications/19/codesAt?date=2023-02-11&language=nn
46 | Accept: application/json;charset=utf-8
47 |
48 | ###
49 | # Grunnbeløp i folketrygden
50 | GET https://data.ssb.no/api/klass/v1/classifications/20/codesAt?date=2023-03-12
51 | Accept: application/json;charset=utf-8
52 |
53 | ###
54 | # Småvilt med variant
55 | GET https://data.ssb.no/api/klass/v1/classifications/74/codesAt?date=2023-03-12&language=nn
56 | Accept: application/json;charset=utf-8
57 |
58 | ###
59 | GET https://data.ssb.no/api/klass/v1/classifications/74/variantAt?language=nn&date=2023-03-03&variantName=Hønsefugler, spurvefugler og due 2023-03 - variant av Klassifisering av småvilt 2017-04
60 | Accept: application/json;charset=utf-8
61 |
62 | ###
63 |
64 | GET https://data.ssb.no/api/klass/v1/variants/2103?language=nb
65 | Accept: application/json;charset=utf-8
66 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/RestCountries/Clients/CountriesClient.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.RestCountries.Data;
2 | using Altinn.Codelists.RestCountries.Models;
3 | using Altinn.Codelists.Utilities;
4 |
5 | namespace Altinn.Codelists.RestCountries.Clients;
6 |
7 | ///
8 | /// Client to get information on all countries of the world.
9 | /// Note that this is not an http client but uses a static json embedded within
10 | /// this dll to resolve the the list of countries.
11 | ///
12 | internal sealed class CountriesClient : ICountryClient
13 | {
14 | ///
15 | /// Sends a asynchronus internal request to get all the countries of the world.
16 | ///
17 | public async Task> GetCountries()
18 | {
19 | var filters = new List();
20 |
21 | return await GetCountries(filters);
22 | }
23 |
24 | ///
25 | /// Sends a asynchronus internal request to get all countries of the world,
26 | /// matching the specified filters.
27 | /// Values within the same filter object are AND'ed,
28 | /// while values between filter objects are OR'ed.
29 | ///
30 | public async Task> GetCountries(IEnumerable filters)
31 | {
32 | string json = await EmbeddedResource.LoadDataAsString(Resources.CountriesJson);
33 | var countries = JsonSerializer.Deserialize>(json) ?? new List();
34 |
35 | IQueryable query = BuildQuery(countries, filters);
36 |
37 | return query.ToList();
38 | }
39 |
40 | private static IQueryable BuildQuery(IEnumerable countries, IEnumerable filters)
41 | {
42 | var query = countries.AsQueryable();
43 |
44 | var predicate = filters.Any() ? PredicateBuilder.False() : PredicateBuilder.True();
45 |
46 | foreach (var filter in filters)
47 | {
48 | var subPredicate = PredicateBuilder.True();
49 | if (!string.IsNullOrEmpty(filter.Region))
50 | {
51 | subPredicate = subPredicate.And(c => c.Region.Equals(filter.Region, StringComparison.Ordinal));
52 | }
53 |
54 | if (!string.IsNullOrEmpty(filter.SubRegion))
55 | {
56 | subPredicate = subPredicate.And(c => c.SubRegion.Equals(filter.SubRegion, StringComparison.Ordinal));
57 | }
58 |
59 | predicate = predicate.Or(subPredicate);
60 | }
61 |
62 | return query.Where(predicate);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | # The branches below must be a subset of the branches above
8 | branches: [ "main" ]
9 | schedule:
10 | - cron: '37 20 * * 3'
11 |
12 | jobs:
13 | analyze:
14 | name: Analyze
15 | runs-on: ubuntu-latest
16 | permissions:
17 | actions: read
18 | contents: read
19 | security-events: write
20 |
21 | strategy:
22 | fail-fast: false
23 | matrix:
24 | language: [ 'csharp' ]
25 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
26 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
27 |
28 | steps:
29 | - name: Checkout repository
30 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
31 |
32 | # Initializes the CodeQL tools for scanning.
33 | - name: Initialize CodeQL
34 | uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4
35 | with:
36 | languages: ${{ matrix.language }}
37 | queries: security-extended,security-and-quality
38 | # If you wish to specify custom queries, you can do so here or in a config file.
39 | # By default, queries listed here will override any specified in a config file.
40 | # Prefix the list here with "+" to use these queries and those in the config file.
41 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
42 |
43 |
44 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
45 | # If this step fails, then you should remove it and run the build manually (see below)
46 | - name: Autobuild
47 | uses: github/codeql-action/autobuild@0499de31b99561a6d14a36a5f662c2a54f91beee # v4
48 |
49 | # ℹ️ Command-line programs to run using the OS shell.
50 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
51 |
52 | # If the Autobuild fails above, remove it and uncomment the following three lines.
53 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
54 |
55 | # - run: |
56 | # echo "Run, Build Application using script"
57 | # ./location_of_script_within_repo/buildscript.sh
58 |
59 | - name: Perform CodeQL Analysis
60 | uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4
61 | with:
62 | category: "/language:${{matrix.language}}"
63 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/RestCountries/Clients/CountriesClientTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.RestCountries;
2 | using Altinn.Codelists.RestCountries.Models;
3 | using Microsoft.Extensions.DependencyInjection;
4 |
5 | namespace Altinn.Codelists.Tests.RestCountries.Clients;
6 |
7 | public class CountriesClientTests
8 | {
9 | private readonly ITestOutputHelper _output;
10 |
11 | public CountriesClientTests(ITestOutputHelper outputHelper)
12 | {
13 | _output = outputHelper;
14 | }
15 |
16 | private sealed record Fixture(IServiceProvider ServiceProvider, ICountryClient Client) : IAsyncDisposable
17 | {
18 | public async ValueTask DisposeAsync()
19 | {
20 | if (ServiceProvider is IAsyncDisposable disposable)
21 | await disposable.DisposeAsync();
22 | }
23 |
24 | public static Fixture Create()
25 | {
26 | var services = new ServiceCollection();
27 | services.AddRestCountriesClient();
28 |
29 | var serviceProvider = services.BuildServiceProvider(
30 | new ServiceProviderOptions { ValidateOnBuild = true, ValidateScopes = true }
31 | );
32 |
33 | var client = serviceProvider.GetRequiredService();
34 | return new(serviceProvider, client);
35 | }
36 | }
37 |
38 | [Fact]
39 | public async Task GetCountries_NoFilter_ShouldReturnAll()
40 | {
41 | await using var fixture = Fixture.Create();
42 | var client = fixture.Client;
43 |
44 | var countries = await client.GetCountries();
45 |
46 | Assert.Equal(250, countries.Count);
47 | }
48 |
49 | [Fact]
50 | public async Task GetCountries_FilterOnRegion_ShouldReturnOnlyInRegion()
51 | {
52 | await using var fixture = Fixture.Create();
53 | var client = fixture.Client;
54 |
55 | var countries = await client.GetCountries(new List() { new Filter() { Region = "Europe" } });
56 |
57 | Assert.Equal(53, countries.Count);
58 | }
59 |
60 | [Fact]
61 | public async Task GetCountries_FilterOnMultipleRegions_ShouldReturnOnlyInRegions()
62 | {
63 | await using var fixture = Fixture.Create();
64 | var client = fixture.Client;
65 |
66 | var countries = await client.GetCountries(
67 | new List()
68 | {
69 | new Filter() { Region = "Europe" },
70 | new Filter() { Region = "Asia", SubRegion = "Eastern Asia" },
71 | }
72 | );
73 |
74 | countries.ForEach(c =>
75 | {
76 | _output.WriteLine(c.Name.Common);
77 | });
78 | Assert.Equal(61, countries.Count);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/.github/workflows/test-and-analyze-fork.yml:
--------------------------------------------------------------------------------
1 | name: Code test and analysis (fork)
2 | on:
3 | pull_request:
4 | branches: [ main ]
5 | types: [opened, synchronize, reopened, ready_for_review]
6 | jobs:
7 | test:
8 | if: github.repository_owner == 'Altinn' && (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true)
9 | name: Build and Test
10 | runs-on: windows-latest
11 | steps:
12 | - name: Setup .NET
13 | uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5
14 | with:
15 | dotnet-version: |
16 | 8.0.x
17 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
18 | with:
19 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
20 |
21 | - name: dotnet build
22 | run: dotnet build Altinn.Codelists.sln -v m
23 |
24 | - name: dotnet test
25 | run: dotnet test Altinn.Codelists.sln --results-directory TestResults/ --collect:"XPlat Code Coverage" -v m
26 |
27 | - name: Generate coverage results
28 | run: |
29 | dotnet tool install --global dotnet-reportgenerator-globaltool
30 | reportgenerator -reports:TestResults/**/coverage.cobertura.xml -targetdir:TestResults/Output/CoverageReport -reporttypes:Cobertura
31 |
32 | - name: Archive code coverage results
33 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
34 | with:
35 | name: code-coverage-report
36 | path: TestResults/Output/CoverageReport/
37 |
38 | code-coverage:
39 | if: github.repository_owner == 'Altinn' && (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false)
40 | name: Report code coverage
41 | runs-on: ubuntu-latest
42 | needs: test
43 | steps:
44 | - name: Download Coverage Results
45 | uses: actions/download-artifact@master
46 | with:
47 | name: code-coverage-report
48 | path: dist/
49 | - name: Create Coverage Summary Report
50 | uses: irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95 # v1.3.0
51 | with:
52 | filename: dist/Cobertura.xml
53 | badge: true
54 | fail_below_min: true
55 | format: markdown
56 | hide_branch_rate: false
57 | hide_complexity: true
58 | indicators: true
59 | output: both
60 | thresholds: '60 80'
61 |
62 | - name: Add Coverage PR Comment
63 | uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2
64 | with:
65 | recreate: true
66 | path: code-coverage-results.md
67 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/SSB/ClassificationOptions.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.SSB.Clients;
2 |
3 | namespace Altinn.Codelists.SSB;
4 |
5 | ///
6 | /// Controls various options when returning data from the SSB Classification API.
7 | ///
8 | public class ClassificationOptions
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | public ClassificationOptions() { }
14 |
15 | ///
16 | /// Maps the classification notes field to the description field.
17 | ///
18 | public bool MapNotesToDescription { get; set; } = false;
19 |
20 | ///
21 | /// Defines a custom mapping function for the description field.
22 | ///
23 | public Func? MapDescriptionFunc { get; set; }
24 |
25 | ///
26 | /// Returns the description for the provided .
27 | /// If is set, this will be used.
28 | /// If is set, the notes field will be used.
29 | /// If is not set and no
30 | /// is specified, an empty string will be returned.
31 | ///
32 | public string GetDescription(ClassificationCode classificationCode)
33 | {
34 | if (MapDescriptionFunc == null)
35 | {
36 | return MapNotesToDescription ? classificationCode.Notes : string.Empty;
37 | }
38 |
39 | return MapDescriptionFunc.Invoke(classificationCode);
40 | }
41 |
42 | ///
43 | /// Maps the classification notes field to the help text field.
44 | ///
45 | public bool MapNotesToHelpText { get; set; } = true;
46 |
47 | ///
48 | /// Defines a custom mapping function for the helptext field.
49 | ///
50 | public Func? MapHelpTextFunc { get; set; }
51 |
52 | ///
53 | /// Returns the helptext for the provided .
54 | /// If is set, this will be used.
55 | /// If is set, the notes field will be used.
56 | /// If is not set and no
57 | /// is specified, an empty string will be returned.
58 | ///
59 | public string GetHelpText(ClassificationCode classificationCode)
60 | {
61 | if (MapHelpTextFunc == null)
62 | {
63 | return MapNotesToHelpText ? classificationCode.Notes : string.Empty;
64 | }
65 |
66 | return MapHelpTextFunc.Invoke(classificationCode);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Kartverket/AdministrativeUnits/Clients/AdministrativeUnitsHttpClient.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Models;
2 | using Microsoft.Extensions.Options;
3 |
4 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits.Clients;
5 |
6 | ///
7 | /// Http client to get information on norways offical administrative units for counties and municipalities .
8 | ///
9 | internal sealed class AdministrativeUnitsHttpClient : IAdministrativeUnitsClient
10 | {
11 | private readonly HttpClient _httpClient;
12 |
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | public AdministrativeUnitsHttpClient(IOptions settings, HttpClient httpClient)
17 | {
18 | _httpClient = httpClient;
19 | _httpClient.BaseAddress = new Uri(settings.Value.BaseApiUrl);
20 | _httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
21 | }
22 |
23 | ///
24 | /// Sends a asynchronus GET request to get all the counties of Norway.
25 | ///
26 | public async Task> GetCounties()
27 | {
28 | using var response = await _httpClient.GetAsync("fylker");
29 | var responseJson = await response.Content.ReadAsStringAsync();
30 |
31 | var counties = JsonSerializer.Deserialize>(responseJson);
32 |
33 | return counties ?? new List();
34 | }
35 |
36 | ///
37 | /// Sends a asynchronus GET request to get all the unicipalities of Norway.
38 | ///
39 | public async Task> GetMunicipalities()
40 | {
41 | using var response = await _httpClient.GetAsync("kommuner");
42 | var responseJson = await response.Content.ReadAsStringAsync();
43 |
44 | var municipalities = JsonSerializer.Deserialize>(responseJson);
45 |
46 | return municipalities ?? new List();
47 | }
48 |
49 | ///
50 | /// Sends a asynchronus GET request to get all the municipalities within the specified county.
51 | ///
52 | /// County number (string) including leading zero's indentifying the county.
53 | public async Task> GetMunicipalities(string countyNumber)
54 | {
55 | using var response = await _httpClient.GetAsync(
56 | $"fylker/{countyNumber}?filtrer=kommuner,fylkesnavn,fylkesnummer"
57 | );
58 | var responseJson = await response.Content.ReadAsStringAsync();
59 |
60 | var county = JsonSerializer.Deserialize(responseJson);
61 |
62 | return county?.Municipalities ?? new List();
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/ClassificationOptionsTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.SSB;
2 | using Altinn.Codelists.SSB.Clients;
3 |
4 | namespace Altinn.Codelists.Tests.SSB;
5 |
6 | public class ClassificationOptionsTests
7 | {
8 | [Fact]
9 | public void ClassificationOptions_True_MapNotesToDescription()
10 | {
11 | var classificationOptions = new ClassificationOptions() { MapNotesToDescription = true };
12 |
13 | Assert.Equal(
14 | "Test",
15 | classificationOptions.GetDescription(new ClassificationCode("1", "Ja", "1") { Notes = "Test" })
16 | );
17 | }
18 |
19 | [Fact]
20 | public void ClassificationOptions_False_MapNotesToDescription()
21 | {
22 | var classificationOptions = new ClassificationOptions();
23 |
24 | Assert.Empty(classificationOptions.GetDescription(new ClassificationCode("1", "Ja", "1")));
25 | }
26 |
27 | [Theory]
28 | [InlineData(true)]
29 | [InlineData(false)]
30 | public void ClassificationOptions_FunctionProvided_FunctionShouldMapToDescription(bool mapNotesToDescription)
31 | {
32 | var classificationOptions = new ClassificationOptions()
33 | {
34 | MapDescriptionFunc = (classificationCode) => classificationCode.Notes,
35 | MapNotesToDescription = mapNotesToDescription,
36 | };
37 | var classificationCode = new ClassificationCode("9112", "Renholdere i virksomheter", "4") { Notes = "Test" };
38 |
39 | Assert.Equal("Test", classificationOptions.GetDescription(classificationCode));
40 | }
41 |
42 | [Fact]
43 | public void ClassificationOptions_True_MapNotesToHelpText()
44 | {
45 | var classificationOptions = new ClassificationOptions() { MapNotesToDescription = true };
46 |
47 | Assert.Equal(
48 | "Test",
49 | classificationOptions.GetHelpText(new ClassificationCode("1", "Ja", "1") { Notes = "Test" })
50 | );
51 | }
52 |
53 | [Fact]
54 | public void ClassificationOptions_False_MapNotesToHelpText()
55 | {
56 | var classificationOptions = new ClassificationOptions();
57 |
58 | Assert.Empty(classificationOptions.GetHelpText(new ClassificationCode("1", "Ja", "1")));
59 | }
60 |
61 | [Theory]
62 | [InlineData(true)]
63 | [InlineData(false)]
64 | public void ClassificationOptions_FunctionProvided_FunctionShouldMapToHelpText(bool mapNotesToHelpText)
65 | {
66 | var classificationOptions = new ClassificationOptions()
67 | {
68 | MapDescriptionFunc = (classificationCode) => classificationCode.Notes,
69 | MapNotesToDescription = mapNotesToHelpText,
70 | };
71 | var classificationCode = new ClassificationCode("9112", "Renholdere i virksomheter", "4") { Notes = "Test" };
72 |
73 | Assert.Equal("Test", classificationOptions.GetHelpText(classificationCode));
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/Posten/Clients/PostenFixture.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using Altinn.Codelists.Posten;
3 | using WireMock.Server;
4 | using WireMock.Settings;
5 |
6 | namespace Altinn.Codelists.Tests.Posten.Clients;
7 |
8 | internal sealed class PostenFixture : IAsyncDisposable
9 | {
10 | public WireMockServer Server { get; }
11 |
12 | private PostenFixture(WireMockServer server) => Server = server;
13 |
14 | public ValueTask DisposeAsync()
15 | {
16 | Server.Dispose();
17 | return default;
18 | }
19 |
20 | ///
21 | /// Starts a mock server for bring API
22 | /// - Proxy == true: it will just proxy the request through to the actual bring API, and save the request and response as a mapping
23 | /// - Proxy == false: serve the static mapping in the mappings folder
24 | ///
25 | /// To update the test data/mapping, just pass proxy == true
26 | /// In addition we can trim down the result set:
27 | /// 1. `wget https://www.bring.no/postnummerregister-ansi.txt`
28 | /// 2. `sed -i '10,5060 d' postnummerregister-ansi.txt`
29 | /// 3. `cat postnummerregister-ansi.txt | base64 -w 0`
30 | /// 4. Update the `Response.BodyAsBytes` property of the mapping JSON file
31 | /// 5. Update content length response header based on `stat postnummerregister-ansi.txt`
32 | /// 6. Remove header matching for hostname and such in the mapping JSON file
33 | /// Next time the test is ran with proxy == false, it will use the updated mapping
34 | ///
35 | public static Task Create(bool proxy)
36 | {
37 | var server = StartServer(proxy);
38 | return Task.FromResult(new PostenFixture(server));
39 | }
40 |
41 | private static WireMockServer StartServer(bool proxy, [CallerFilePath] string filePath = "")
42 | {
43 | WireMockServerSettings settings;
44 | if (proxy)
45 | {
46 | settings = new WireMockServerSettings
47 | {
48 | StartAdminInterface = true,
49 | ProxyAndRecordSettings = new ProxyAndRecordSettings
50 | {
51 | Url = PostenSettings.DefaultBaseUrl,
52 | SaveMapping = true,
53 | SaveMappingToFile = true,
54 | SaveMappingForStatusCodePattern = "2xx",
55 | PrefixForSavedMappingFile = "wiremock",
56 | },
57 | };
58 | }
59 | else
60 | {
61 | settings = new WireMockServerSettings { };
62 | }
63 | var server = WireMockServer.Start(settings);
64 | var dir = new FileInfo(filePath).Directory?.FullName;
65 | Assert.NotNull(dir);
66 | dir = Path.Join(dir, "mappings");
67 | if (proxy)
68 | {
69 | server.SaveStaticMappings(dir);
70 | }
71 | else
72 | {
73 | server.ReadStaticMappings(dir);
74 | }
75 |
76 | return server;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/.github/workflows/test-and-analyze.yml:
--------------------------------------------------------------------------------
1 | name: Code test and analysis
2 | on:
3 | push:
4 | branches: [ main ]
5 | pull_request:
6 | branches: [ main ]
7 | types: [opened, synchronize, reopened]
8 | workflow_dispatch:
9 | jobs:
10 | analyze:
11 | if: |
12 | github.repository_owner == 'Altinn' &&
13 | (github.event_name != 'pull_request' && github.event.repository.fork == false) ||
14 | (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false)
15 | name: Static code analysis
16 | runs-on: windows-latest
17 | steps:
18 | - name: Setup .NET
19 | uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5
20 | with:
21 | dotnet-version: |
22 | 8.0.x
23 | - name: Set up JDK 11
24 | uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5
25 | with:
26 | distribution: 'zulu'
27 | java-version: 17
28 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
29 | with:
30 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
31 | - name: Cache SonarCloud packages
32 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
33 | with:
34 | path: ~\sonar\cache
35 | key: ${{ runner.os }}-sonar
36 | restore-keys: ${{ runner.os }}-sonar
37 | - name: Cache SonarCloud scanner
38 | id: cache-sonar-scanner
39 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
40 | with:
41 | path: .\.sonar\scanner
42 | key: ${{ runner.os }}-sonar-scanner
43 | restore-keys: ${{ runner.os }}-sonar-scanner
44 | - name: Install SonarCloud scanner
45 | if: steps.cache-sonar-scanner.outputs.cache-hit != 'true'
46 | shell: powershell
47 | run: |
48 | New-Item -Path .\.sonar\scanner -ItemType Directory
49 | dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner
50 | - name: Build & Test
51 | run: |
52 | dotnet test Altinn.Codelists.sln -v m
53 | - name: Analyze
54 | env:
55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
56 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
57 | shell: powershell
58 | run: |
59 | .\.sonar\scanner\dotnet-sonarscanner begin /k:"Altinn_codelists-lib-dotnet" /o:"altinn" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.vstest.reportsPaths="**/*.trx" /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml"
60 |
61 | dotnet build Altinn.Codelists.sln
62 | dotnet test Altinn.Codelists.sln `
63 | --no-build `
64 | --results-directory TestResults/ `
65 | --collect:"XPlat Code Coverage" `
66 | -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
67 |
68 | .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}"
69 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/SSB/Clients/ClassificationCode.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace Altinn.Codelists.SSB.Clients;
4 |
5 | ///
6 | /// Represents a single classification code.
7 | ///
8 | public class ClassificationCode(string code, string name, string level)
9 | {
10 | ///
11 | /// Unique classification code
12 | ///
13 | [JsonPropertyName("code")]
14 | public string Code { get; set; } = code;
15 |
16 | ///
17 | /// If part of a hierarchy, this is a reference to the parent
18 | ///
19 | [JsonPropertyName("parentCode")]
20 | public string? ParentCode { get; set; } = null;
21 |
22 | ///
23 | /// Hierarchy level for classifications thats hierarchical. Non hierarchical classifications
24 | /// will have a value of one.
25 | ///
26 | [JsonPropertyName("level")]
27 | public string Level { get; set; } = level;
28 |
29 | ///
30 | /// Classification name.
31 | ///
32 | [JsonPropertyName("name")]
33 | public string Name { get; set; } = name;
34 |
35 | ///
36 | /// Classification short name.
37 | ///
38 | [JsonPropertyName("shortName")]
39 | public string ShortName { get; set; } = string.Empty;
40 |
41 | ///
42 | /// Classification presentation name.
43 | ///
44 | [JsonPropertyName("presentationName")]
45 | public string PresentationName { get; set; } = string.Empty;
46 |
47 | ///
48 | /// When the the classification is valid from.
49 | ///
50 | [JsonPropertyName("validFrom")]
51 | public DateTime? ValidFrom { get; set; }
52 |
53 | ///
54 | /// When the classification is valid to.
55 | ///
56 | [JsonPropertyName("validTo")]
57 | public DateTime? ValidTo { get; set; }
58 |
59 | ///
60 | /// Given a range (from/to) that spans dates where the classification has changed,
61 | /// this will show the from date the classification is valid. In such a response
62 | /// you will get multiple entries and will have to use
63 | /// and to separate them.
64 | ///
65 | [JsonPropertyName("validFromInRequestedRange")]
66 | public DateTime? ValidFromInRequestedRange { get; set; }
67 |
68 | ///
69 | /// Given a range (from/to) that spans dates where the classification has changed,
70 | /// this will show the to date the classification is valid. In such a response
71 | /// you will get multiple entries and will have to use
72 | /// and to separate them.
73 | ///
74 | [JsonPropertyName("validToInRequestedRange")]
75 | public DateTime? ValidToInRequestedRange { get; set; }
76 |
77 | ///
78 | /// Textual notes added to the classification code.
79 | ///
80 | [JsonPropertyName("notes")]
81 | public string Notes { get; set; } = string.Empty;
82 | }
83 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/Testdata/smallGame_Variant.json:
--------------------------------------------------------------------------------
1 | {
2 | "codes": [
3 | {
4 | "code": "01",
5 | "parentCode": null,
6 | "level": "1",
7 | "name": "Storfugl",
8 | "shortName": "",
9 | "presentationName": "",
10 | "validFrom": null,
11 | "validTo": null,
12 | "notes": ""
13 | },
14 | {
15 | "code": "02",
16 | "parentCode": null,
17 | "level": "1",
18 | "name": "Orrfugl",
19 | "shortName": "",
20 | "presentationName": "",
21 | "validFrom": null,
22 | "validTo": null,
23 | "notes": ""
24 | },
25 | {
26 | "code": "04",
27 | "parentCode": null,
28 | "level": "1",
29 | "name": "Jerpe",
30 | "shortName": "",
31 | "presentationName": "",
32 | "validFrom": null,
33 | "validTo": null,
34 | "notes": ""
35 | },
36 | {
37 | "code": "05",
38 | "parentCode": null,
39 | "level": "1",
40 | "name": "Ringdue",
41 | "shortName": "",
42 | "presentationName": "",
43 | "validFrom": null,
44 | "validTo": null,
45 | "notes": ""
46 | },
47 | {
48 | "code": "06",
49 | "parentCode": null,
50 | "level": "1",
51 | "name": "Ravn",
52 | "shortName": "",
53 | "presentationName": "",
54 | "validFrom": null,
55 | "validTo": null,
56 | "notes": ""
57 | },
58 | {
59 | "code": "07",
60 | "parentCode": null,
61 | "level": "1",
62 | "name": "Kråke",
63 | "shortName": "",
64 | "presentationName": "",
65 | "validFrom": null,
66 | "validTo": null,
67 | "notes": ""
68 | },
69 | {
70 | "code": "08.1",
71 | "parentCode": null,
72 | "level": "1",
73 | "name": "Skjære",
74 | "shortName": "",
75 | "presentationName": "",
76 | "validFrom": null,
77 | "validTo": null,
78 | "notes": ""
79 | },
80 | {
81 | "code": "08.2",
82 | "parentCode": null,
83 | "level": "1",
84 | "name": "Nøtteskrike",
85 | "shortName": "",
86 | "presentationName": "",
87 | "validFrom": null,
88 | "validTo": null,
89 | "notes": ""
90 | },
91 | {
92 | "code": "09.1",
93 | "parentCode": null,
94 | "level": "1",
95 | "name": "Gråtrost",
96 | "shortName": "",
97 | "presentationName": "",
98 | "validFrom": null,
99 | "validTo": null,
100 | "notes": ""
101 | },
102 | {
103 | "code": "15.1",
104 | "parentCode": null,
105 | "level": "1",
106 | "name": "Toppskarv",
107 | "shortName": "",
108 | "presentationName": "",
109 | "validFrom": null,
110 | "validTo": null,
111 | "notes": ""
112 | },
113 | {
114 | "code": "15.2",
115 | "parentCode": null,
116 | "level": "1",
117 | "name": "Storskarv",
118 | "shortName": "",
119 | "presentationName": "",
120 | "validFrom": null,
121 | "validTo": null,
122 | "notes": ""
123 | }
124 | ]
125 | }
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/Mocks/AdministrativeUnitsHttpClientMock.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.Kartverket.AdministrativeUnits;
2 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Clients;
3 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Models;
4 | using RichardSzalay.MockHttp;
5 |
6 | namespace Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Mocks;
7 |
8 | public class AdministrativeUnitsHttpClientMock : IAdministrativeUnitsClient
9 | {
10 | private const string COUNTIES_TESTDATA_RESOURCE =
11 | "Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Testdata.fylker.json";
12 | private const string COUNTY_MUNICIPALITIES_TESTDATA_RESOURCE =
13 | "Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Testdata.fylke46_kommuner.json";
14 | private const string MUNICIPALITIES_TESTDATA_RESOURCE =
15 | "Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Testdata.kommuner.json";
16 |
17 | private readonly IAdministrativeUnitsClient _administrativeUnitsHttpClient;
18 | private readonly IOptions _administrativeUnitsOptions;
19 |
20 | public MockHttpMessageHandler HttpMessageHandlerMock { get; private set; }
21 | public MockedRequest MockedCountiesRequest { get; private set; }
22 | public MockedRequest MockedMunicipalitiesRequest { get; private set; }
23 |
24 | public AdministrativeUnitsHttpClientMock(IOptions administrativeUnitsOptions)
25 | {
26 | _administrativeUnitsOptions = administrativeUnitsOptions;
27 |
28 | HttpMessageHandlerMock = new MockHttpMessageHandler();
29 | MockedCountiesRequest = HttpMessageHandlerMock
30 | .When("https://ws.geonorge.no/kommuneinfo/v1/fylker")
31 | .Respond("application/json", EmbeddedResource.LoadDataAsString(COUNTIES_TESTDATA_RESOURCE).Result);
32 |
33 | MockedMunicipalitiesRequest = HttpMessageHandlerMock
34 | .When("https://ws.geonorge.no/kommuneinfo/v1/kommuner")
35 | .Respond("application/json", EmbeddedResource.LoadDataAsString(MUNICIPALITIES_TESTDATA_RESOURCE).Result);
36 |
37 | MockedMunicipalitiesRequest = HttpMessageHandlerMock
38 | .When("https://ws.geonorge.no/kommuneinfo/v1/fylker/46?filtrer=kommuner,fylkesnavn,fylkesnummer")
39 | .Respond(
40 | "application/json",
41 | EmbeddedResource.LoadDataAsString(COUNTY_MUNICIPALITIES_TESTDATA_RESOURCE).Result
42 | );
43 |
44 | _administrativeUnitsHttpClient = new AdministrativeUnitsHttpClient(
45 | _administrativeUnitsOptions,
46 | new HttpClient(HttpMessageHandlerMock)
47 | );
48 | }
49 |
50 | public Task> GetCounties()
51 | {
52 | return _administrativeUnitsHttpClient.GetCounties();
53 | }
54 |
55 | public Task> GetMunicipalities()
56 | {
57 | return _administrativeUnitsHttpClient.GetMunicipalities();
58 | }
59 |
60 | public Task> GetMunicipalities(string countyNumber)
61 | {
62 | return _administrativeUnitsHttpClient.GetMunicipalities(countyNumber);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/OccupationsCodelistProviderTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.SSB;
3 | using Altinn.Codelists.SSB.Clients;
4 | using Altinn.Codelists.SSB.Models;
5 | using Altinn.Codelists.Tests.SSB.Mocks;
6 |
7 | namespace Altinn.Codelists.Tests.SSB;
8 |
9 | public class OccupationsCodelistProviderTests
10 | {
11 | [Fact]
12 | public async Task GetAppOptionsAsync_AllLevels_ShouldReturnListOfCodes()
13 | {
14 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings()));
15 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider(
16 | "yrker",
17 | Classification.Occupations,
18 | httpClientMock
19 | );
20 |
21 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary());
22 |
23 | Assert.NotNull(appOptions.Options);
24 | Assert.Equal(582, appOptions.Options.Count);
25 | Assert.Equal("Akademiske yrker", appOptions.Options.First(x => x.Value == "2").Label);
26 | Assert.Equal("IKT-rådgivere", appOptions.Options.First(x => x.Value == "25").Label);
27 | Assert.Equal(
28 | "Programvare- og applikasjonsutviklere/analytikere",
29 | appOptions.Options.First(x => x.Value == "251").Label
30 | );
31 | Assert.Equal("Programvareutviklere", appOptions.Options.First(x => x.Value == "2512").Label);
32 | }
33 |
34 | [Fact]
35 | public async Task GetAppOptionsAsync_FirstLevelOnly_ShouldReturnListOfCodes()
36 | {
37 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings()));
38 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider(
39 | "yrker",
40 | Classification.Occupations,
41 | httpClientMock
42 | );
43 |
44 | var appOptions = await appOptionsProvider.GetAppOptionsAsync(
45 | "nb",
46 | new Dictionary() { { "level", "1" } }
47 | );
48 |
49 | Assert.NotNull(appOptions.Options);
50 | Assert.Equal(10, appOptions.Options.Count);
51 | Assert.Equal("Militære yrker og uoppgitt", appOptions.Options.First(x => x.Value == "0").Label);
52 | }
53 |
54 | [Fact]
55 | public async Task GetAppOptionsAsync_DefaultFirstLevel_ShouldReturnListOfCodes()
56 | {
57 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings()));
58 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider(
59 | "næringsgruppering",
60 | Classification.IndustryGrouping,
61 | httpClientMock,
62 | new Dictionary() { { "level", "1" } }
63 | );
64 |
65 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary());
66 |
67 | Assert.NotNull(appOptions.Options);
68 | Assert.Equal(21, appOptions.Options.Count);
69 | Assert.Equal("Jordbruk, skogbruk og fiske", appOptions.Options.First(x => x.Value == "A").Label);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/IndustryGroupingCodelistProviderTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.SSB;
3 | using Altinn.Codelists.SSB.Clients;
4 | using Altinn.Codelists.SSB.Models;
5 | using Altinn.Codelists.Tests.SSB.Mocks;
6 |
7 | namespace Altinn.Codelists.Tests.SSB;
8 |
9 | public class IndustryGroupingCodelistProviderTests
10 | {
11 | [Fact]
12 | public async Task GetAppOptionsAsync_AllLevels_ShouldReturnListOfCodes()
13 | {
14 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings()));
15 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider(
16 | "næringsgruppering",
17 | Classification.IndustryGrouping,
18 | httpClientMock
19 | );
20 |
21 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary());
22 |
23 | Assert.NotNull(appOptions.Options);
24 | Assert.Equal(1811, appOptions.Options.Count);
25 | Assert.Equal("Jordbruk, skogbruk og fiske", appOptions.Options.First(x => x.Value == "A").Label);
26 | Assert.Equal(
27 | "Jordbruk og tjenester tilknyttet jordbruk, jakt og viltstell",
28 | appOptions.Options.First(x => x.Value == "01").Label
29 | );
30 | Assert.Equal("Dyrking av ettårige vekster", appOptions.Options.First(x => x.Value == "01.1").Label);
31 | }
32 |
33 | [Fact]
34 | public async Task GetAppOptionsAsync_FirstLevelOnly_ShouldReturnListOfCodes()
35 | {
36 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings()));
37 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider(
38 | "næringsgruppering",
39 | Classification.IndustryGrouping,
40 | httpClientMock
41 | );
42 |
43 | var appOptions = await appOptionsProvider.GetAppOptionsAsync(
44 | "nb",
45 | new Dictionary() { { "level", "1" } }
46 | );
47 |
48 | Assert.NotNull(appOptions.Options);
49 | Assert.Equal(21, appOptions.Options.Count);
50 | Assert.Equal("Jordbruk, skogbruk og fiske", appOptions.Options.First(x => x.Value == "A").Label);
51 | Assert.Equal("1", appOptions.Parameters.First(x => x.Key == "level").Value);
52 | }
53 |
54 | [Fact]
55 | public async Task GetAppOptionsAsync_DefaultFirstLevel_ShouldReturnListOfCodes()
56 | {
57 | var httpClientMock = new ClassificationsHttpClientMock(Options.Create(new ClassificationSettings()));
58 | IAppOptionsProvider appOptionsProvider = new ClassificationCodelistProvider(
59 | "næringsgruppering",
60 | Classification.IndustryGrouping,
61 | httpClientMock,
62 | new Dictionary() { { "level", "1" } }
63 | );
64 |
65 | var appOptions = await appOptionsProvider.GetAppOptionsAsync("nb", new Dictionary());
66 |
67 | Assert.NotNull(appOptions.Options);
68 | Assert.Equal(21, appOptions.Options.Count);
69 | Assert.Equal("Jordbruk, skogbruk og fiske", appOptions.Options.First(x => x.Value == "A").Label);
70 | Assert.Equal("nb", appOptions.Parameters.First(x => x.Key == "language").Value);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/Altinn.Codelists.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 | false
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | runtime; build; native; contentfiles; analyzers; buildtransitive
51 | all
52 |
53 |
54 | runtime; build; native; contentfiles; analyzers; buildtransitive
55 | all
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/Testdata/maritalStatus.json:
--------------------------------------------------------------------------------
1 | {
2 | "codes": [
3 | {
4 | "code": "1",
5 | "parentCode": null,
6 | "level": "1",
7 | "name": "Ugift",
8 | "shortName": "",
9 | "presentationName": "",
10 | "validFrom": null,
11 | "validTo": null,
12 | "validFromInRequestedRange": "2022-07-10",
13 | "validToInRequestedRange": null,
14 | "notes": ""
15 | },
16 | {
17 | "code": "2",
18 | "parentCode": null,
19 | "level": "1",
20 | "name": "Gift",
21 | "shortName": "",
22 | "presentationName": "",
23 | "validFrom": null,
24 | "validTo": null,
25 | "validFromInRequestedRange": "2022-07-10",
26 | "validToInRequestedRange": null,
27 | "notes": ""
28 | },
29 | {
30 | "code": "3",
31 | "parentCode": null,
32 | "level": "1",
33 | "name": "Enke/enkemann",
34 | "shortName": "",
35 | "presentationName": "",
36 | "validFrom": null,
37 | "validTo": null,
38 | "validFromInRequestedRange": "2022-07-10",
39 | "validToInRequestedRange": null,
40 | "notes": ""
41 | },
42 | {
43 | "code": "4",
44 | "parentCode": null,
45 | "level": "1",
46 | "name": "Skilt",
47 | "shortName": "",
48 | "presentationName": "",
49 | "validFrom": null,
50 | "validTo": null,
51 | "validFromInRequestedRange": "2022-07-10",
52 | "validToInRequestedRange": null,
53 | "notes": ""
54 | },
55 | {
56 | "code": "5",
57 | "parentCode": null,
58 | "level": "1",
59 | "name": "Separert",
60 | "shortName": "",
61 | "presentationName": "",
62 | "validFrom": null,
63 | "validTo": null,
64 | "validFromInRequestedRange": "2022-07-10",
65 | "validToInRequestedRange": null,
66 | "notes": ""
67 | },
68 | {
69 | "code": "6",
70 | "parentCode": null,
71 | "level": "1",
72 | "name": "Registrert partner",
73 | "shortName": "",
74 | "presentationName": "",
75 | "validFrom": null,
76 | "validTo": null,
77 | "validFromInRequestedRange": "2022-07-10",
78 | "validToInRequestedRange": null,
79 | "notes": ""
80 | },
81 | {
82 | "code": "7",
83 | "parentCode": null,
84 | "level": "1",
85 | "name": "Separert partner",
86 | "shortName": "",
87 | "presentationName": "",
88 | "validFrom": null,
89 | "validTo": null,
90 | "validFromInRequestedRange": "2022-07-10",
91 | "validToInRequestedRange": null,
92 | "notes": ""
93 | },
94 | {
95 | "code": "8",
96 | "parentCode": null,
97 | "level": "1",
98 | "name": "Skilt partner",
99 | "shortName": "",
100 | "presentationName": "",
101 | "validFrom": null,
102 | "validTo": null,
103 | "validFromInRequestedRange": "2022-07-10",
104 | "validToInRequestedRange": null,
105 | "notes": ""
106 | },
107 | {
108 | "code": "9",
109 | "parentCode": null,
110 | "level": "1",
111 | "name": "Gjenlevende partner",
112 | "shortName": "",
113 | "presentationName": "",
114 | "validFrom": null,
115 | "validTo": null,
116 | "validFromInRequestedRange": "2022-07-10",
117 | "validToInRequestedRange": null,
118 | "notes": ""
119 | }
120 | ]
121 | }
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/Clients/ClassificationsHttpClientCachedTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.SSB.Clients;
2 | using Altinn.Codelists.Tests.SSB.Mocks;
3 | using Microsoft.Extensions.Caching.Memory;
4 |
5 | namespace Altinn.Codelists.Tests.SSB.Clients;
6 |
7 | public class ClassificationsHttpClientCachedTests
8 | {
9 | [Fact]
10 | public async Task GetCodes_EmptyCache_ShouldReturnValues()
11 | {
12 | var classificationsHttpClientMock = new ClassificationsHttpClientMock(
13 | Options.Create(new ClassificationSettings())
14 | );
15 | var classificationsHttpClientCached = new ClassificationsHttpClientCached(
16 | classificationsHttpClientMock,
17 | new MemoryCache(new MemoryCacheOptions())
18 | );
19 |
20 | var maritalStatus = await classificationsHttpClientCached.GetClassificationCodes(19);
21 |
22 | Assert.Equal(9, maritalStatus.Codes.Count);
23 | }
24 |
25 | [Fact]
26 | public async Task GetCounties_CacheFilled_ShouldReturnFromCache()
27 | {
28 | var classificationsHttpClientMock = new ClassificationsHttpClientMock(
29 | Options.Create(new ClassificationSettings())
30 | );
31 | var classificationsHttpClientCached = new ClassificationsHttpClientCached(
32 | classificationsHttpClientMock,
33 | new MemoryCache(new MemoryCacheOptions())
34 | );
35 |
36 | // First request will fill the cache
37 | _ = await classificationsHttpClientCached.GetClassificationCodes(19);
38 |
39 | // Second request should not trigger another http request from the client
40 | var maritalStatus = await classificationsHttpClientCached.GetClassificationCodes(19);
41 |
42 | Assert.Equal(9, maritalStatus.Codes.Count);
43 | Assert.Equal(
44 | 1,
45 | classificationsHttpClientMock.HttpMessageHandlerMock.GetMatchCount(
46 | classificationsHttpClientMock.MockedMaritalStatusRequest
47 | )
48 | );
49 | }
50 |
51 | [Fact]
52 | public async Task GetCounties_CacheExpired_ShouldPopulateAgain()
53 | {
54 | var classificationsHttpClientMock = new ClassificationsHttpClientMock(
55 | Options.Create(new ClassificationSettings())
56 | );
57 | var classificationsHttpClientCached = new ClassificationsHttpClientCached(
58 | classificationsHttpClientMock,
59 | new MemoryCache(new MemoryCacheOptions()),
60 | () =>
61 | {
62 | // Let the cache entry live for 100 milliseconds
63 | return new MemoryCacheEntryOptions()
64 | {
65 | AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(100),
66 | Priority = CacheItemPriority.Normal,
67 | };
68 | }
69 | );
70 |
71 | // First request will fill the cache
72 | await classificationsHttpClientCached.GetClassificationCodes(19);
73 |
74 | // Wait for the cached entry to be evicted
75 | await Task.Delay(200);
76 |
77 | // This should trigger another http request and fill the cache again
78 | var maritalStatusCodes = await classificationsHttpClientCached.GetClassificationCodes(19);
79 |
80 | Assert.Equal(9, maritalStatusCodes.Codes.Count);
81 | Assert.Equal(
82 | 2,
83 | classificationsHttpClientMock.HttpMessageHandlerMock.GetMatchCount(
84 | classificationsHttpClientMock.MockedMaritalStatusRequest
85 | )
86 | );
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/Kartverket/AdministrativeUnits/Clients/CountiesClientCachedTests.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.Kartverket.AdministrativeUnits;
2 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Clients;
3 | using Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Mocks;
4 | using Microsoft.Extensions.Caching.Memory;
5 |
6 | namespace Altinn.Codelists.Tests.Kartverket.AdministrativeUnits.Clients;
7 |
8 | public class CountiesClientCachedTests
9 | {
10 | [Fact]
11 | public async Task GetCounties_EmptyCache_ShouldReturnAllCounties()
12 | {
13 | var administrativeUnitsHttpClientMock = new AdministrativeUnitsHttpClientMock(
14 | Options.Create(new AdministrativeUnitsSettings())
15 | );
16 | var administrativeUnitsHttpClientCached = new AdministrativeUnitsHttpClientCached(
17 | administrativeUnitsHttpClientMock,
18 | new MemoryCache(new MemoryCacheOptions())
19 | );
20 |
21 | var counties = await administrativeUnitsHttpClientCached.GetCounties();
22 |
23 | Assert.Equal(11, counties.Count);
24 | }
25 |
26 | [Fact]
27 | public async Task GetCounties_CacheFilled_ShouldReturnFromCache()
28 | {
29 | var administrativeUnitsHttpClientMock = new AdministrativeUnitsHttpClientMock(
30 | Options.Create(new AdministrativeUnitsSettings())
31 | );
32 | var administrativeUnitsHttpClientCached = new AdministrativeUnitsHttpClientCached(
33 | administrativeUnitsHttpClientMock,
34 | new MemoryCache(new MemoryCacheOptions())
35 | );
36 |
37 | // First request will fill the cache
38 | await administrativeUnitsHttpClientCached.GetCounties();
39 |
40 | // Second request should not trigger another http request from the client
41 | var counties = await administrativeUnitsHttpClientCached.GetCounties();
42 |
43 | Assert.Equal(11, counties.Count);
44 | Assert.Equal(
45 | 1,
46 | administrativeUnitsHttpClientMock.HttpMessageHandlerMock.GetMatchCount(
47 | administrativeUnitsHttpClientMock.MockedCountiesRequest
48 | )
49 | );
50 | }
51 |
52 | [Fact]
53 | public async Task GetCounties_CacheExpired_ShouldPopulateAgain()
54 | {
55 | var administrativeUnitsHttpClientMock = new AdministrativeUnitsHttpClientMock(
56 | Options.Create(new AdministrativeUnitsSettings())
57 | );
58 | var administrativeUnitsHttpClientCached = new AdministrativeUnitsHttpClientCached(
59 | administrativeUnitsHttpClientMock,
60 | new MemoryCache(new MemoryCacheOptions()),
61 | () =>
62 | {
63 | // Let the cache entry live for 100 milliseconds
64 | return new MemoryCacheEntryOptions()
65 | {
66 | AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(100),
67 | Priority = CacheItemPriority.Normal,
68 | };
69 | }
70 | );
71 |
72 | // First request will fill the cache
73 | await administrativeUnitsHttpClientCached.GetCounties();
74 |
75 | // Wait for the cached entry to be evicted
76 | await Task.Delay(200);
77 |
78 | // This should trigger another http request and fill the cache again
79 | var counties = await administrativeUnitsHttpClientCached.GetCounties();
80 |
81 | Assert.Equal(11, counties.Count);
82 | Assert.Equal(
83 | 2,
84 | administrativeUnitsHttpClientMock.HttpMessageHandlerMock.GetMatchCount(
85 | administrativeUnitsHttpClientMock.MockedCountiesRequest
86 | )
87 | );
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/RestCountries/Models/Country.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace Altinn.Codelists.RestCountries.Models;
4 |
5 | ///
6 | /// Holds information of a country.
7 | ///
8 | public class Country(Name name)
9 | {
10 | ///
11 | /// The name of the country (in english)
12 | ///
13 | [JsonPropertyName("name")]
14 | public Name Name { get; set; } = name;
15 |
16 | ///
17 | /// ISO 3166-1 alpha-2 – two-letter country codes which are the most widely used of the three,
18 | /// and used most prominently for the Internet's country code top-level domains (with a few exceptions).
19 | ///
20 | [JsonPropertyName("cca2")]
21 | public string CountryCodeAlpha2 { get; set; } = "";
22 |
23 | ///
24 | /// ISO 3166-1 alpha-3 – three-letter country codes which allow a better visual association
25 | /// between the codes and the country names than the alpha-2 codes.
26 | ///
27 | [JsonPropertyName("ccn3")]
28 | public string CountryCodeNumeric3 { get; set; } = "";
29 |
30 | ///
31 | /// ISO 3166-1 numeric – three-digit country codes which are identical to those developed and maintained
32 | /// by the United Nations Statistics Division, with the advantage of script (writing system) independence,
33 | /// and hence useful for people or systems using non-Latin scripts.
34 | ///
35 | [JsonPropertyName("cca3")]
36 | public string CountryCodeAlpha3 { get; set; } = "";
37 |
38 | ///
39 | /// ISO 3166-1 independence status (denotes the country is considered a sovereign state)
40 | ///
41 | [JsonPropertyName("independent")]
42 | public bool Independent { get; set; }
43 |
44 | ///
45 | /// ISO 3166-1 assignment status
46 | ///
47 | [JsonPropertyName("status")]
48 | public string Status { get; set; } = "";
49 |
50 | ///
51 | /// UN Member status
52 | ///
53 | [JsonPropertyName("unMember")]
54 | public bool UnitedNationsMember { get; set; }
55 |
56 | ///
57 | /// Emojii flag character
58 | ///
59 | [JsonPropertyName("flag")]
60 | public string EmojiFlag { get; set; } = "";
61 |
62 | ///
63 | /// Region (Africa, Americas, Antarctic, Asia, Europe, Oceania)
64 | ///
65 | [JsonPropertyName("region")]
66 | public string Region { get; set; } = "";
67 |
68 | ///
69 | /// Subregion
70 | ///
71 | [JsonPropertyName("subregion")]
72 | public string SubRegion { get; set; } = "";
73 |
74 | ///
75 | /// Official languages
76 | ///
77 | [JsonPropertyName("languages")]
78 | public Dictionary Languages { get; set; } = new Dictionary();
79 |
80 | ///
81 | /// Name of country translated to various languages.
82 | ///
83 | [JsonPropertyName("translations")]
84 | public Dictionary Translations { get; set; } = new Dictionary();
85 |
86 | ///
87 | /// Latitude and longitude
88 | ///
89 | [JsonPropertyName("latlng")]
90 | public decimal[] LatitudeLongitude { get; set; } = Array.Empty();
91 |
92 | ///
93 | /// Top level domains
94 | ///
95 | [JsonPropertyName("tld")]
96 | public string[] TopLevelDomains { get; set; } = Array.Empty();
97 |
98 | ///
99 | /// Currencies used
100 | ///
101 | [JsonPropertyName("currencies")]
102 | public Dictionary Currencies { get; set; } = new Dictionary();
103 | }
104 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/SSB/Clients/ClassificationsHttpClientCached.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using Microsoft.Extensions.Caching.Memory;
3 |
4 | namespace Altinn.Codelists.SSB.Clients;
5 |
6 | ///
7 | /// Http client to get classification codes from SSB.
8 | /// This is a decorator class for
9 | /// that caches the information for performance reasons.
10 | ///
11 | internal sealed class ClassificationsHttpClientCached : IClassificationsClient
12 | {
13 | private readonly IClassificationsClient _classificationsClient;
14 | private readonly IMemoryCache _memoryCache;
15 | private readonly Func _getCacheEntryOptions;
16 |
17 | ///
18 | /// Initializes a new instance of the class.
19 | ///
20 | public ClassificationsHttpClientCached(IClassificationsClient classificationsClient, IMemoryCache memoryCache)
21 | : this(classificationsClient, memoryCache, DefaultCacheEntryOptions)
22 | {
23 | _classificationsClient = classificationsClient;
24 | _memoryCache = memoryCache;
25 | }
26 |
27 | ///
28 | /// Initializes a new instance of the class.
29 | ///
30 | public ClassificationsHttpClientCached(
31 | IClassificationsClient classificationsClient,
32 | IMemoryCache memoryCache,
33 | Func getCacheEntryOptionsFunc
34 | )
35 | {
36 | _classificationsClient = classificationsClient;
37 | _memoryCache = memoryCache;
38 | _getCacheEntryOptions = getCacheEntryOptionsFunc;
39 | }
40 |
41 | ///
42 | public async Task GetClassificationCodes(
43 | int classificationId,
44 | string language = "nb",
45 | DateOnly? atDate = null,
46 | string level = "",
47 | string variant = "",
48 | string selectCodes = ""
49 | )
50 | {
51 | var cacheKey = GetCacheKey(classificationId, language, atDate, level, variant, selectCodes);
52 |
53 | var codes = await _memoryCache.GetOrCreateAsync(
54 | cacheKey,
55 | async cacheEntry =>
56 | {
57 | var cacheEntryOptions = _getCacheEntryOptions.Invoke();
58 | cacheEntry.SetOptions(cacheEntryOptions);
59 | var data = await _classificationsClient.GetClassificationCodes(
60 | classificationId,
61 | language,
62 | atDate,
63 | level,
64 | variant,
65 | selectCodes
66 | );
67 |
68 | if (data is null)
69 | {
70 | cacheEntry.Dispose();
71 | return null;
72 | }
73 |
74 | return data;
75 | }
76 | );
77 |
78 | return codes ?? new ClassificationCodes() { Codes = new List() };
79 | }
80 |
81 | private static string GetCacheKey(
82 | int classificationId,
83 | string language,
84 | DateOnly? atDate,
85 | string level,
86 | string variant,
87 | string selectCodes
88 | )
89 | {
90 | return $"{classificationId}_{language}_{atDate?.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}_{level}_{variant}_{selectCodes}";
91 | }
92 |
93 | // Expires the cache entry at midnight, to get potential new or removed entries.
94 | private static MemoryCacheEntryOptions DefaultCacheEntryOptions()
95 | {
96 | DateTime expirationTime = DateTime.Today.AddDays(1);
97 |
98 | return new MemoryCacheEntryOptions()
99 | {
100 | AbsoluteExpiration = expirationTime,
101 | Priority = CacheItemPriority.Normal,
102 | };
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/Testdata/counties.json:
--------------------------------------------------------------------------------
1 | {
2 | "codes": [
3 | {
4 | "code": "03",
5 | "parentCode": null,
6 | "level": "1",
7 | "name": "Oslo",
8 | "shortName": "",
9 | "presentationName": "",
10 | "validFrom": null,
11 | "validTo": null,
12 | "validFromInRequestedRange": "2022-07-10",
13 | "validToInRequestedRange": null,
14 | "notes": ""
15 | },
16 | {
17 | "code": "11",
18 | "parentCode": null,
19 | "level": "1",
20 | "name": "Rogaland",
21 | "shortName": "",
22 | "presentationName": "",
23 | "validFrom": null,
24 | "validTo": null,
25 | "validFromInRequestedRange": "2022-07-10",
26 | "validToInRequestedRange": null,
27 | "notes": ""
28 | },
29 | {
30 | "code": "15",
31 | "parentCode": null,
32 | "level": "1",
33 | "name": "Møre og Romsdal",
34 | "shortName": "",
35 | "presentationName": "",
36 | "validFrom": null,
37 | "validTo": null,
38 | "validFromInRequestedRange": "2022-07-10",
39 | "validToInRequestedRange": null,
40 | "notes": ""
41 | },
42 | {
43 | "code": "18",
44 | "parentCode": null,
45 | "level": "1",
46 | "name": "Nordland - Nordlánnda",
47 | "shortName": "",
48 | "presentationName": "",
49 | "validFrom": null,
50 | "validTo": null,
51 | "validFromInRequestedRange": "2022-07-10",
52 | "validToInRequestedRange": null,
53 | "notes": "Stortingets vedtak fra 27. april 2021 innfører tospråklig navn på Nordland fylke, norsk og lulesamisk. Endringen trer i kraft straks. "
54 | },
55 | {
56 | "code": "30",
57 | "parentCode": null,
58 | "level": "1",
59 | "name": "Viken",
60 | "shortName": "",
61 | "presentationName": "",
62 | "validFrom": null,
63 | "validTo": null,
64 | "validFromInRequestedRange": "2022-07-10",
65 | "validToInRequestedRange": null,
66 | "notes": ""
67 | },
68 | {
69 | "code": "34",
70 | "parentCode": null,
71 | "level": "1",
72 | "name": "Innlandet",
73 | "shortName": "",
74 | "presentationName": "",
75 | "validFrom": null,
76 | "validTo": null,
77 | "validFromInRequestedRange": "2022-07-10",
78 | "validToInRequestedRange": null,
79 | "notes": ""
80 | },
81 | {
82 | "code": "38",
83 | "parentCode": null,
84 | "level": "1",
85 | "name": "Vestfold og Telemark",
86 | "shortName": "",
87 | "presentationName": "",
88 | "validFrom": null,
89 | "validTo": null,
90 | "validFromInRequestedRange": "2022-07-10",
91 | "validToInRequestedRange": null,
92 | "notes": ""
93 | },
94 | {
95 | "code": "42",
96 | "parentCode": null,
97 | "level": "1",
98 | "name": "Agder",
99 | "shortName": "",
100 | "presentationName": "",
101 | "validFrom": null,
102 | "validTo": null,
103 | "validFromInRequestedRange": "2022-07-10",
104 | "validToInRequestedRange": null,
105 | "notes": ""
106 | },
107 | {
108 | "code": "46",
109 | "parentCode": null,
110 | "level": "1",
111 | "name": "Vestland",
112 | "shortName": "",
113 | "presentationName": "",
114 | "validFrom": null,
115 | "validTo": null,
116 | "validFromInRequestedRange": "2022-07-10",
117 | "validToInRequestedRange": null,
118 | "notes": ""
119 | },
120 | {
121 | "code": "50",
122 | "parentCode": null,
123 | "level": "1",
124 | "name": "Trøndelag - Trööndelage",
125 | "shortName": "",
126 | "presentationName": "",
127 | "validFrom": null,
128 | "validTo": null,
129 | "validFromInRequestedRange": "2022-07-10",
130 | "validToInRequestedRange": null,
131 | "notes": ""
132 | },
133 | {
134 | "code": "54",
135 | "parentCode": null,
136 | "level": "1",
137 | "name": "Troms og Finnmark - Romsa ja Finnmárku - Tromssa ja Finmarkku",
138 | "shortName": "",
139 | "presentationName": "",
140 | "validFrom": null,
141 | "validTo": null,
142 | "validFromInRequestedRange": "2022-07-10",
143 | "validToInRequestedRange": null,
144 | "notes": ""
145 | },
146 | {
147 | "code": "99",
148 | "parentCode": null,
149 | "level": "1",
150 | "name": "Uoppgitt",
151 | "shortName": "",
152 | "presentationName": "",
153 | "validFrom": null,
154 | "validTo": null,
155 | "validFromInRequestedRange": "2022-07-10",
156 | "validToInRequestedRange": null,
157 | "notes": ""
158 | }
159 | ]
160 | }
--------------------------------------------------------------------------------
/src/Altinn.Codelists/Kartverket/AdministrativeUnits/Clients/AdministrativeUnitsHttpClientCached.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.Kartverket.AdministrativeUnits.Models;
2 | using Microsoft.Extensions.Caching.Memory;
3 |
4 | namespace Altinn.Codelists.Kartverket.AdministrativeUnits.Clients;
5 |
6 | ///
7 | /// Http client to get information on norways offical administrative units for counties and municipalities.
8 | /// This class caches the information for performance reasons to avoid costly http calls.
9 | ///
10 | internal sealed class AdministrativeUnitsHttpClientCached : IAdministrativeUnitsClient
11 | {
12 | private const string COUNTIES_CACHE_KEY = "counties";
13 | private const string MUNICIPALITIES_CACHE_KEY_BASE = "municipalities";
14 |
15 | private readonly IAdministrativeUnitsClient _administrativeUnitsClient;
16 | private readonly IMemoryCache _memoryCache;
17 | private readonly Func _getCacheEntryOptions;
18 |
19 | ///
20 | /// Initializes a new instance of the class.
21 | ///
22 | public AdministrativeUnitsHttpClientCached(IAdministrativeUnitsClient countiesClient, IMemoryCache memoryCache)
23 | : this(countiesClient, memoryCache, DefaultCacheEntryOptions) { }
24 |
25 | ///
26 | /// Initializes a new instance of the class.
27 | ///
28 | public AdministrativeUnitsHttpClientCached(
29 | IAdministrativeUnitsClient countiesClient,
30 | IMemoryCache memoryCache,
31 | Func getCacheEntryOptionsFunc
32 | )
33 | {
34 | _administrativeUnitsClient = countiesClient;
35 | _memoryCache = memoryCache;
36 | _getCacheEntryOptions = getCacheEntryOptionsFunc;
37 | }
38 |
39 | ///
40 | public async Task> GetCounties()
41 | {
42 | var counties = await _memoryCache.GetOrCreateAsync(
43 | COUNTIES_CACHE_KEY,
44 | async cacheEntry =>
45 | {
46 | var cacheEntryOptions = _getCacheEntryOptions.Invoke();
47 | cacheEntry.SetOptions(cacheEntryOptions);
48 |
49 | return await _administrativeUnitsClient.GetCounties();
50 | }
51 | );
52 |
53 | return counties ?? new List();
54 | }
55 |
56 | ///
57 | public async Task> GetMunicipalities()
58 | {
59 | var municipalities = await _memoryCache.GetOrCreateAsync(
60 | MUNICIPALITIES_CACHE_KEY_BASE,
61 | async cacheEntry =>
62 | {
63 | var cacheEntryOptions = _getCacheEntryOptions.Invoke();
64 | cacheEntry.SetOptions(cacheEntryOptions);
65 | var data = await _administrativeUnitsClient.GetMunicipalities();
66 |
67 | if (data is null)
68 | {
69 | cacheEntry.Dispose();
70 | return null;
71 | }
72 |
73 | return data;
74 | }
75 | );
76 |
77 | return municipalities ?? new List();
78 | }
79 |
80 | ///
81 | public async Task> GetMunicipalities(string countyNumber)
82 | {
83 | var counties = await GetCounties();
84 | if (counties.FirstOrDefault(c => c.Number == countyNumber) == null)
85 | {
86 | return new List();
87 | }
88 |
89 | var municipalities = await _memoryCache.GetOrCreateAsync(
90 | $"county-{countyNumber}-{MUNICIPALITIES_CACHE_KEY_BASE}",
91 | async cacheEntry =>
92 | {
93 | var cacheEntryOptions = _getCacheEntryOptions.Invoke();
94 | cacheEntry.SetOptions(cacheEntryOptions);
95 |
96 | var data = await _administrativeUnitsClient.GetMunicipalities(countyNumber);
97 |
98 | if (data is null)
99 | {
100 | cacheEntry.Dispose();
101 | return null;
102 | }
103 |
104 | return data;
105 | }
106 | );
107 |
108 | return municipalities ?? new List();
109 | }
110 |
111 | // Expires the cache entry at midnight, to get potential new or removed entries.
112 | private static MemoryCacheEntryOptions DefaultCacheEntryOptions()
113 | {
114 | DateTime expirationTime = DateTime.Today.AddDays(1);
115 |
116 | return new MemoryCacheEntryOptions()
117 | {
118 | AbsoluteExpiration = expirationTime,
119 | Priority = CacheItemPriority.Normal,
120 | };
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/SSB/Clients/ClassificationsHttpClient.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Net;
3 | using Altinn.Codelists.Extensions;
4 | using Microsoft.Extensions.Options;
5 |
6 | namespace Altinn.Codelists.SSB.Clients;
7 |
8 | ///
9 | /// Http client to get classification codes from SSB.
10 | ///
11 | internal sealed class ClassificationsHttpClient : IClassificationsClient
12 | {
13 | private readonly HttpClient _httpClient;
14 |
15 | ///
16 | /// Initializes a new instance of the class.
17 | ///
18 | public ClassificationsHttpClient(IOptions settings, HttpClient httpClient)
19 | {
20 | _httpClient = httpClient;
21 | _httpClient.BaseAddress = new Uri(settings.Value.BaseApiUrl);
22 | _httpClient.DefaultRequestHeaders.Add("Accept", "application/json;charset=utf-8");
23 | }
24 |
25 | ///
26 | /// Gets the codes for the specified classification.
27 | ///
28 | /// The id of the classification to get
29 | /// The language code used for the labels. Valid options are nb (norsk bokmål), nn (nynorsk) and en (english)
30 | /// Default if nothing is specified is nb (norsk bokmål).
31 | ///
32 | /// The date the classification should be valid
33 | /// The hierarchy level for classifications with multiple levels. Defaults to empty string, ie. all levels.
34 | /// The name of the variant to use instead of the original code list specified.
35 | /// selectCodes is used to limit the result to codes that match the pattern given by selectCodes.
36 | ///
37 | public async Task GetClassificationCodes(
38 | int classificationId,
39 | string language = "nb",
40 | DateOnly? atDate = null,
41 | string level = "",
42 | string variant = "",
43 | string selectCodes = ""
44 | )
45 | {
46 | string selectLanguage = $"language={language}";
47 |
48 | // If no date is specified we use todays date to get the latest classification codes.
49 | DateOnly date = atDate ?? DateOnly.FromDateTime(DateTime.Today);
50 | string selectDate = $"&date={date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}";
51 |
52 | // No level specified means all levels will be returned
53 | string selectLevel = level == string.Empty ? string.Empty : $"&selectLevel={level}";
54 |
55 | // Variants are referenced by name
56 | string selectVariant = variant.IsNullOrEmpty() ? string.Empty : $"&variantName={variant}";
57 |
58 | //SelectCodes
59 | string selectedCodes = selectCodes.IsNullOrEmpty() ? string.Empty : $"&selectCodes={selectCodes}";
60 |
61 | // Start of url differs depending on if we are getting codes or variants
62 | string url = $"{classificationId}/codesAt";
63 | if (!variant.IsNullOrEmpty())
64 | {
65 | url = $"{classificationId}/variantAt";
66 | }
67 | string query = BuildQuery(selectLanguage, selectDate, selectLevel, selectVariant, selectedCodes);
68 |
69 | using var response = await _httpClient.GetAsync($"{url}{query}");
70 |
71 | if (response.IsSuccessStatusCode)
72 | {
73 | var responseJson = await response.Content.ReadAsStringAsync();
74 | var classificationCodes = JsonSerializer.Deserialize(responseJson);
75 | return classificationCodes ?? new ClassificationCodes();
76 | }
77 | // If we get a 404 we try to get the codes in the fallback language (nb)
78 | else if (response.StatusCode == HttpStatusCode.NotFound && language != "nb")
79 | {
80 | string fallbackQuery = BuildQuery("language=nb", selectDate, selectLevel, selectVariant, selectedCodes);
81 | using var fallbackResponse = await _httpClient.GetAsync($"{url}{fallbackQuery}");
82 | if (fallbackResponse.IsSuccessStatusCode)
83 | {
84 | var fallbackResponseJosn = await fallbackResponse.Content.ReadAsStringAsync();
85 | var fallbackClassificationCodes = JsonSerializer.Deserialize(fallbackResponseJosn);
86 | return fallbackClassificationCodes ?? new ClassificationCodes();
87 | }
88 | }
89 |
90 | return new ClassificationCodes();
91 | }
92 |
93 | private static string BuildQuery(
94 | string selectLanguage,
95 | string selectDate,
96 | string selectLevel,
97 | string selectVariant,
98 | string selectCodes
99 | )
100 | {
101 | return $"?{selectLanguage}{selectDate}{selectLevel}{selectVariant}{selectCodes}";
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/Posten/Clients/mappings/wiremock_GET_postnummerregister-ansi.txt.json:
--------------------------------------------------------------------------------
1 | {
2 | "Guid": "a5f1bfa5-b2fa-4293-9350-afaa58a671c4",
3 | "UpdatedAt": "2025-09-19T11:14:40.6499182Z",
4 | "Title": "Proxy Mapping for GET /postnummerregister-ansi.txt",
5 | "Description": "Proxy Mapping for GET /postnummerregister-ansi.txt",
6 | "Priority": -2000000,
7 | "Request": {
8 | "Path": {
9 | "Matchers": [
10 | {
11 | "Name": "WildcardMatcher",
12 | "Pattern": "/postnummerregister-ansi.txt",
13 | "IgnoreCase": false
14 | }
15 | ]
16 | },
17 | "Methods": [
18 | "GET"
19 | ],
20 | "HttpVersion": "1.1",
21 | "Headers": []
22 | },
23 | "Response": {
24 | "StatusCode": 200,
25 | "BodyAsBytes": "MDAwMQlPU0xPCTAzMDEJT1NMTwlQDQowMDEwCU9TTE8JMDMwMQlPU0xPCUINCjAwMTUJT1NMTwkwMzAxCU9TTE8JQg0KMDAxOAlPU0xPCTAzMDEJT1NMTwlTDQowMDIxCU9TTE8JMDMwMQlPU0xPCVANCjAwMjQJT1NMTwkwMzAxCU9TTE8JUA0KMDAyNglPU0xPCTAzMDEJT1NMTwlCDQowMDI4CU9TTE8JMDMwMQlPU0xPCVANCjAwMzAJT1NMTwkwMzAxCU9TTE8JUA0KOTY5MQlIQVbYWVNVTkQJNTYxOAlNxVPYWQlQDQo5NjkyCU3FU9hZCTU2MTgJTcVT2FkJQg0KOTcwMAlMQUtTRUxWCTU2MjIJUE9SU0FOR0VSIFBPUlPBTkdVIFBPUlNBTktJCUcNCjk3MDkJUE9SU0FOR01PRU4JNTYyMglQT1JTQU5HRVIgUE9SU8FOR1UgUE9SU0FOS0kJRw0KOTcxMAlJTkRSRSBCSUxMRUZKT1JECTU2MjIJUE9SU0FOR0VSIFBPUlPBTkdVIFBPUlNBTktJCUcNCjk3MTEJTEFLU0VMVgk1NjIyCVBPUlNBTkdFUiBQT1JTwU5HVSBQT1JTQU5LSQlQDQo5NzEyCUxBS1NFTFYJNTYyMglQT1JTQU5HRVIgUE9SU8FOR1UgUE9SU0FOS0kJUA0KOTcxMwlSVVNTRU5FUwk1NjIyCVBPUlNBTkdFUiBQT1JTwU5HVSBQT1JTQU5LSQlHDQo5NzE0CVNORUZKT1JECTU2MTgJTcVT2FkJRw0KOTcxNQlLT0tFTFYJNTYwMwlIQU1NRVJGRVNUCUcNCjk3MTYJQthSU0VMVgk1NjIyCVBPUlNBTkdFUiBQT1JTwU5HVSBQT1JTQU5LSQlHDQo5NzE3CVZFSURORVMJNTYyNAlMRUJFU0JZCUINCjk3MjIJU0tPR0FOVkFSUkUJNTYyMglQT1JTQU5HRVIgUE9SU8FOR1UgUE9SU0FOS0kJRw0KOTczMAlLQVJBU0pPSwk1NjEwCUtBUkFTSk9IS0EgS0FSQVNKT0sJRw0KOTczMQlLQVJBU0pPSwk1NjEwCUtBUkFTSk9IS0EgS0FSQVNKT0sJRw0KOTczMglLQVJBU0pPSwk1NjEwCUtBUkFTSk9IS0EgS0FSQVNKT0sJRw0KOTczMwlLQVJBU0pPSwk1NjEwCUtBUkFTSk9IS0EgS0FSQVNKT0sJRw0KOTczNAlLQVJBU0pPSwk1NjEwCUtBUkFTSk9IS0EgS0FSQVNKT0sJRw0KOTczNQlLQVJBU0pPSwk1NjEwCUtBUkFTSk9IS0EgS0FSQVNKT0sJUA0KOTczNglLQVJBU0pPSwk1NjEwCUtBUkFTSk9IS0EgS0FSQVNKT0sJRw0KOTczNwlLQVJBU0pPSwk1NjEwCUtBUkFTSk9IS0EgS0FSQVNKT0sJRw0KOTc0MAlMRUJFU0JZCTU2MjQJTEVCRVNCWQlHDQo5NzQyCUtVTkVTCTU2MjQJTEVCRVNCWQlCDQo5NzUwCUhPTk5JTkdTVsVHCTU2MjAJTk9SREtBUFAJRw0KOTc1MQlIT05OSU5HU1bFRwk1NjIwCU5PUkRLQVBQCVANCjk3NjAJTk9SRFbFR0VOCTU2MjAJTk9SREtBUFAJRw0KOTc2MglLQU3YWVbGUgk1NjIwCU5PUkRLQVBQCUcNCjk3NjMJU0tBUlNWxUcJNTYyMAlOT1JES0FQUAlHDQo5NzY0CU5PUkRLQVBQCTU2MjAJTk9SREtBUFAJRw0KOTc2NQlHSkVTVsZSCTU2MjAJTk9SREtBUFAJRw0KOTc2NglTQVJORVMJNTYyMAlOT1JES0FQUAlHDQo5NzY4CVJFUFbFRwk1NjIwCU5PUkRLQVBQCUcNCjk3NzAJTUVIQU1OCTU2MjYJR0FNVklLCUINCjk3NzEJU0tKxU5FUwk1NjI2CUdBTVZJSwlHDQo5NzcyCUxBTkdGSk9SRE5FUwk1NjI2CUdBTVZJSwlHDQo5NzczCU5FUlZFSQk1NjI2CUdBTVZJSwlCDQo5Nzc1CUdBTVZJSwk1NjI2CUdBTVZJSwlCDQo5NzgyCURZRkpPUkQJNTYyNAlMRUJFU0JZCUcNCjk3OTAJS0rYTExFRkpPUkQJNTYyNAlMRUJFU0JZCUINCjk4MDAJVkFEU9gJNTYwNwlWQURT2AlHDQo5ODAxCVZBRFPYCTU2MDcJVkFEU9gJRw0KOTgwMglWRVNUUkUgSkFLT0JTRUxWCTU2MDcJVkFEU9gJRw0KOTgwMwlWQURT2Ak1NjA3CVZBRFPYCUcNCjk4MDQJVkFEU9gJNTYwNwlWQURT2AlHDQo5ODEwCVZFU1RSRSBKQUtPQlNFTFYJNTYwNwlWQURT2AlQDQo5ODExCVZBRFPYCTU2MDcJVkFEU9gJUA0KOTgxNQlWQURT2Ak1NjA3CVZBRFPYCVANCjk4MjAJVkFSQU5HRVJCT1ROCTU2MzYJVU5KQVJHR0EgTkVTU0VCWQlQDQo5ODI2CVNJUk1BCTU2MjgJREVBVE5VIFRBTkEJRw0KOTg0MAlWQVJBTkdFUkJPVE4JNTYzNglVTkpBUkdHQSBORVNTRUJZCUcNCjk4NDEJVEFOQQk1NjI4CURFQVROVSBUQU5BCUcNCjk4NDIJVEFOQQk1NjI4CURFQVROVSBUQU5BCUcNCjk4NDMJVEFOQQk1NjI4CURFQVROVSBUQU5BCUcNCjk4NDQJVEFOQQk1NjI4CURFQVROVSBUQU5BCUcNCjk4NDUJVEFOQQk1NjI4CURFQVROVSBUQU5BCUcNCjk4NDYJVEFOQQk1NjI4CURFQVROVSBUQU5BCVANCjk5MDAJS0lSS0VORVMJNTYwNQlT2FItVkFSQU5HRVIJRw0KOTkwMQlLSVJLRU5FUwk1NjA1CVPYUi1WQVJBTkdFUglHDQo5OTEwCUJK2FJORVZBVE4JNTYwNQlT2FItVkFSQU5HRVIJRw0KOTkxMQlKQVJGSk9SRAk1NjA1CVPYUi1WQVJBTkdFUglHDQo5OTEyCUhFU1NFTkcJNTYwNQlT2FItVkFSQU5HRVIJRw0KOTkxNAlCSthSTkVWQVROCTU2MDUJU9hSLVZBUkFOR0VSCVANCjk5MTUJS0lSS0VORVMJNTYwNQlT2FItVkFSQU5HRVIJUA0KOTkxNglIRVNTRU5HCTU2MDUJU9hSLVZBUkFOR0VSCVANCjk5MTcJS0lSS0VORVMJNTYwNQlT2FItVkFSQU5HRVIJUA0KOTkyNQlTVkFOVklLCTU2MDUJU9hSLVZBUkFOR0VSCUcNCjk5MjYJUEFTVklLCTU2MDUJU9hSLVZBUkFOR0VSCUcNCjk5MzAJTkVJREVOCTU2MDUJU9hSLVZBUkFOR0VSCUcNCjk5MzUJQlVH2FlORVMJNTYwNQlT2FItVkFSQU5HRVIJQg0KOTk1MAlWQVJE2Ak1NjM0CVZBUkTYCUcNCjk5NTEJVkFSRNgJNTYzNAlWQVJE2AlQDQo5OTUyCVZBUkTYCTU2MzQJVkFSRNgJRw0KOTk1NQlTVkFSVE5FUwk1NjM0CVZBUkTYCUcNCjk5NjAJS0lCRVJHCTU2MzQJVkFSRNgJRw0KOTk4MAlCRVJMRVbFRwk1NjMwCUJFUkxFVsVHCUcNCjk5ODEJQkVSTEVWxUcJNTYzMAlCRVJMRVbFRwlQDQo5OTgyCUtPTkdTRkpPUkQJNTYzMAlCRVJMRVbFRwlHDQo5OTkwCULFVFNGSk9SRAk1NjMyCULFVFNGSk9SRAlHDQo5OTkxCULFVFNGSk9SRAk1NjMyCULFVFNGSk9SRAlQDQo=",
26 | "Headers": {
27 | "Content-Length": "2897",
28 | "Content-Type": "text/plain; charset=iso-8859-1",
29 | "Connection": "keep-alive",
30 | "Server": "Apache",
31 | "X-XSS-Protection": "1; mode=block",
32 | "cache-status": "Booster; fwd=miss",
33 | "X-Frame-Options": "DENY",
34 | "x-xp-app": "no.posten.mapextended",
35 | "X-Content-Type-Options": "nosniff",
36 | "Via": [
37 | "1.1 google",
38 | "1.1 varnish"
39 | ],
40 | "Accept-Ranges": "bytes",
41 | "Date": "Fri, 19 Sep 2025 11:14:40 GMT",
42 | "X-Served-By": "cache-osl6526-OSL, cache-osl6531-OSL",
43 | "X-Cache": "MISS, MISS",
44 | "X-Cache-Hits": "0, 0",
45 | "X-Timer": "S1758280481.521525,VS0,VE104",
46 | "Vary": "Accept-Encoding",
47 | "Strict-Transport-Security": "max-age=31557600",
48 | "Alt-Svc": [
49 | "h3=\":443\"",
50 | "h3-29=\":443\"",
51 | "h3-27=\":443\""
52 | ]
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/Mocks/ClassificationsHttpClientMock.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Codelists.SSB;
2 | using Altinn.Codelists.SSB.Clients;
3 | using RichardSzalay.MockHttp;
4 |
5 | namespace Altinn.Codelists.Tests.SSB.Mocks;
6 |
7 | public class ClassificationsHttpClientMock : IClassificationsClient
8 | {
9 | private const string SEX_TESTDATA_RESOURCE = "Altinn.Codelists.Tests.SSB.Testdata.sex.json";
10 | private const string INDUSTRY_GROUPING_TESTDATA_RESOURCE =
11 | "Altinn.Codelists.Tests.SSB.Testdata.industryGrouping.json";
12 | private const string OCCUPATIONS_TESTDATA_RESOURCE = "Altinn.Codelists.Tests.SSB.Testdata.occupations.json";
13 | private const string MARITAL_STATUS_TESTDATA_RESOURCE = "Altinn.Codelists.Tests.SSB.Testdata.maritalStatus.json";
14 | private const string BASE_AMOUT_NATIONAL_INSURANCE_TESTDATA_RESOURCE =
15 | "Altinn.Codelists.Tests.SSB.Testdata.baseAmountNationalInsurance.json";
16 | private const string COUNTIES_TESTDATA_RESOURCE = "Altinn.Codelists.Tests.SSB.Testdata.counties.json";
17 | private const string MUNICIPALITIES_TESTDATA_RESOURCE = "Altinn.Codelists.Tests.SSB.Testdata.municipalities.json";
18 | private const string COUNTRIES_TESTDATA_RESOURCE = "Altinn.Codelists.Tests.SSB.Testdata.countries.json";
19 | private const string SMALL_GAME_VARIANT_TESTDATA_RESOURCE =
20 | "Altinn.Codelists.Tests.SSB.Testdata.smallGame_Variant.json";
21 | private const string UNITS_TESTDATA_RESOURCE = "Altinn.Codelists.Tests.SSB.Testdata.units.json";
22 |
23 | private readonly IClassificationsClient _client;
24 | private readonly IOptions _options;
25 |
26 | public MockHttpMessageHandler HttpMessageHandlerMock { get; private set; }
27 | public MockedRequest MockedClassificationsRequest { get; private set; }
28 | public MockedRequest MockedMaritalStatusRequest { get; private set; }
29 |
30 | public ClassificationsHttpClientMock(IOptions classificationOptions)
31 | {
32 | _options = classificationOptions;
33 |
34 | HttpMessageHandlerMock = new MockHttpMessageHandler();
35 |
36 | HttpMessageHandlerMock
37 | .When("https://data.ssb.no/api/klass/v1/classifications/2/*")
38 | .Respond("application/json", EmbeddedResource.LoadDataAsString(SEX_TESTDATA_RESOURCE).Result);
39 |
40 | HttpMessageHandlerMock
41 | .When("https://data.ssb.no/api/klass/v1/classifications/6/*")
42 | .Respond("application/json", EmbeddedResource.LoadDataAsString(INDUSTRY_GROUPING_TESTDATA_RESOURCE).Result);
43 |
44 | HttpMessageHandlerMock
45 | .When("https://data.ssb.no/api/klass/v1/classifications/7/*")
46 | .Respond("application/json", EmbeddedResource.LoadDataAsString(OCCUPATIONS_TESTDATA_RESOURCE).Result);
47 |
48 | MockedMaritalStatusRequest = HttpMessageHandlerMock
49 | .When("https://data.ssb.no/api/klass/v1/classifications/19/*")
50 | .Respond("application/json", EmbeddedResource.LoadDataAsString(MARITAL_STATUS_TESTDATA_RESOURCE).Result);
51 |
52 | MockedClassificationsRequest = HttpMessageHandlerMock
53 | .When("https://data.ssb.no/api/klass/v1/classifications/20/*")
54 | .Respond(
55 | "application/json",
56 | EmbeddedResource.LoadDataAsString(BASE_AMOUT_NATIONAL_INSURANCE_TESTDATA_RESOURCE).Result
57 | );
58 |
59 | HttpMessageHandlerMock
60 | .When("https://data.ssb.no/api/klass/v1/classifications/104/*")
61 | .Respond("application/json", EmbeddedResource.LoadDataAsString(COUNTIES_TESTDATA_RESOURCE).Result);
62 | HttpMessageHandlerMock
63 | .When("https://data.ssb.no/api/klass/v1/classifications/131/*")
64 | .Respond("application/json", EmbeddedResource.LoadDataAsString(MUNICIPALITIES_TESTDATA_RESOURCE).Result);
65 |
66 | HttpMessageHandlerMock
67 | .When("https://data.ssb.no/api/klass/v1/classifications/552/*")
68 | .Respond("application/json", EmbeddedResource.LoadDataAsString(COUNTRIES_TESTDATA_RESOURCE).Result);
69 |
70 | HttpMessageHandlerMock
71 | .When("https://data.ssb.no/api/klass/v1/classifications/74/variantAt*")
72 | .Respond(
73 | "application/json",
74 | EmbeddedResource.LoadDataAsString(SMALL_GAME_VARIANT_TESTDATA_RESOURCE).Result
75 | );
76 |
77 | HttpMessageHandlerMock
78 | .When("https://data.ssb.no/api/klass/v1/classifications/303/*")
79 | .Respond("application/json", EmbeddedResource.LoadDataAsString(UNITS_TESTDATA_RESOURCE).Result);
80 |
81 | _client = new ClassificationsHttpClient(_options, new HttpClient(HttpMessageHandlerMock));
82 | }
83 |
84 | public async Task GetClassificationCodes(
85 | int classificationId,
86 | string language = "nb",
87 | DateOnly? atDate = null,
88 | string level = "",
89 | string variant = "",
90 | string selectCodes = ""
91 | )
92 | {
93 | ClassificationCodes classificationCodes = await _client.GetClassificationCodes(
94 | classificationId,
95 | language,
96 | atDate,
97 | level,
98 | variant,
99 | selectCodes
100 | );
101 |
102 | return level == string.Empty
103 | ? classificationCodes
104 | : new ClassificationCodes()
105 | {
106 | Codes = classificationCodes.Codes.Where(x => x.Level == level.ToString()).ToList(),
107 | };
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
352 | *.received.txt
353 | *.received.json
354 |
--------------------------------------------------------------------------------
/src/Altinn.Codelists/SSB/Extensions/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Altinn.App.Core.Features;
2 | using Altinn.Codelists.SSB.Clients;
3 | using Altinn.Codelists.SSB.Models;
4 | using Microsoft.Extensions.Caching.Memory;
5 | using Microsoft.Extensions.DependencyInjection;
6 |
7 | namespace Altinn.Codelists.SSB.Extensions;
8 |
9 | ///
10 | /// Extends the .
11 | ///
12 | public static class ServiceCollectionExtensions
13 | {
14 | ///
15 | /// Registers the services required to get support for SSB Classifiations.
16 | ///
17 | public static IServiceCollection AddSSBClassifications(this IServiceCollection services)
18 | {
19 | EnsureBasicServicesRegistered(services);
20 |
21 | // Add the codelist providers
22 | services.AddSSBClassificationCodelistProvider("kjønn", Classification.Sex);
23 | services.AddSSBClassificationCodelistProvider("næringsgruppering", Classification.IndustryGrouping);
24 | services.AddSSBClassificationCodelistProvider("yrker", Classification.Occupations);
25 | services.AddSSBClassificationCodelistProvider("sivilstand", Classification.MaritalStatus);
26 | services.AddSSBClassificationCodelistProvider(
27 | "grunnbeløpfolketrygden",
28 | Classification.BaseAmountNationalInsurance
29 | );
30 | services.AddSSBClassificationCodelistProvider("fylker", Classification.Counties);
31 | services.AddSSBClassificationCodelistProvider("kommuner", Classification.Municipalities);
32 | services.AddSSBClassificationCodelistProvider("land", Classification.Countries);
33 |
34 | return services;
35 | }
36 |
37 | // Services added here should be safe to add multiple times
38 | // either resulting in singleton or replacing the existing service
39 | private static void EnsureBasicServicesRegistered(IServiceCollection services)
40 | {
41 | services.AddMemoryCache();
42 | services.AddOptions();
43 |
44 | if (!services.Any(x => x.ServiceType == typeof(IClassificationsClient)))
45 | {
46 | services.AddHttpClient();
47 | services.AddTransient(sp => new ClassificationsHttpClientCached(
48 | ActivatorUtilities.CreateInstance(sp),
49 | sp.GetRequiredService()
50 | ));
51 | }
52 | }
53 |
54 | ///
55 | /// Adds the specified as an with the specified id.
56 | ///
57 | /// The to add to
58 | /// The codelist id
59 | /// The to return
60 | /// Default set of key/value pairs to be used. Will be overriden by matching qyery parameters runtime.
61 | #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
62 | public static IServiceCollection AddSSBClassificationCodelistProvider(
63 | this IServiceCollection services,
64 | string id,
65 | Classification classification,
66 | Dictionary? defaultKeyValuePairs = null
67 | )
68 | {
69 | EnsureBasicServicesRegistered(services);
70 | services.AddTransient(sp => new ClassificationCodelistProvider(
71 | id,
72 | classification,
73 | sp.GetRequiredService(),
74 | defaultKeyValuePairs
75 | ));
76 |
77 | return services;
78 | }
79 |
80 | ///
81 | /// Adds the specified classification based on the known classification id. If it is an id mapped to the enum
82 | /// the correct enum will be set, otherwise Custom will be used as enum value but the id will be sent to the underlying api.
83 | ///
84 | /// The to add to
85 | /// The codelist id
86 | /// The to return
87 | /// allowing control over how data maps from the source to the app options
88 | /// Default set of key/value pairs to be used. Will be overriden by matching qyery parameters runtime.
89 | public static IServiceCollection AddSSBClassificationCodelistProvider(
90 | this IServiceCollection services,
91 | string id,
92 | Classification classification,
93 | ClassificationOptions options,
94 | Dictionary? defaultKeyValuePairs = null
95 | )
96 | {
97 | EnsureBasicServicesRegistered(services);
98 | services.AddTransient(sp => new ClassificationCodelistProvider(
99 | id,
100 | classification,
101 | sp.GetRequiredService(),
102 | options,
103 | defaultKeyValuePairs
104 | ));
105 |
106 | return services;
107 | }
108 |
109 | ///
110 | /// Adds the specified classification based on the known classification id. If it is an id mapped to the enum
111 | /// the correct enum will be set, otherwise Custom will be used as enum value but the id will be sent to the underlying api.
112 | ///
113 | /// The to add to
114 | /// The codelist id
115 | /// The id of the classification to return
116 | /// Default set of key/value pairs to be used. Will be overriden by matching qyery parameters runtime.
117 | public static IServiceCollection AddSSBClassificationCodelistProvider(
118 | this IServiceCollection services,
119 | string id,
120 | int classificationId,
121 | Dictionary? defaultKeyValuePairs = null
122 | )
123 | {
124 | EnsureBasicServicesRegistered(services);
125 | services.AddTransient(sp => new ClassificationCodelistProvider(
126 | id,
127 | classificationId,
128 | sp.GetRequiredService(),
129 | defaultKeyValuePairs
130 | ));
131 |
132 | return services;
133 | }
134 |
135 | ///
136 | /// Adds the specified classification based on the known classification id. If it is an id mapped to the enum
137 | /// the correct enum will be set, otherwise Custom will be used as enum value but the id will be sent to the underlying api.
138 | ///
139 | /// The to add to
140 | /// The codelist id
141 | /// The id of the classification to return
142 | /// allowing control over how data maps from the source to the app options
143 | /// Default set of key/value pairs to be used. Will be overriden by matching qyery parameters runtime.
144 | public static IServiceCollection AddSSBClassificationCodelistProvider(
145 | #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
146 |
147 | this IServiceCollection services,
148 | string id,
149 | int classificationId,
150 | ClassificationOptions options,
151 | Dictionary? defaultKeyValuePairs = null
152 | )
153 | {
154 | EnsureBasicServicesRegistered(services);
155 | services.AddTransient(sp => new ClassificationCodelistProvider(
156 | id,
157 | classificationId,
158 | sp.GetRequiredService(),
159 | defaultKeyValuePairs,
160 | options
161 | ));
162 |
163 | return services;
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | # Non-configurable behaviors
3 | charset = utf-8
4 | insert_final_newline = true
5 | trim_trailing_whitespace = true
6 |
7 | # Configurable behaviors
8 | # end_of_line = lf - there is no 'auto' with a .editorconfig
9 | indent_style = space
10 | indent_size = 4
11 | max_line_length = 120
12 |
13 | # XML project files
14 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
15 | indent_size = 2
16 |
17 | # XML config files
18 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
19 | indent_size = 2
20 |
21 | [*.{cs,vb}]
22 | dotnet_sort_system_directives_first = true
23 | dotnet_separate_import_directive_groups = false
24 |
25 | end_of_line = crlf
26 |
27 | #### Naming styles ####
28 |
29 | # Interfaces start with 'I'
30 | dotnet_naming_symbols.interface.applicable_kinds = interface
31 | dotnet_naming_style.begins_with_i.required_prefix = I
32 | dotnet_naming_style.begins_with_i.required_suffix =
33 | dotnet_naming_style.begins_with_i.word_separator =
34 | dotnet_naming_style.begins_with_i.capitalization = pascal_case
35 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning
36 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
37 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
38 |
39 | # Types should be PascalCase
40 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum, namespace, delegate
41 | dotnet_naming_rule.types_should_be_pascal_case.severity = warning
42 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types
43 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
44 |
45 | # Non-private members should be PascalCase
46 | dotnet_naming_symbols.non_private_members.applicable_kinds = property, event, field
47 | dotnet_naming_symbols.non_private_members.applicable_accessibilities = public, internal, protected, protected_internal
48 | dotnet_naming_rule.non_private_members_should_be_pascal_case.severity = warning
49 | dotnet_naming_rule.non_private_members_should_be_pascal_case.symbols = non_private_members
50 | dotnet_naming_rule.non_private_members_should_be_pascal_case.style = pascal_case
51 |
52 | # All methods should be PascalCase
53 | dotnet_naming_symbols.methods.applicable_kinds = method, local_function
54 | dotnet_naming_rule.methods_should_be_pascal_case.severity = warning
55 | dotnet_naming_rule.methods_should_be_pascal_case.symbols = methods
56 | dotnet_naming_rule.methods_should_be_pascal_case.style = pascal_case
57 |
58 | # Private member should be '_' prefixed and camelCase
59 | dotnet_naming_symbols.private_members.applicable_kinds = property, event, field
60 | dotnet_naming_symbols.private_members.applicable_accessibilities = private, private_protected
61 | dotnet_naming_style.__prefixed_camel_case.required_prefix = _
62 | dotnet_naming_style.__prefixed_camel_case.required_suffix =
63 | dotnet_naming_style.__prefixed_camel_case.word_separator =
64 | dotnet_naming_style.__prefixed_camel_case.capitalization = camel_case
65 | dotnet_naming_rule.private_members_should_be___prefixed_camel_case.severity = warning
66 | dotnet_naming_rule.private_members_should_be___prefixed_camel_case.symbols = private_members
67 | dotnet_naming_rule.private_members_should_be___prefixed_camel_case.style = __prefixed_camel_case
68 |
69 | # Const fields should be PascalCase
70 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = warning
71 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
72 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case
73 | dotnet_naming_symbols.constant_fields.applicable_kinds = field
74 | dotnet_naming_symbols.constant_fields.required_modifiers = const
75 |
76 |
77 | # General naming styles
78 |
79 | dotnet_naming_style.pascal_case.required_prefix =
80 | dotnet_naming_style.pascal_case.required_suffix =
81 | dotnet_naming_style.pascal_case.word_separator =
82 | dotnet_naming_style.pascal_case.capitalization = pascal_case
83 |
84 | dotnet_style_coalesce_expression = true:suggestion
85 | dotnet_style_null_propagation = true:suggestion
86 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
87 | dotnet_style_prefer_auto_properties = true:silent
88 | dotnet_style_object_initializer = true:suggestion
89 | dotnet_style_collection_initializer = true:suggestion
90 | dotnet_style_operator_placement_when_wrapping = beginning_of_line
91 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
92 |
93 | csharp_using_directive_placement = outside_namespace:silent
94 | csharp_prefer_simple_using_statement = true:suggestion
95 | csharp_prefer_braces = true:silent
96 | csharp_style_prefer_method_group_conversion = true:silent
97 | csharp_style_prefer_top_level_statements = true:silent
98 | csharp_style_expression_bodied_methods = false:silent
99 | csharp_style_expression_bodied_constructors = false:silent
100 | csharp_style_expression_bodied_operators = false:silent
101 | csharp_style_expression_bodied_properties = true:silent
102 | csharp_style_expression_bodied_indexers = true:silent
103 | csharp_style_expression_bodied_accessors = true:silent
104 | csharp_style_expression_bodied_lambdas = true:silent
105 | csharp_style_expression_bodied_local_functions = false:silent
106 | csharp_indent_labels = one_less_than_current
107 | csharp_style_prefer_primary_constructors = false:suggestion
108 | resharper_convert_to_primary_constructor_highlighting = none
109 | csharp_style_namespace_declarations = file_scoped:error
110 |
111 | # Naming rule violation
112 | dotnet_diagnostic.IDE1006.severity = error
113 |
114 | # Unused usings
115 | dotnet_diagnostic.IDE0005.severity = warning
116 |
117 | dotnet_diagnostic.CA1825.severity = warning
118 |
119 | # IDE0052: Remove unread private member
120 | dotnet_diagnostic.IDE0052.severity = warning
121 |
122 | # CA1848: Use the LoggerMessage delegates
123 | dotnet_diagnostic.CA1848.severity = none
124 |
125 | # CA1727: Use PascalCase for named placeholders
126 | dotnet_diagnostic.CA1727.severity = suggestion
127 |
128 | # CA2254: Template should be a static expression
129 | dotnet_diagnostic.CA2254.severity = none
130 |
131 | # CA1822: Mark members as static
132 | dotnet_diagnostic.CA1822.severity = suggestion
133 |
134 | # IDE0080: Remove unnecessary suppression operator
135 | dotnet_diagnostic.IDE0080.severity = error
136 |
137 | # CA1859: Use concrete types when possible for improved performance
138 | dotnet_diagnostic.CA1859.severity = suggestion
139 |
140 | # CA1716: Rename namespace "" so that it no longer conflicts with the reserved language keyword 'Interface'
141 | # TODO: fixing this would be breaking
142 | dotnet_diagnostic.CA1716.severity = suggestion
143 |
144 | # CA1805: Do not initialize unnecessarily
145 | dotnet_diagnostic.CA1805.severity = suggestion
146 |
147 | # CA1711: Identifiers should not have incorrect suffix
148 | # TODO: fixing this would be breaking
149 | dotnet_diagnostic.CA1711.severity = suggestion
150 |
151 | # CA2201: Do not raise reserved exception types
152 | dotnet_diagnostic.CA2201.severity = suggestion
153 |
154 | # CA1720: Identifier contains type name
155 | # TODO: fixing this would be breaking
156 | dotnet_diagnostic.CA1720.severity = suggestion
157 |
158 | # CA1816: Call GC.SuppressFinalize correctly
159 | dotnet_diagnostic.CA1816.severity = warning
160 |
161 | # CA1707: Identifiers should not contain underscores
162 | dotnet_diagnostic.CA1707.severity = none
163 |
164 | # S2325: Methods and properties that don't access instance data should be static
165 | dotnet_diagnostic.S2325.severity = suggestion
166 |
167 | # S3267: Loops should be simplified with "LINQ" expressions
168 | dotnet_diagnostic.S3267.severity = suggestion
169 |
170 | # S125: Sections of code should not be commented out
171 | dotnet_diagnostic.S125.severity = suggestion
172 |
173 | # S1075: URIs should not be hardcoded
174 | dotnet_diagnostic.S1075.severity = suggestion
175 |
176 | [Program.cs]
177 | dotnet_diagnostic.CA1050.severity = none
178 | dotnet_diagnostic.S1118.severity = none
179 |
180 | [*.{yml,yaml}]
181 | indent_size = 2
182 | end_of_line = lf
183 |
184 | # Verify settings
185 | # https://github.com/VerifyTests/Verify?tab=readme-ov-file#editorconfig-settings
186 |
187 | [*.{received,verified}.{json,txt,xml}]
188 | charset = "utf-8-bom"
189 | end_of_line = lf
190 | indent_size = unset
191 | indent_style = unset
192 | insert_final_newline = false
193 | tab_width = unset
194 | trim_trailing_whitespace = false
195 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Altinn Common Codelists
2 | 
3 | 
4 | 
5 |
6 | This library contains common code lists for use in Altinn 3 based applications.
7 |
8 | The [Getting started](#getting-started) section describes the basic steps required in order to use this package within an Altinn 3 application. For a more comprehensive description of code lists in Altinn 3 please see https://docs.altinn.studio/app/development/data/options/.
9 |
10 |
11 |
12 | ## Getting started using the package
13 |
14 | This guide assumes you have an existing Altinn 3 application. If not please see https://docs.altinn.studio/app/getting-started/create-app/ to get started.
15 |
16 | ### 1. Add reference to [Altinn.Codelists nuget package](https://www.nuget.org/packages/Altinn.Codelists)
17 | Open command line to the repo of your application and navigate to the App folder where the App.csproj file is located and run the following command:
18 |
19 | ```shell
20 | dotnet add package Altinn.Codelists
21 | ```
22 | This will add the latest stable version of the package to your solution.
23 |
24 | As an alternative you can edit your applications App.csproj file directly by adding the reference below to the `` where you have package references.
25 | ```xml
26 |
27 | ```
28 | Note that you then need to explicitly specify the version you would like. See the link on step one for available versions.
29 |
30 | ### 2. Register the codelists in your app DI container
31 | Add the following to your Program.cs file:
32 | ```csharp
33 | services.AddAltinnCodelists();
34 | ```
35 | By calling this you will register all codelists accross all sources listed below in available codelists. You can also register codelists one by one if you for example would like to provide your own codelist id, or if you would like to control the mappings to description and help texts.
36 |
37 | ### 3. Connect your application to the codelist you would like to use
38 | See the section below for available codelist id's.
39 |
40 | You can either do this using [Altinn Studio](https://altinn.studio) and configure the *Kodeliste-ID* of your component in the UI.
41 |
42 | Or you can configure the component by editing the optionsId property in FormLayout.json according to the [documentation](https://docs.altinn.studio/app/development/data/options/#connect-the-component-to-options-code-list)
43 |
44 | ## Custom configuration
45 | While the above mentioned configuration where you call `services.AddAltinnCodelists();` will add all available codelists with default values, there are cases where you might want to customize the configuration. The examples will vary a bit depending on the source of the codelist.
46 |
47 | ### Add a codelist with custom codelist id
48 | If you don't want to use the default codelist id, or only want to register codelists relevant for you app you can register each codelist individually.
49 |
50 | Example using codelist from SSB overriding the option id:
51 | ```csharp
52 | services.AddSSBClassificationCodelistProvider("næring", Classification.IndustryGrouping);
53 | ```
54 |
55 | ### Add a codelist with default parameters
56 | Some of the codelists accepts parameters controlling what's returned.
57 |
58 | Example using the codelist from SSB specifiying a level filter to only get values from the first level (this particular codelist is hierarchical).
59 |
60 | ```csharp
61 | services.AddSSBClassificationCodelistProvider("næring", Classification.IndustryGrouping, new Dictionary() { { "level", "1" } });
62 | ```
63 | The default parameters is a name/value pair collection allowing for any parameter to be passed in and picked up by the implementation of the codelist provider.
64 |
65 | ### Add a codelist that has support for description and/or help text values
66 | While a regular codelist is only key/value pairs, you can extend this with adding description and help text allowing for a more descriptive UI.
67 |
68 | The following example enables the notes field from SSB classification to populate the description text.
69 |
70 | ```csharp
71 | services.AddSSBClassificationCodelistProvider("næring", Classification.IndustryGrouping,
72 | new ClassificationOptions() { MapNotesToDescription = true },
73 | new Dictionary() { { "level", "1" } });
74 | ```
75 | The above example enables a predefined way of adding a description text. If you would like to customize the description text even further you can pass inn a function.
76 | The follwing examples passes in a function that that will be evaluated when populating the code list and will return a combination of the classification code and the notes fields separated by colon.
77 |
78 | ```csharp
79 | services.AddSSBClassificationCodelistProvider(
80 | "næring",
81 | Classification.IndustryGrouping,
82 | new ClassificationOptions()
83 | {
84 | MapDescriptionFunc = (classificationCode) => $"{classificationCode.Code}: {classificationCode.Notes}"
85 | },
86 | new Dictionary() { { "level", "1" } });
87 | ```
88 |
89 | ### Add a codelist from SSB not available in the `Classification` enum.
90 | Currently only a small subset of the available codelist from SSB is included in the `Classification` enum. The enum is really only provided as a more readable version of the underlying id provided by SSB. But in our case also serves as a way of telling what codelists we have tested explicitly against. If you find a codelist you would like to use, you can specify it's id instead of the enum.
91 |
92 | ```csharp
93 | services.AddSSBClassificationCodelistProvider("næring", 6);
94 | ```
95 |
96 |
97 | ## Available codelists
98 | The list below shows currently implemented code lists with their default id.
99 |
100 | | Default Codelist Id | Source | Description |
101 | |------------------------- | ------------ | --------------------------------------------------------- |
102 | | fylker | SSB | The counties of Norway |
103 | | fylker-kv | Kartverket | The counties of Norway |
104 | | grunnbeløpfolketrygden | SSB | National insurance base amount |
105 | | kjønn | SSB | Sex |
106 | | kommuner | SSB | The communes of Norway (all) |
107 | | kommuner-kv | Kartverket | The communes of Norway with ability to filter on county |
108 | | land | SSB | The countries of the world |
109 | | næringsgruppering | SSB | Industrical grouping |
110 | | poststed | Posten | Norwegian postal codes |
111 | | sivilstand | SSB | Marital status |
112 | | yrker | SSB | Occupations |
113 |
114 |
115 |
116 | ## Sources
117 | Below are the sources used for the various codelists above. The underlying api's provide different functionality with regards to query parameters. Espessialy the SSB api's provide a rich set of parameters allowing the query for valid values on a given date or date range.
118 |
119 | ### [Statistisk Sentralbyrå/SSB](https://www.ssb.no/)
120 | Doc: https://data.ssb.no/api/klass/v1/api-guide.html
121 |
122 | Licence: https://data.ssb.no/api/klass/v1/api-guide.html#_license
123 |
124 | ### [Kartverket/KV](https://www.kartverket.no/)
125 | Doc: https://kartkatalog.geonorge.no/metadata/administrative-enheter-kommuner/041f1e6e-bdbc-4091-b48f-8a5990f3cc5b
126 |
127 | Api: https://ws.geonorge.no/kommuneinfo/v1/
128 |
129 | Licence: https://www.kartverket.no/api-og-data/vilkar-for-bruk
130 |
131 | ### [Posten](https://www.bring.no)
132 | Doc: https://www.bring.no/tjenester/adressetjenester/postnummer/postnummertabeller-veiledning
133 |
134 | ## Contributing
135 | Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
136 | ## Authors
137 | Altinn Apps development team - If you want to get in touch, just create a new issue.
138 | See also the list of [contributors](https://github.com/Altinn/codelists-lib-dotnet/graphs/contributors) who participated in this project.
139 | ## License
140 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details.
141 |
--------------------------------------------------------------------------------
/test/Altinn.Codelists.Tests/SSB/Testdata/baseAmountNationalInsurance.json:
--------------------------------------------------------------------------------
1 | {
2 | "codes": [
3 | {
4 | "code": "1991",
5 | "parentCode": null,
6 | "level": "1",
7 | "name": "35 500 kroner",
8 | "shortName": "",
9 | "presentationName": "",
10 | "validFrom": null,
11 | "validTo": null,
12 | "notes": ""
13 | },
14 | {
15 | "code": "1992",
16 | "parentCode": null,
17 | "level": "1",
18 | "name": "36 500 kroner",
19 | "shortName": "",
20 | "presentationName": "",
21 | "validFrom": null,
22 | "validTo": null,
23 | "notes": ""
24 | },
25 | {
26 | "code": "1993",
27 | "parentCode": null,
28 | "level": "1",
29 | "name": "37 300 kroner",
30 | "shortName": "",
31 | "presentationName": "",
32 | "validFrom": null,
33 | "validTo": null,
34 | "notes": ""
35 | },
36 | {
37 | "code": "1994",
38 | "parentCode": null,
39 | "level": "1",
40 | "name": "38 080 kroner",
41 | "shortName": "",
42 | "presentationName": "",
43 | "validFrom": null,
44 | "validTo": null,
45 | "notes": ""
46 | },
47 | {
48 | "code": "1995",
49 | "parentCode": null,
50 | "level": "1",
51 | "name": "39 230 kroner",
52 | "shortName": "",
53 | "presentationName": "",
54 | "validFrom": null,
55 | "validTo": null,
56 | "notes": ""
57 | },
58 | {
59 | "code": "1996",
60 | "parentCode": null,
61 | "level": "1",
62 | "name": "41 000 kroner",
63 | "shortName": "",
64 | "presentationName": "",
65 | "validFrom": null,
66 | "validTo": null,
67 | "notes": ""
68 | },
69 | {
70 | "code": "1997",
71 | "parentCode": null,
72 | "level": "1",
73 | "name": "42 500 kroner",
74 | "shortName": "",
75 | "presentationName": "",
76 | "validFrom": null,
77 | "validTo": null,
78 | "notes": ""
79 | },
80 | {
81 | "code": "1998",
82 | "parentCode": null,
83 | "level": "1",
84 | "name": "45 370 kroner",
85 | "shortName": "",
86 | "presentationName": "",
87 | "validFrom": null,
88 | "validTo": null,
89 | "notes": ""
90 | },
91 | {
92 | "code": "1999",
93 | "parentCode": null,
94 | "level": "1",
95 | "name": "46 950 kroner",
96 | "shortName": "",
97 | "presentationName": "",
98 | "validFrom": null,
99 | "validTo": null,
100 | "notes": ""
101 | },
102 | {
103 | "code": "2000",
104 | "parentCode": null,
105 | "level": "1",
106 | "name": "49 090 kroner",
107 | "shortName": "",
108 | "presentationName": "",
109 | "validFrom": null,
110 | "validTo": null,
111 | "notes": ""
112 | },
113 | {
114 | "code": "2001",
115 | "parentCode": null,
116 | "level": "1",
117 | "name": "51 360 kroner",
118 | "shortName": "",
119 | "presentationName": "",
120 | "validFrom": null,
121 | "validTo": null,
122 | "notes": ""
123 | },
124 | {
125 | "code": "2002",
126 | "parentCode": null,
127 | "level": "1",
128 | "name": "54 170 kroner",
129 | "shortName": "",
130 | "presentationName": "",
131 | "validFrom": null,
132 | "validTo": null,
133 | "notes": ""
134 | },
135 | {
136 | "code": "2003",
137 | "parentCode": null,
138 | "level": "1",
139 | "name": "56 861 kroner",
140 | "shortName": "",
141 | "presentationName": "",
142 | "validFrom": null,
143 | "validTo": null,
144 | "notes": ""
145 | },
146 | {
147 | "code": "2004",
148 | "parentCode": null,
149 | "level": "1",
150 | "name": "58 778 kroner",
151 | "shortName": "",
152 | "presentationName": "",
153 | "validFrom": null,
154 | "validTo": null,
155 | "notes": ""
156 | },
157 | {
158 | "code": "2005",
159 | "parentCode": null,
160 | "level": "1",
161 | "name": "60 699 kroner",
162 | "shortName": "",
163 | "presentationName": "",
164 | "validFrom": null,
165 | "validTo": null,
166 | "notes": ""
167 | },
168 | {
169 | "code": "2006",
170 | "parentCode": null,
171 | "level": "1",
172 | "name": "62 892 kroner",
173 | "shortName": "",
174 | "presentationName": "",
175 | "validFrom": null,
176 | "validTo": null,
177 | "notes": ""
178 | },
179 | {
180 | "code": "2007",
181 | "parentCode": null,
182 | "level": "1",
183 | "name": "66 812 kroner",
184 | "shortName": "",
185 | "presentationName": "",
186 | "validFrom": null,
187 | "validTo": null,
188 | "notes": ""
189 | },
190 | {
191 | "code": "2008",
192 | "parentCode": null,
193 | "level": "1",
194 | "name": "70 256 kroner",
195 | "shortName": "",
196 | "presentationName": "",
197 | "validFrom": null,
198 | "validTo": null,
199 | "notes": ""
200 | },
201 | {
202 | "code": "2009",
203 | "parentCode": null,
204 | "level": "1",
205 | "name": "72 881 kroner",
206 | "shortName": "",
207 | "presentationName": "",
208 | "validFrom": null,
209 | "validTo": null,
210 | "notes": ""
211 | },
212 | {
213 | "code": "2010",
214 | "parentCode": null,
215 | "level": "1",
216 | "name": "75 641 kroner",
217 | "shortName": "",
218 | "presentationName": "",
219 | "validFrom": null,
220 | "validTo": null,
221 | "notes": ""
222 | },
223 | {
224 | "code": "2011",
225 | "parentCode": null,
226 | "level": "1",
227 | "name": "79 216 kroner",
228 | "shortName": "",
229 | "presentationName": "",
230 | "validFrom": null,
231 | "validTo": null,
232 | "notes": ""
233 | },
234 | {
235 | "code": "2012",
236 | "parentCode": null,
237 | "level": "1",
238 | "name": "82 122 kroner",
239 | "shortName": "",
240 | "presentationName": "",
241 | "validFrom": null,
242 | "validTo": null,
243 | "notes": ""
244 | },
245 | {
246 | "code": "2013",
247 | "parentCode": null,
248 | "level": "1",
249 | "name": "85 245 kroner",
250 | "shortName": "",
251 | "presentationName": "",
252 | "validFrom": null,
253 | "validTo": null,
254 | "notes": ""
255 | },
256 | {
257 | "code": "2014",
258 | "parentCode": null,
259 | "level": "1",
260 | "name": "88 370 kroner",
261 | "shortName": "",
262 | "presentationName": "",
263 | "validFrom": null,
264 | "validTo": null,
265 | "notes": ""
266 | },
267 | {
268 | "code": "2015",
269 | "parentCode": null,
270 | "level": "1",
271 | "name": "90 068 kroner",
272 | "shortName": "",
273 | "presentationName": "",
274 | "validFrom": null,
275 | "validTo": null,
276 | "notes": ""
277 | },
278 | {
279 | "code": "2016",
280 | "parentCode": null,
281 | "level": "1",
282 | "name": "92 576 kroner",
283 | "shortName": "",
284 | "presentationName": "",
285 | "validFrom": null,
286 | "validTo": null,
287 | "notes": ""
288 | },
289 | {
290 | "code": "2017",
291 | "parentCode": null,
292 | "level": "1",
293 | "name": "93 634 kroner",
294 | "shortName": "",
295 | "presentationName": "",
296 | "validFrom": null,
297 | "validTo": null,
298 | "notes": ""
299 | },
300 | {
301 | "code": "2018",
302 | "parentCode": null,
303 | "level": "1",
304 | "name": "96 883 kroner",
305 | "shortName": "",
306 | "presentationName": "",
307 | "validFrom": null,
308 | "validTo": null,
309 | "notes": ""
310 | },
311 | {
312 | "code": "2019",
313 | "parentCode": null,
314 | "level": "1",
315 | "name": "99 858 kroner",
316 | "shortName": "",
317 | "presentationName": "",
318 | "validFrom": null,
319 | "validTo": null,
320 | "notes": ""
321 | },
322 | {
323 | "code": "2020",
324 | "parentCode": null,
325 | "level": "1",
326 | "name": "101 351 kroner",
327 | "shortName": "",
328 | "presentationName": "",
329 | "validFrom": null,
330 | "validTo": null,
331 | "notes": ""
332 | },
333 | {
334 | "code": "2021",
335 | "parentCode": null,
336 | "level": "1",
337 | "name": "106 399 kroner",
338 | "shortName": "",
339 | "presentationName": "",
340 | "validFrom": null,
341 | "validTo": null,
342 | "notes": ""
343 | },
344 | {
345 | "code": "2022",
346 | "parentCode": null,
347 | "level": "1",
348 | "name": "111 477 kroner",
349 | "shortName": "",
350 | "presentationName": "",
351 | "validFrom": null,
352 | "validTo": null,
353 | "notes": ""
354 | }
355 | ]
356 | }
--------------------------------------------------------------------------------