Sist endret: 22. okt. 2024

eFormidling

How to configure integration with eFormidling for an app.

In addition to the documentation below, we have created a sample application showing the complete eFormidling setup.

This page describes the setup for a v8 Altinn Application, for prior versions please refer to the eFormidling setup for v7 documentation.

Prerequisites

Before setting up eFormidling you will need to have the following set up:

Maskinporten Integration

In order to enable eFormidling in your application you will need to setup an integration between your app and Maskinporten.

  • NB! In Program.cs add the following instead of what is described in the steps above in the RegisterCustomAppServices-method:

    App/Program.cs
    void RegisterCustomAppServices(IServiceCollection services, IConfiguration config, IWebHostEnvironment env)
    {
      services.Configure<MaskinportenSettings>(config.GetSection("MaskinportenSettings"));
      services.AddMaskinportenJwkTokenProvider("MaskinportenSettings--EncodedJwk");
    }
    

Events

An event subscription needs to be setup in order to ensure that the application knows the delivery status of the messages sent through eFormidling.

  • Add a new secret EventSubscription--SecretCode to Azure key vault.
  • Create a new .cs file and add the following:
App/logic/Events/EventSecretCodeProvider.cs
using Altinn.App.Core.Internal.Events;
using System;
using System.Threading.Tasks;
using Altinn.App.Core.Internal.Secrets;

namespace Altinn.App.logic.Events
{
  public class EventSecretCodeProvider : IEventSecretCodeProvider
  {
    private readonly ISecretsClient _keyVaultClient;
    private string _secretCode = string.Empty;

    public EventSecretCodeProvider(ISecretsClient keyVaultClient)
    {
      _keyVaultClient = keyVaultClient;
    }

    public async Task<string> GetSecretCode()
    {
      if (!string.IsNullOrEmpty(_secretCode))
      {
        return _secretCode;
      }

      var secretKey = "EventSubscription--SecretCode";
      string secretCode = await _keyVaultClient.GetSecretAsync(secretKey);
      _secretCode = secretCode ?? throw new ArgumentException($"Unable to fetch event subscription secret code from key vault with the specified secret {secretKey}.");
      return _secretCode;
    }
  }
}
  • In Program.cs add the following to RegisterCustomAppServices:
App/Program.cs
void RegisterCustomAppServices(IServiceCollection services, IConfiguration config, IWebHostEnvironment env)
{
  services.AddSingleton<IEventSecretCodeProvider, EventSecretCodeProvider>();

  services.AddMaskinportenHttpClient<SettingsJwkClientDefinition, EventsSubscriptionClient>(
  config.GetSection("MaskinportenSettings"), clientDefinition =>
  {
      clientDefinition.ClientSettings.Scope = "altinn:serviceowner/instances.read";
      clientDefinition.ClientSettings.ExhangeToAltinnToken = true;
      clientDefinition.ClientSettings.EnableDebugLogging = true;
  }).AddTypedClient<IEventsSubscription, EventsSubscriptionClient>();
}

Setup eFormidling in your application

Register eFormidling Services

In order to add support for eFormidling in your application you need to register its services by adding the following to the RegisterCustomAppServices-method in Program.cs:

App/Program.cs
void RegisterCustomAppServices(IServiceCollection services, IConfiguration config, IWebHostEnvironment env)
{
  services.AddEFormidlingServices2<EFormidlingMetadata, EFormidlingReceivers>(config);
}

Configuring Shipment Metadata

Metadata related to the eFormidling shipment is required, and this is set up in applicationmetadata.json.

    In order to setup the required metadata you will need to create a new section "eFormidling" in applicationmetadata.json and populate values for the parameters defined below.

    PropertyTypeDescription
    serviceId *stringID that specifies the shipment type. (DPO, DPV, DPI or DPF)
    dpfShipmentTypestringThe DPF shipment type used for routing in the receiving system
    receiverstringOrganisation number of the receiver (can be omitted). Only Norwegian organisations are supported.
    sendAfterTaskIdstringID of the task to be completed before the shipment is sent.
    process **stringProcess type
    standard ***stringThe document standard
    typeVersionstringVersion of the message type
    type ***stringThe document type
    securityLevel ***numberSecurity level to be set on the StandardBusinessDocument
    dataTypesarrayList of data types to include in the shipment

    * Altinn only supports DPF and DPO.

    ** Available processes for each receiver can be found at:

    https://platform.altinn.no/eformidling/api/capabilities/{orgnumber}
    

    *** Can be found within the pages describing each document type or using the URL above.

    Below is an example of the configuration for the message type arkivmelding.

    App/applicationmetadata.json
    {
    ...
     "eFormidling": {
       "serviceId": "DPO",
       "receiver": "991825827",
       "sendAfterTaskId": "Task_1",
       "process": "urn:no:difi:profile:arkivmelding:administrasjon:ver1.0",
       "standard": "urn:no:difi:arkivmelding:xsd::arkivmelding",
       "typeVersion": "2.0",
       "type": "arkivmelding",
      "securityLevel": 3,
       "dataTypes": [
        "ref-data-as-pdf"
       ]
      },
    ...
    }
    

    Activate eFormidling Integration in your application

    Integration with eFormidling needs to be explicitly activated in the application.
    In appsettings.json you need to enable eFormidling in the "AppSettings"-section as well as add a new section "EFormidlingClientSettings":

    App/appsettings.json
    {
      ...
      "AppSettings": {
        ...
        "EnableEFormidling": true
      },
      "EFormidlingClientSettings": {
        "BaseUrl": "http://localhost:9093/api/"
      }
    }
    

    If you do not wish to test the eFormidling integration locally, you can add an "AppSettings"-section to appsettings.Development.json and set "EnableEFormidling" to false.

    Message Metadata Generation in the application

    It is the application developer’s responsibility to create the message of the shipment sent through eFormidling.

      In order to create the shipment message you will need a class that implements the IEFormidlingMetadata-interface and create your message in the GenerateEFormidlingMetadata-method. Remember to register your class in Program.cs.

      You will need to replace YourMessageType and yourMessage with your shipment message type.

      App/logic/EFormidling/EFormidlingMetadata.cs
      public class EFormidlingMetadata : IEFormidlingMetadata
      {
        public async Task<(string MetadataFilename, Stream Metadata)> GenerateEFormidlingMetadata(Instance instance)
        {
            YourMessageType yourMessage = new YourMessageType();
      
            MemoryStream stream = new MemoryStream();
            XmlSerializer serializer = new XmlSerializer(typeof(YourMessageType));
            serializer.Serialize(stream, yourMessage);
            stream.Position = 0;
            StreamContent streamContent = new StreamContent(stream);
            streamContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/xml");
      
            return await Task.FromResult(("yourMessage.xml", stream));
        }
      }
      

      The following example shows a setup of an EFormidlingMetadata-class with the arkivmelding message type.

      In order for this example to work we have created a class Arkivmelding (based on arkivmelding.xsd). Bear in mind that this only includes the required parts of the arkivmelding, so if you wish to include other parts you must do so yourself.

      App/logic/EFormidling/EFormidlingMetadata.cs
      using System;
      using System.Collections.Generic;
      using System.IO;
      using System.Linq;
      using System.Net.Http;
      using System.Net.Http.Headers;
      using System.Threading.Tasks;
      using System.Xml.Serialization;
      using Altinn.App.Core.EFormidling.Interface;
      using Altinn.App.Core.Internal.App;
      using Altinn.App.Core.Models;
      using Altinn.App.Models.Arkivmelding;
      using Altinn.Platform.Storage.Interface.Models;
      using Microsoft.Extensions.Logging;
      
      namespace Altinn.App.logic.EFormidling;
      
      public class EFormidlingMetadata : IEFormidlingMetadata
      {
          private readonly ApplicationMetadata _applicationMetadata;
          private readonly ILogger<EFormidlingMetadata> _logger;
          private readonly string _documentCreator = "Digitaliseringsdirektoratet";
      
          public EFormidlingMetadata(IAppMetadata appMetadata, ILogger<EFormidlingMetadata> logger)
          {
              _applicationMetadata = appMetadata.GetApplicationMetadata().Result;
              _logger = logger;
          }
      
          public async Task<(string MetadataFilename, Stream Metadata)> GenerateEFormidlingMetadata(Instance instance)
          {
              string title = $"{_applicationMetadata.Title["nb"]}";
              Guid mappeSystemID = Guid.NewGuid();
      
              List<Dokumentbeskrivelse> dokumentbeskrivelse = new List<Dokumentbeskrivelse>();
      
              int documentNumber = 1;
              DataElement pdf = instance.Data.First(dataElement => dataElement.DataType == "ref-data-as-pdf");
              dokumentbeskrivelse.Add(GetDokumentbeskrivelse(pdf.Filename, documentNumber, "Hoveddokument"));
      
              List<DataElement> attachments = new List<DataElement>(instance.Data.FindAll(dataElement => dataElement.DataType == "attachments"));
      
              foreach (DataElement attachment in attachments)
              {
                  documentNumber += 1;
                  dokumentbeskrivelse.Add(GetDokumentbeskrivelse(attachment.Filename, documentNumber, "Vedlegg"));
              }
      
              Arkivmelding arkivmelding = new()
              {
                  System = "Altinn",
                  MeldingId = Guid.NewGuid().ToString(),
                  Tidspunkt = DateTime.Now,
                  AntallFiler = documentNumber,
                  Mappe = new List<Mappe> {
                      new Mappe {
                          Type = "saksmappe",
                          SystemID = mappeSystemID,
                          Tittel = title,
                          OpprettetDato = DateTime.Now,
                          OpprettetAv = _documentCreator,
                          Basisregistrering = new Basisregistrering
                          {
                              Type = "journalpost",
                              SystemID = Guid.NewGuid(),
                              OpprettetDato = DateTime.Now,
                              OpprettetAv = _documentCreator,
                              ReferanseForelderMappe = mappeSystemID,
                              Dokumentbeskrivelse = dokumentbeskrivelse,
                              Tittel = title,
                              OffentligTittel = title,
                              Journalposttype = "Utgående dokument",
                              Journalstatus = "Journalført",
                              Journaldato = DateTime.Now,
                          },
                          Saksdato = DateTime.Now,
                          AdministrativEnhet = _documentCreator,
                          Saksansvarlig = "Ingen",
                          Saksstatus = "Under behandling"
                      }
                  }
              };
      
              MemoryStream stream = new MemoryStream();
              XmlSerializer serializer = new XmlSerializer(typeof(Arkivmelding));
              serializer.Serialize(stream, arkivmelding);
              stream.Position = 0;
              StreamContent streamContent = new StreamContent(stream);
              streamContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/xml");
      
              return await Task.FromResult(("arkivmelding.xml", stream));
          }
      
          private Dokumentbeskrivelse GetDokumentbeskrivelse(string fileName, int documentNumber, string tilknyttetRegistreringSom)
          {
              return new Dokumentbeskrivelse
              {
                  SystemID = Guid.NewGuid(),
                  Dokumenttype = "Skjema",
                  Dokumentstatus = "Dokumentet er ferdigstilt",
                  Tittel = fileName,
                  OpprettetDato = DateTime.Now,
                  OpprettetAv = _documentCreator,
                  TilknyttetRegistreringSom = tilknyttetRegistreringSom,
                  Dokumentnummer = documentNumber,
                  TilknyttetDato = DateTime.Now,
                  TilknyttetAv = _documentCreator,
                  Dokumentobjekt = new Dokumentobjekt
                  {
                      Versjonsnummer = 1,
                      Variantformat = "Produksjonsformat",
                      OpprettetDato = DateTime.Now,
                      OpprettetAv = _documentCreator,
                      ReferanseDokumentfil = fileName,
                  },
              };
          }
      }
      

      Dynamically setting the shipment receiver

      If the receiver of a shipment needs to be set dynamically, a class implementing the IEFormidlingReceivers-interface needs to be created and registered in Program.cs.

        App/logic/EFormidling/EFormidlingReceivers.cs
        using System.Collections.Generic;
        using System.Threading.Tasks;
        using Altinn.App.Core.EFormidling.Interface;
        using Altinn.Common.EFormidlingClient.Models.SBD;
        using Altinn.Platform.Storage.Interface.Models;
        
        namespace Altinn.App.logic.EFormidling;
        
        public class EFormidlingReceivers : IEFormidlingReceivers
        {
            public async Task<List<Receiver>> GetEFormidlingReceivers(Instance instance)
            {
                Identifier identifier = new()
                {
                    Authority = "iso6523-actorid-upis",
                    // All Norwegian organisations need a prefix of '0192:'
                    Value = "0192:{organisationNumber}"
                };
        
                List<Receiver> receiverList = [new Receiver { Identifier = identifier }];
        
                return await Task.FromResult(receiverList);
            }
        }
        

        NB! Note that only Norwegian organisations are supported, and that the prefix 0192: is required before the organisation number.

        Adding a feedback task to the application process

        While not strictly necessary, it is recommended to add a feedback task to your application. This is to ensure that the process is moved along when the message has been received.
        No further changes are needed when the task has been added as the eFormidling service we added earlier will automatically move the process along.
        If you wish to customize the texts that are presented to the user during this step you can do so by overriding the text keys

        Ensuring unique filenames

        If the message sent by your application contains multiple attachments, it is important to ensure that these have unique filenames as the shipment will fail otherwise.
        If the message includes the generated PDF of the form, you need to check that the other filename(s) are not the same as the application name.
        One way to ensure unique filenames is through the use of file validation.

        Testing

        Thorough testing for the eFormidling integration in an application is encouraged.
        Safety measures and retry mechanisms are in place to ensure that a shipment reaches the receiver when errors are due to weak network connections.
        However, invalid shipments, including but not limited to missing attachments or mistakes in the "arkivmelding", will cause the shipment to fail without explicit warning to the end user or app owner.

        Local

        For the time being it is not possible to test the eFormidling integration locally as efm-mocks, which is necessary for local testing, is under renovation.

        Test environment (TT02)

        You can monitor the status of a shipment sent in the test environment through the endpoint below.
        https://platform.tt02.altinn.no/eformidling/api/conversations?messageId={instanceGuid}
        
        • {instanceGuid}: the GUID of the instance that has been archived.