Arbeidstilsynet.Common.AltinnApp
2.8.1
dotnet add package Arbeidstilsynet.Common.AltinnApp --version 2.8.1
NuGet\Install-Package Arbeidstilsynet.Common.AltinnApp -Version 2.8.1
<PackageReference Include="Arbeidstilsynet.Common.AltinnApp" Version="2.8.1" />
<PackageVersion Include="Arbeidstilsynet.Common.AltinnApp" Version="2.8.1" />
<PackageReference Include="Arbeidstilsynet.Common.AltinnApp" />
paket add Arbeidstilsynet.Common.AltinnApp --version 2.8.1
#r "nuget: Arbeidstilsynet.Common.AltinnApp, 2.8.1"
#:package Arbeidstilsynet.Common.AltinnApp@2.8.1
#addin nuget:?package=Arbeidstilsynet.Common.AltinnApp&version=2.8.1
#tool nuget:?package=Arbeidstilsynet.Common.AltinnApp&version=2.8.1
Introduction
A collection of common patterns and extensions for cross-cutting concerns in Altinn applications.
📖 Installation
To install the package, you can use the following command in your terminal:
dotnet add package Arbeidstilsynet.Common.AltinnApp
🚀 Features
- Extension Methods for common Altinn operations
- Abstract Data Processors for handling data changes
- Country Code Lookup with country dialing codes and names
- Altinn Options Provider for country dropdowns
- Embedded Resource Utilities for JSON data
🧑💻 Usage
Dependency Injection Setup
Altinn Application Utilities
Add services to your service collection:
public void ConfigureServices(IServiceCollection services)
{
// Add country code lookup functionality
services.AddLandskoder();
// OR add country options for Altinn dropdowns (includes AddLandskoder)
services.AddLandOptions();
}
Structured Data
1. Add the structured data extension to your service collection, specifying the data model and structured data types
// Adds a DataElement to you instance to hold structured data of type TStructuredData
// Content type will be "application/json".
services.AddStructuredData<TDataModel, TStructuredData>((datamodel) =>
{
return new TStructuredData
{
// Property mappings here
};
});
2. Add the data element to your App/config/applicationmetadata.json dataTypes
{
"dataTypes": [
{
"id": "structured-data",
"allowedContentTypes": [
"application/json"
],
"allowedContributors": [
"app:owned"
]
},
/// ...Other data types
]
}
Extension Methods
Instance Extensions
Easily extract common information from Altinn instances:
using Arbeidstilsynet.Common.AltinnApp.Extensions;
public class MyService
{
public void ProcessInstance(Instance instance)
{
// Extract instance GUID
Guid instanceId = instance.GetInstanceGuid();
// Get application name
string appName = instance.GetAppName();
// Get party ID of instance owner
int partyId = instance.GetInstanceOwnerPartyId();
Console.WriteLine($"Processing {appName} instance {instanceId} for party {partyId}");
}
}
DataClient Extensions
Simplified data operations with Altinn instances:
using Arbeidstilsynet.Common.AltinnApp.Extensions;
public class DataService
{
private readonly IDataClient _dataClient;
public DataService(IDataClient dataClient)
{
_dataClient = dataClient;
}
public async Task<MyDataModel?> GetFormData(Instance instance)
{
// Get form data with default data type "structured-data"
return await _dataClient.GetSkjemaData<MyDataModel>(instance);
// Or specify custom data type
return await _dataClient.GetSkjemaData<MyDataModel>(instance, "customDataType");
}
public async Task CleanupAttachments(Instance instance, List<DataElement> unusedAttachments)
{
foreach (var attachment in unusedAttachments)
{
// Delete data element and remove from instance
await _dataClient.DeleteElement(instance, attachment, delay: true);
}
}
}
Assembly Extensions
Load and deserialize embedded JSON resources:
using Arbeidstilsynet.Common.AltinnApp.Extensions;
public class ConfigurationService
{
public Task<MyResourceType> GetResource()
{
var assembly = Assembly.GetExecutingAssembly();
// Load embedded JSON resource and deserialize
return assembly.GetEmbeddedResource<MyResourceType>("resource.json");
}
}
Country Code Lookup
Use the built-in country code lookup service:
public class AddressService
{
private readonly ILandskodeLookup _landskodeLookup;
public AddressService(ILandskodeLookup landskodeLookup)
{
_landskodeLookup = landskodeLookup;
}
public async Task<string> GetCountryInfo(string countryCode)
{
// Look up specific country by ISO code
var country = await _landskodeLookup.GetLandskode("NOR");
if (country != null)
{
return $"{country.Land} ({country.Kode})"; // "Norway (+47)"
}
return "Unknown country";
}
}
Abstract Data Processors
Create data processors that respond to form data changes:
Simple Data Processor
using Arbeidstilsynet.Common.AltinnApp.Abstract.Processing;
public class MyFormDataProcessor : BaseDataProcessor<MyDataModel>
{
private readonly ILogger<MyFormDataProcessor> _logger;
public MyFormDataProcessor(ILogger<MyFormDataProcessor> logger)
{
_logger = logger;
}
protected override async Task ProcessData(MyDataModel current, MyDataModel? previous)
{
if (previous == null)
{
_logger.LogInformation("New form data created");
return;
}
_logger.LogInformation("Form data updated");
// Your custom processing logic here
}
}
Member-Specific Processor
public class EmailChangeProcessor : MemberProcessor<MyDataModel, string>
{
protected override string? AccessMember(MyDataModel dataModel)
{
return dataModel.Email;
}
protected override async Task ProcessMember(
string? currentEmail,
string? previousEmail,
MyDataModel currentDataModel,
MyDataModel previousDataModel)
{
// Your custom logic to handle the changed member
}
}
List Processor
public class AttachmentListProcessor : ListProcessor<MyDataModel, AttachmentInfo>
{
protected override List<AttachmentInfo>? AccessMember(MyDataModel dataModel)
{
return dataModel.Attachments; // Point to the list we want to process
}
protected override async Task ProcessListChange(
List<AttachmentInfo>? currentList,
List<AttachmentInfo>? previousList,
MyDataModel currentDataModel,
MyDataModel previousDataModel)
{
// Process that attachments have been removed or added
}
protected override async Task ProcessItem(
AttachmentInfo currentItem,
AttachmentInfo previousItem,
MyDataModel currentDataModel,
MyDataModel previousDataModel)
{
// Process individual item changes
}
}
Registration of Data Processors
Register your data processors in dependency injection:
public void ConfigureServices(IServiceCollection services)
{
// Register data processors
services.AddTransient<IDataProcessor, MyFormDataProcessor>();
services.AddTransient<IDataProcessor, EmailChangeProcessor>();
services.AddTransient<IDataProcessor, AttachmentListProcessor>();
}
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Altinn.App.Core (>= 8.12.7)
- FluentValidation (>= 12.1.1)
- OpenTelemetry.Api (>= 1.16.0)
-
net8.0
- Altinn.App.Core (>= 8.12.7)
- FluentValidation (>= 12.1.1)
- OpenTelemetry.Api (>= 1.16.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.8.1 | 93 | 7/1/2026 |
| 2.8.0 | 106 | 5/22/2026 |
| 2.8.0-alpha1 | 200 | 5/22/2026 |
| 2.7.2 | 198 | 4/29/2026 |
| 2.7.2-alpha | 98 | 4/29/2026 |
| 2.7.1 | 471 | 4/29/2026 |
| 2.7.1-alpha | 107 | 4/28/2026 |
| 2.7.0 | 103 | 4/28/2026 |
| 2.7.0-alpha7 | 123 | 4/25/2026 |
| 2.7.0-alpha6 | 104 | 4/25/2026 |
| 2.7.0-alpha5 | 102 | 4/24/2026 |
| 2.7.0-alpha4 | 99 | 4/24/2026 |
| 2.7.0-alpha3 | 103 | 4/24/2026 |
| 2.7.0-alpha2 | 100 | 4/24/2026 |
| 2.7.0-alpha | 98 | 4/24/2026 |
| 2.6.1 | 111 | 4/27/2026 |
| 2.6.0 | 198 | 3/6/2026 |
| 2.5.0 | 114 | 2/25/2026 |
| 2.4.1 | 140 | 2/18/2026 |
| 2.4.1-beta1 | 109 | 2/18/2026 |
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added <!-- for new features. -->
### Changed <!-- for changes in existing functionality. -->
### Deprecated <!-- for soon-to-be removed features. -->
### Removed <!-- for now removed features. -->
### Fixed <!-- for any bug fixes. -->
- fixed: `StructuredDataManager` now verifies that structured data exists by process end even when `KeepAppDataModelAfterMapping = true`.
### Security <!-- in case of vulnerabilities. -->
## 2.8.1
### Changed
- changed(deps): Applied minor and patch updates to dependencies
## 2.8.0
### Added
- added: `PreSubmitDataModelProcessor<TDataModel>` supports `TaskIdFilter` for limiting pre-submit processing to selected Altinn task IDs
- added: `StructuredDataConfiguration.TaskIdFilter` for limiting structured data generation to selected Altinn task IDs
### Changed
- changed: `StructuredDataManager` now refuses to delete the app data model if no structured data element of the configured type exists on the instance, guarding against data loss on misconfiguration or when structured data mapping did not run for the task.
## 2.7.2
### Fixed
- fixed: `StructuredDataManager` now deletes any pre-existing `structured-data` data elements before inserting the new one, making `CreateStructuredData` idempotent on retries
## 2.7.1
### Fixed
- fixed: `StructuredDataManager<,>` was registered as singleton but depends (via `IStructuredDataValidator<T>`) on FluentValidation's `IValidator<T>`, which is scoped by default. This caused DI scope validation to fail at startup. The manager is now registered as transient.
## 2.7.0
### Added
- added: `ILanguageObserver` interface and `AddLanguageObserver<T>` DI extension for receiving notifications about the user's selected language during `ProcessDataRead`
- added: `SelectedLanguageProcessor` that resolves the current language from the request or falls back to the user's profile preference. Only notifies when the language is supported by the application
- added: `StructuredDataManager` now validates structured data before insertion. Uses registered `IValidator<T>` implementations, or falls back to `DataAnnotations`. Controlled via `StructuredDataConfiguration.DisableValidation`
- added: `FieldsValidator<TDataModel, TField>` base class for field-level validators with expression-based field paths and change detection
- added: `RepeatingGroupValidator<TDataModel, TItem>` base class for validating items within repeating groups (collections), with indexed path generation
- added: `DataTypeValidator<TDataModel, TToValidate>` base class that automatically discovers and validates all instances of a target type within a data model by walking the object graph via reflection, with circular type reference detection
### Changed
- changed: `ILanguageObserver.NotifyCurrentLanguage` now receives the current data model as a parameter (`object dataModel, string language`)
## 2.6.1
### Changed
- changed(deps): Applied minor and patch updates to dependencies
## 2.6.0
### Changed
- changed(deps): Applied minor and patch updates to dependencies
## 2.5.0
### Changed
- ListProcessor no longer emits ProcessList -- only ProcessItem, and items are compared using AltinnRowId. If you need to process the list as a whole, you should use MemberProcessor pointing to the list in question.
## 2.4.1
### Changed
- chore: moved package to nuget.org
## 2.4.0
- changed: Differentiate structured data and main content.
- More configurable, though defaults should for most.
- The altinn app will declare which data elemnts are the main content, and structured data, using data values
## 2.3.0
- added: PreSubmitProcessor to make it easier to implement custom pre-submit logic in AltinnApps.
- added: A few more extension methods for `IApplicationClient` and `IDataClient`
## 2.2.0
- added: Option to disable deletion of app datamodel after mapping structured data. This can be useful for testing and some legacy use cases.
## 2.1.0
### Changed
- changed(deps): Applied minor and patch updates to dependencies
## 2.0.0
### Changed
- changed(deps): Major dotnet updated (v10)
## 1.5.1
- added: Option to include error details related to mapping structured data in the validation response. This intended for development environments.
## 1.5.0
- added: Extension to manage DataElements for structured data. It deletes the xml-based DataModel, and replaces it with application/json of a user-provided type.
## 1.4.1
### Changed
- applied new default skjema type `structured-data` as default
## 1.4.0
- added: More customization options for LandOptions
- Custom order function for country list in LandOptions (default is alphabetical by country name)
- Option for using either alpha2 or alpha3 country codes as value in dropdowns (default is alpha3)
## 1.3.0
### Changed
- changed: Moved all AltinnApp logic (from the Altinn package) to this new dedicated package, only intended to be used by AltinnApps.