Initial setup
The client library is available as a Nuget package. The client library (and associated Nuget package) is updated regularly as new functionality is added.
To install the Nuget package, follow these steps in Visual Studio/Rider:
-
Select TOOLS -> Nuget Package Manager -> Manage Nuget Packages Solution…
- Search for Digipost.Api.Client. Multiple packages will appear. Install those necessary for you. Make sure you DON’T install the
digipost-api-client packages
. Those are .NET Framework libraries with an unfortunately similar name. If you’re looking for the .NET Framework documention, please see version 8.3. If you would like pre-releases of this package, make sure Include Prerelease` is enabled. Please refer to documentation for your version of Visual Studio for detailed instructions. - Select which Digipost.Api.Client.X libraries you need and click Install for each.
Install and use enterprise certificate
SSL Certificates are small data files that digitally bind a cryptographic key to an organization's details. When installed on a web server, it activates the padlock and the https protocol (over port 443) and allows secure connections from a web server to a browser.
To communicate over HTTPS you need to sign your request with a enterprise certificate. The enterprise certificate can be loaded directly from file or from the Windows certificate store.
The following steps will install the certificate in the your certificate store. This should be done on the server where your application will run. For more information, please see the Microsoft Documentation.
The path and password to the certificate must be put somewhere safe.
The path on Windows is:
%APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json
On MacOS/Linux the path is:
~/.microsoft/usersecrets/<user_secrets_id>/secrets.json
Add the following UserSecretsId
element to your .csproj
file:
This means that the element <user_secrets_id>
in the path will be enterprise-certificate
.
From the command line, navigate to the directory where the current .csproj
file is located and run the following commands with your own certificate values:
dotnet user-secrets set "Certificate:Path:Absolute" "<your-certificate.p12>"
dotnet user-secrets set "Certificate:Password" "<your-certificate-password>"
Trust the Certificate on Windows:
- Double-click on the actual certificate file (CertificateName.p12)
- Save the sertificate in
Current User
orLocal Machine
and click Next. If you are running the client library from a system account, but debugging from a different user, please install it onLocal Machine
, as this enables loading it from any user. - Use the suggested filename. Click Next
- Enter password for private key and select Mark this key as exportable … Click Next
- Choose Automatically select the certificate store based on the type of certificate
- Click Next and Finish
- Accept the certificate if prompted.
- When prompted that the import was successful, click Ok
Trust the Certificate on MacOS:
- Open
Keychain Access
- Choose
login
keychain - Press File and then Import
- Choose the enterprise certificate and add it
Trust the Certificate on Linux:
Download the root and intermediate certificates from Difi for your enterprise certificate provider.
Note the renaming to have .crt
ending for update-ca-certificates
:
sudo cp Buypass_Class_3_Test4_Root_CA.pem /usr/local/share/ca-certificates/Buypass_Class_3_Test4_Root_CA.crt
sudo cp Buypass_Class_3_Test4_CA_3.pem /usr/local/share/ca-certificates/Buypass_Class_3_Test4_CA_3.crt
sudo update-ca-certificates
Create ClientConfig and DigipostClient:
// The actual sender of the message. The broker is the owner of the organization certificate
// used in the library. The broker id can be retrieved from your Digipost organization account.
const int brokerId = 12345;
var broker = new Broker(brokerId);
var clientConfig = new ClientConfig(broker, Environment.Production);
var client = new DigipostClient(clientConfig, CertificateReader.ReadCertificate());
Where CertificateReader.ReadCertificate()
is:
var pathToSecrets = $"{System.Environment.GetEnvironmentVariable("HOME")}/.microsoft/usersecrets/smoke-certificate/secrets.json";
_logger.LogDebug($"Reading certificate details from secrets file: {pathToSecrets}");
var fileExists = File.Exists(pathToSecrets);
if (!fileExists)
{
_logger.LogDebug($"Did not find file at {pathToSecrets}");
}
var certificateConfig = File.ReadAllText(pathToSecrets);
var deserializeObject = JsonConvert.DeserializeObject<Dictionary<string, string>>(certificateConfig);
deserializeObject.TryGetValue("Certificate:Path:Absolute", out var certificatePath);
deserializeObject.TryGetValue("Certificate:Password", out var certificatePassword);
_logger.LogDebug("Reading certificate from path found in secrets file: " + certificatePath);
return new X509Certificate2(certificatePath, certificatePassword, X509KeyStorageFlags.Exportable);
Load enterprise certificate from file
If there is a reason for not loading the certificate from the Windows certificate store, you can use the constructor taking in a X509Certificate2
:
var clientConfig = new ClientConfig(broker, Environment.Production);
var enterpriseCertificate =
new X509Certificate2(
@"C:\Path\To\Certificate\Cert.p12",
"secretPasswordProperlyInstalledAndLoaded",
X509KeyStorageFlags.Exportable
);
var client = new DigipostClient(clientConfig, enterpriseCertificate);
[Warning: This is technically supported on other operating systems than Windows, but we’ve not tested that thoroughly. Use at your own risk.] Load enterprise certificate from thumbprint in code
- Start mmc.exe (Click windowsbutton and type mmc.exe)
- Choose File -> Add/Remove Snap-in… (Ctrl + M)
- Mark certificate and click Add >
- If the certificate was installed in
Current User
chooseMy User Account
and if installed onLocal Machine
chooseComputer Account
. Then click Finish and then OK - Expand Certificates node, select Personal and open Certificates
- Double-click on the installed certificate
- Go to the Details tab
- Scroll down to Thumbprint
- Copy the thumbprint and load as shown below
var clientConfig = new ClientConfig(broker, Environment.Production);
var client = new DigipostClient(clientConfig, thumbprint: "84e492a972b7e...");
Load root element of Digipost rest api
The Digipost API is a HATEOAS-driven REST API. If you query the root you will get a response containing the Digipost public key and a list of hyperlinks to resources you can use with your sender or as a broker for a sender. This information is used behind the scenes by the client library to get the correct urls for different resources.
Fetch the default root. This root is normally what you want to use if you are not a broker for another sender.
var root = client.GetRoot(new ApiRootUri());
Fetch the root for a sender.
var root = client.GetRoot(new ApiRootUri(12345));
You can use this resource to safely test your integration or to fetch Digipost public key.
Proxy support
We have basic proxy support in the client. To use a proxy you need to define it in ClientConfig with a NetworkCredential for authentication.
var clientConfig = new ClientConfig(broker, Environment.Production)
{
WebProxy = new WebProxy("10.0.0.1"),
Credential = new NetworkCredential("foo", "bar")
};
var client = new DigipostClient(clientConfig, thumbprint: "84e492a972b7e...");
Send messages
Client configuration
ClientConfig
is a container for all the connection specific parameters that you can set.
// The actual sender of the message. The broker is the owner of the organization certificate
// used in the library. The broker id can be retrieved from your Digipost organization account.
var broker = new Broker(12345);
// The sender is what the receiver of the message sees as the sender of the message.
// Sender and broker id will both be your organization's id if you are sending on behalf of yourself.
var sender = new Sender(67890);
var clientConfig = new ClientConfig(broker, Environment.Production);
var client = new DigipostClient(clientConfig, CertificateReader.ReadCertificate());
Send one letter to recipient via personal identification number
var message = new Message(
sender,
new RecipientById(IdentificationType.PersonalIdentificationNumber, "311084xxxx"),
new Document(subject: "Attachment", fileType: "txt", path: @"c:\...\document.txt")
);
var result = client.SendMessage(message);
Other recipient types
There are other recipient types available to identify recipients of messages. Note that some recipient types may require special permissions to be set up in order to be used.
var recipient = new RecipientByNameAndAddress(
fullName: "Ola Nordmann",
addressLine1: "Prinsensveien 123",
postalCode: "0460",
city: "Oslo"
);
var primaryDocument = new Document(subject: "document subject", fileType: "pdf", path: @"c:\...\document.pdf");
var message = new Message(sender, recipient, primaryDocument);
var result = client.SendMessage(message);
Send one letter with multiple attachments
var primaryDocument = new Document(subject: "Primary document", fileType: "pdf", path: @"c:\...\document.pdf");
var attachment1 = new Document(subject: "Attachment 1", fileType: "txt", path: @"c:\...\attachment_01.txt");
var attachment2 = new Document(subject: "Attachment 2", fileType: "pdf", path: @"c:\...\attachment_02.pdf");
var message = new Message(
sender,
new RecipientById(IdentificationType.PersonalIdentificationNumber, id: "241084xxxxx"),
primaryDocument
){Attachments = {attachment1, attachment2}};
var result = client.SendMessage(message);
Send letter with SMS notification
var primaryDocument = new Document(subject: "Primary document", fileType: "pdf", path: @"c:\...\document.pdf");
primaryDocument.SmsNotification = new SmsNotification(afterHours: 0); //SMS reminder after 0 hours
primaryDocument.SmsNotification.NotifyAtTimes.Add(new DateTime(2015, 05, 05, 12, 00, 00)); //new reminder at a specific date
var message = new Message(
sender,
new RecipientById(identificationType: IdentificationType.PersonalIdentificationNumber, id: "311084xxxx"),
primaryDocument
);
var result = client.SendMessage(message);
Send letter with fallback to print if the user does not exist in Digipost
In cases where the recipient is not a Digipost user, it is also possible to use the recipient’s name and address for physical mail delivery.
var recipient = new RecipientByNameAndAddress(
fullName: "Ola Nordmann",
addressLine1: "Prinsensveien 123",
postalCode: "0460",
city: "Oslo"
);
var printDetails =
new PrintDetails(
printRecipient: new PrintRecipient(
"Ola Nordmann",
new NorwegianAddress("0460", "Oslo", "Prinsensveien 123")),
printReturnRecipient: new PrintReturnRecipient(
"Kari Nordmann",
new NorwegianAddress("0400", "Oslo", "Akers Àle 2"))
);
var primaryDocument = new Document(subject: "document subject", fileType: "pdf", path: @"c:\...\document.pdf");
var messageWithFallbackToPrint = new Message(sender, recipient, primaryDocument)
{
PrintDetails = printDetails
};
var result = client.SendMessage(messageWithFallbackToPrint);
Send message with request for registration
It is possible to send a message to a person who does not have a Digipost account, where the message triggers an SMS notification with a request for registration. The SMS notification says that if they register for a Digipost account the document will be delivered digitally.
The actual content of the SMS is not part of the request, it is stored as part of the Digipost sender account and must be agreed upon with Digipost, as well as the SMS sender ID or phone number.
If the user does not register for a Digipost account within the defined deadline, the document will be either delivered as physical mail or not at all.
Be aware that using PersonalIdentificationNumber
is required as Recipient to be able to deliver the document to the correct person.
The phone number provided SHOULD include the country code (i.e. +47). If the phone number does not start with either "+"
, "00"
or "011"
, we will prepend "+47"
if and only if the phone number string is 8 characters long. If this is not the case, the request is rejected.
In the following the document will be delivered as physical mail by Digipost if the recipient has not registered for a Digipost account by the defined deadline:
var recipient = new RecipientById(identificationType: IdentificationType.PersonalIdentificationNumber, id: "311084xxxx");
var documentGuid = Guid.NewGuid();
var requestForRegistration = new RequestForRegistration(
DateTime.Now.AddDays(3),
"+4711223344",
null,
new PrintDetails(
printRecipient: new PrintRecipient(
"Ola Nordmann",
new NorwegianAddress("0460", "Oslo", "Prinsensveien 123")),
printReturnRecipient: new PrintReturnRecipient(
"Kari Nordmann",
new NorwegianAddress("0400", "Oslo", "Akers Àle 2"))
)
);
var primaryDocument = new Document(subject: "document subject", fileType: "pdf", path: @"c:\...\document.pdf")
{
Guid = documentGuid.ToString()
};
var messageWithRequestForRegistration = new Message(sender, recipient, primaryDocument)
{
RequestForRegistration = requestForRegistration
};
var result = client.SendMessage(messageWithRequestForRegistration);
If the sender wishes to send the document as physical mail through its own
service, print details must not be included but be null
instead.
var recipient = new RecipientById(identificationType: IdentificationType.PersonalIdentificationNumber, id: "311084xxxx");
var documentGuid = Guid.NewGuid();
var requestForRegistration = new RequestForRegistration(
DateTime.Now.AddDays(3),
"+4711223344",
null,
null
);
In this case the status of the delivery can be checked with the following:
var documentStatus = _digipostClient.GetDocumentStatus(sender).GetDocumentStatus(documentGuid).Result;
If documentDeliveryStatus is still “NOT_DELIVERED” after the expiry date, you know that the user did not register a Digipost account and the document is not delivered.
The documentGuid is the same as the one used when the originating message was sent.
Send letter with fallback to print if the user does not read the message within a certain deadline
var recipient = new RecipientByNameAndAddress(
fullName: "Ola Nordmann",
addressLine1: "Prinsensveien 123",
postalCode: "0460",
city: "Oslo"
);
var printDetails =
new PrintDetails(
printRecipient: new PrintRecipient(
"Ola Nordmann",
new NorwegianAddress("0460", "Oslo", "Prinsensveien 123")),
printReturnRecipient: new PrintReturnRecipient(
"Kari Nordmann",
new NorwegianAddress("0400", "Oslo", "Akers Àle 2"))
);
var primaryDocument = new Document(subject: "document subject", fileType: "pdf", path: @"c:\...\document.pdf");
var messageWithPrintIfUnread = new Message(sender, recipient, primaryDocument)
{
PrintDetails = printDetails,
DeliveryTime = DateTime.Now.AddDays(3),
PrintIfUnread = new PrintIfUnread(DateTime.Now.AddDays(6), printDetails)
};
var result = client.SendMessage(messageWithPrintIfUnread);
Send letter to print without any Digipost addressing information (directly to print)
var printDetails =
new PrintDetails(
printRecipient: new PrintRecipient(
"Ola Nordmann",
new NorwegianAddress("0460", "Oslo", "Prinsensveien 123")),
printReturnRecipient: new PrintReturnRecipient(
"Kari Nordmann",
new NorwegianAddress("0400", "Oslo", "Akers Àle 2"))
);
var primaryDocument = new Document(subject: "document subject", fileType: "pdf", path: @"c:\...\document.pdf");
var messageToPrint = new PrintMessage(sender, printDetails, primaryDocument)
var result = client.SendMessage(messageToPrint);
// MessageStatus.DeliveredToPrint
Send letter with higher security level
var primaryDocument = new Document(subject: "Primary document", fileType: "pdf", path: @"c:\...\document.pdf")
{
AuthenticationLevel = AuthenticationLevel.TwoFactor, // Require BankID or BuyPass to open letter
SensitivityLevel = SensitivityLevel.Sensitive // Sender information and subject will be hidden until Digipost user is logged in at the appropriate authentication level
};
var message = new Message(
sender,
new RecipientById(identificationType: IdentificationType.PersonalIdentificationNumber, id: "311084xxxx"),
primaryDocument
);
var result = client.SendMessage(message);
Send letter with higher security level
var config = new ClientConfig("xxxxx", Environment.Production);
var client = new DigipostClient(config, thumbprint: "84e492a972b7e...");
var primaryDocument = new Document(subject: "Primary document", fileType: "pdf", path: @"c:\...\document.pdf");
primaryDocument.AuthenticationLevel = AuthenticationLevel.TwoFactor; // Require BankID or BuyPass to open letter
primaryDocument.SensitivityLevel = SensitivityLevel.Sensitive; // Sender information and subject will be hidden until Digipost user is logged in at the appropriate authentication level
var message = new Message(
new RecipientById(identificationType: IdentificationType.PersonalIdentificationNumber, id: "311084xxxx"), primaryDocument);
var result = client.SendMessage(message);
Identify recipient
Attempts to identify the person submitted in the request and returns whether he or she has a Digipost account. The person can be identified by personal identification number (PIN), Digipost address, or name and address.
If the user is identified, the ResultType
will be DigipostAddress
or PersonAlias
. In cases where we identify the person, the data parameter will contain the given identification value.
User is identified and have a Digipost account:
ResultType: DigipostAddress
Data: "Ola.Nordmann#3244B"
Error: Null
User is identified but does not have a Digipost account:
ResultType: PersonAlias
Data: "azdixsdfsdffsdfncixtvpwdp#6QE6"
Error: Null
The PersonAlias
can be used for feature lookups, instead of the given identify criteria.
Note: If you identify a person by PIN, the Digipost address will not be returned, since it is preferred that you continue to use this as identificator when sending the message.
If the user is not identified, the ResultType
will be InvalidReason
or UnidentifiedReason
. See the Error parameter for more detailed error message.
The user is not identified because the PIN is not valid:
ResultType: InvalidReason
Data: Null
Error: InvalidPersonalIdentificationNumber
The user is not identified because we did not have a match from the identify criteria:
ResultType: UnidentifiedReason
Data: Null
Error: NotFound
Following is a example that uses personal identification number as identification choice.
var identification = new Identification(new RecipientById(IdentificationType.PersonalIdentificationNumber, "211084xxxxx"));
var identificationResponse = client.Identify(identification);
if (identificationResponse.ResultType == IdentificationResultType.DigipostAddress)
{
//Exist as user in Digipost.
//If you used personal identification number to identify - use this to send a message to this individual.
//If not, see Data field for DigipostAddress.
}
else if (identificationResponse.ResultType == IdentificationResultType.Personalias)
{
//The person is identified but does not have an active Digipost account.
//You can continue to use this alias to check the status of the user in future calls.
}
else if (identificationResponse.ResultType == IdentificationResultType.InvalidReason ||
identificationResponse.ResultType == IdentificationResultType.UnidentifiedReason)
{
//The person is NOT identified. Check Error for more details.
}
Send letter through Norsk Helsenett
The Digipost API is accessible from both internet and Norsk Helsenett (NHN). Both entry points use the same API, the only difference is that ClientConfig.Environment
must be set to Environment.NorskHelsenett
.
// API URL is different when request is sent from NHN
var config = new ClientConfig(new Broker(12345), Environment.NorskHelsenett);
var client = new DigipostClient(config, thumbprint: "84e492a972b7e...");
var message = new Message(
sender,
new RecipientById(IdentificationType.PersonalIdentificationNumber, "311084xxxx"),
new Document(subject: "Attachment", fileType: "txt", path: @"c:\...\document.txt")
);
var result = client.SendMessage(message);
Search for receivers
A central part of a user interface in the application that is integrating with Digipost is the possiblity to search for receivers. This is available via the search endpoint. A person can be found by simply searching by first name and last name, e.g. Ola Nordmann
, or specified further by street address, postal code, city and organization name.
It is important to note that the search results returned do not necessarily include the receiver to which you actually wish to send. The search results returned are strictly based on the search query you have sent in. This equally applies when only one search result is returned. This means that the actual person to which you wish to send must be confirmed by a human being before the actual document i sent (typically in the senders application). If the goal is to create a 100% automated workflow then the identify recipient endpoint should be used (see Identify recipient use case).
var response = client.Search("Ola Nordmann Bellsund Longyearbyen");
foreach (var person in response.PersonDetails)
{
var digipostAddress = person.DigipostAddress;
var phoneNumber = person.MobileNumber;
}
Send on behalf of organization
In the following use case, Sender
is defined as the party who is responsible for the actual content of the letter. Broker
is defined as the party who is responsible for the technical transaction, which in this context means creating the request and being the party that is authenticated.
Sending on behalf of an organization is accomplished by setting Message.Sender
to the id of the sender when constructing a message. The actual letter will appear in the receivers Digipost mailbox with the senders details (logo, name, etc.).
Remember to use the enterprise certificate of the broker to sign the message, not the one belonging to the sender. Also, the proper permissions need to be set by Digipost to send on behalf of an organization.
Let us illustrate this with an example. Let BrokerCompany be an organization with id 12345, and thumbprint of their certificate 84e492a972b7e…. They want to send on behalf of SenderCompany with organization id 67890.
var broker = new Broker(12345);
var sender = new Sender(67890);
var digitalRecipient = new RecipientById(IdentificationType.PersonalIdentificationNumber, "311084xxxx");
var primaryDocument = new Document(subject: "Attachment", fileType: "txt", path: @"c:\...\document.txt");
var clientConfig = new ClientConfig(broker, Environment.Production);
var message = new Message(sender, digitalRecipient, primaryDocument);
var result = client.SendMessage(message);
Send message with delivery time
A message can be sent with a delivery time. This means that a message can be sent at 11 AM, and be delivered to the recipient’s Digipost inbox at 3 PM the next day. Message.DeliveryTime
is used for this purpose and is of type DateTime
. This gives you a lot of flexibility on how to set the delivery time.
var message = new Message(
sender,
new RecipientById(IdentificationType.PersonalIdentificationNumber, "311084xxxx"),
new Document(subject: "Attachment", fileType: "txt", path: @"c:\...\document.txt")
)
{
DeliveryTime = DateTime.Now.AddDays(1).AddHours(4)
};
var result = client.SendMessage(message);
Get status of a sent document
You can for any given document check its status to see if it and when has been delivered, status of read approval, delivery method etc.
To do this you need to have the Guid given to the document.
Send the message:
var documentGuid = Guid.NewGuid();
var message = new Message(
sender,
new RecipientById(IdentificationType.PersonalIdentificationNumber, "311084xxxx"),
new Document(subject: "Attachment", fileType: "txt", path: @"c:\...\document.txt")
{
Guid = documentGuid.ToString()
}
);
client.SendMessage(message);
To fetch fhe DocumentStatus later:
var documentStatus = _digipostClient.GetDocumentStatus(sender).GetDocumentStatus(documentGuid).Result;
This can be useful if you use fallback to print, print-if-unread, request for registration etc.
Get Sender by organisation number and a part id and send a message
In very specific usecases where a broker organisation has multiple sub-organisations in Digipost it is possible to send with organisation number and a partid. The partid can be used to distinguish between different divisions or however the organisation sees fit. This makes it possible to not have to store the Digipost account id, but in stead fetch this information from the api.
Fetch sender information based on orgnumber and partid.
var senderInformation = client.GetSenderInformation(new SenderOrganisation("9876543210", "thePartId"));
var message = new Message(
senderInformation.Sender,
new RecipientById(IdentificationType.PersonalIdentificationNumber, "311084xxxx"),
new Document(subject: "Attachment", fileType: "txt", path: @"c:\...\document.txt")
);
var result = client.SendMessage(message);
Get document events
Document events is special events created by Digipost when certain events happens to a document that the broker/sender could use to perform other operations with regards to the processing of the document.
When a sender send a RequestForRegistration one might be interested in knowing IF the user
has registered with in the timeout specified to for instance print the message or do som other
processing. When the timeout is reached, Digipost will create a DocumentEvent
with type RequestForRegistrationExpired
. This can then be fetched by the sender via DocumentEvent-api.
This is done by polling in certain intervals defined by the sender.
The following code fetches at max 100 events last 5 minutes.
var from = DateTime.Now.Subtract(TimeSpan.FromMinutes(5));
var to = DateTime.Now;
DocumentEvents events = await Client.DocumentsApi(Sender)
.GetDocumentEvents(from, to, offset: 0, maxResults: 100);
IEnumerable<DocumentEventType> documentEventTypes = events.Select(aEvent => aEvent.EventType);
Please note that this does not mean that you return all events in the time interval, but the maxResult you ask for. This means that if you get, say, 100 events in the list back, there is a possibility that there are more events in the specified time interval. You then need to fetch the same interval again, but specify an offset according to you maxResult.
Note that we here specify offset as 100 and use the same DateTime instance as used above.
DocumentEvents events = await Client.DocumentsApi(Sender)
.GetDocumentEvents(from: from, to: to, offset: 100, maxResults: 100);
IEnumerable<DocumentEventType> documentEventTypes = events.Select(aEvent => aEvent.EventType);
Usually we want the you to pull as few events at a time that seems reasonable since you need to process the events somehow while pulling more.
When you receive 0 og less that the specified maxResults, you have received all events in the current time interval.
We propose that you somehow store the state of the polling time interval. We expire/delete events after 30 days, so there are enough time to be sure you have processed them all. There is no way to delete an event through the api.
Send smart post
Send a message with extra computer readable data
Starting with version 8 of the Digipost API, messages can have extra bits of computer readable information that allows the creation of a customized, dynamic user experience for messages in Digipost. These extra bits of information are referred to as “Datatypes”.
All datatypes are sent in the same way. Each document can accommodate one datatype-object. An exhaustive list of available datatypes and their documentation can be found at digipost/digipost-data-types-dotnet.
Send invoice or inkasso (Dept collection)
It is possible to send invoice-metadata with a document. Documents with invoice-metadata enables the Send to Online Bank feature, which allows people to pay the invoice directly in Digipost.
var invoice = new Invoice(dueDate: DateTime.Parse("2022-12-03T10:15:30+01:00 Europe/Paris"), sum: new decimal(100.21), creditorAccount: "2593143xxxx")
{
Kid = "123123123"
};
var message = new Message(
sender,
new RecipientById(IdentificationType.PersonalIdentificationNumber, "211084xxxx"),
new Document(
subject: "Invoice 1",
fileType: "pdf",
path: @"c:\...\invoice.pdf",
dataType: invoice)
);
var result = client.SendMessage(message);
It is possible to send inkasso-metadata with a document.
var inkasso = new Inkasso(dueDate: DateTime.Parse("2022-12-03T10:15:30+01:00 Europe/Paris"))
{
Kid = "123123123",
Sum = new decimal(100.21),
Account = "2593143xxxx"
};
var message = new Message(
sender,
new RecipientById(IdentificationType.PersonalIdentificationNumber, "211084xxxx"),
new Document(
subject: "Invoice 1",
fileType: "pdf",
path: @"c:\...\invoice.pdf",
dataType: inkasso)
);
var result = client.SendMessage(message);
Send message with appointment datatype
Appointment
represents a meeting set for a specific place and time. The following example demonstrates how to include such extra data:
var appointment = new Appointment(startTime: DateTime.Parse("2017-11-24T13:00:00+0100"))
{
EndTime = DateTime.Parse("2017-11-24T13:00:00+0100").AddMinutes(30),
Address = new Address {StreetAddress = "Storgata 1", PostalCode = "0001", City = "Oslo"}
};
var document = new Document(
subject: "Your appointment",
fileType: "pdf",
path: @"c:\...\document.pdf",
dataType: appointment
);
// Create Message and send using the client as specified in other examples.
Send message with external link datatype
ExternalLink
enhances a message in Digipost with a button which sends the user to an external site. The button
can optionally have a deadline, a description and a custom text.
var externalLink = new ExternalLink(absoluteUri: new Uri("https://example.org/loan-offer/uniqueCustomerId/"))
{
Description = "Please read the terms, and use the button above to accept them. The offer expires at 23/10-2018 10:00.",
ButtonText = "Accept offer",
Deadline = DateTime.Parse("2018-10-23T10:00:00+0200")
};
var document = new Document(
subject: "Your appointment",
fileType: "pdf",
path: @"c:\...\document.pdf",
dataType: externalLink
);
// Create Message and send using the client as specified in other examples.
Datatype ShareDocumentsRequest
This datatype facilitates exchange of documents between an organisation and a Digipost end user. The sender first sends a message of datatype ShareDocumentsRequest, to which the end user can attach a list of documents. When new documents are shared, a DocumentEvent is generated. The organisation can retrieve the status of their ShareDocumentsRequest. If documents are shared and the sharing is not cancelled, the documents can either be downloaded or viewed on the digipostdata.no domain. Active requests can be cancelled both by the end user and the organisation.
The purpose
attribute of the ShareDocumentsRequest should briefly explain why the sender organisation want to gain
access to the relevant documents. The primary document should contain a more detailed explanation.
Send ShareDocumentsRequest
var shareDocReq = new ShareDocumentsRequest(
maxShareDurationSeconds: 60 * 60 * 24 * 5, // Five calendar days
purpose: "The purpose for my use of the document");
var requestGuid = Guid.NewGuid(); // Keep this in your database as reference to this particular user interaction
var document = new Document(
subject: "Information about document sharing",
fileType: "pdf",
path: @"c:\...\document.pdf",
dataType: shareDocReq
)
{
Guid = requestGuid.ToString()
};
var message = new Message(
senderInformation.Sender,
new RecipientById(IdentificationType.PersonalIdentificationNumber, "311084xxxx"),
new Document(subject: "Attachment", fileType: "pdf", path: @"c:\...\attachment.pdf")
);
IMessageDeliveryResult result = client.SendMessage(message);
Be notified of new shared documents
The sender organisation can be notified of new shared documents by polling document events regularly. Use the uuid
attribute
of the DocumentEvent to match with the messageUUID
of the origin ShareDocumentsRequest.
This particular api is still not developed for .NET client. Periodically check the below api to check current status for sent shared request.
Get state of ShareDocumentsRequest
- Use the
requestGuid
that you stored from previous.
var shareDocumentsRequestState = await client.GetDocumentSharing(sender)
.GetShareDocumentsRequestState(requestGuid);
If the shareDocumentsRequestState.SharedDocuments.Count() > 0
is true, then the user has shared a
document for this share request.
Get documents
Each SharedDocument
has attributes describing the document and its origin. If SharedDocumentOrigin
is of type
OrganisationOrigin
, the corresponding document was received by the end user through Digipost from the organisation
with the provided organisation number. If the origin is of type PrivatePersonOrigin
, the document was received either
from another end user or uploaded by the user itself.
Get a single document as stream:
Stream stream = await client.GetDocumentSharing(sender)
.FetchSharedDocument(
shareDocumentsRequestState.SharedDocuments.First().GetSharedDocumentContentStreamUri()
);
Get link to view a single document on digipostdata.no:
SharedDocumentContent sharedDocumentContent = await client.GetDocumentSharing(sender)
.GetShareDocumentContent(
shareDocumentsRequestState.SharedDocuments.First().GetSharedDocumentContentUri()
);
Uri uriToOpenInBrowser = sharedDocumentContent.Uri;
Stop sharing
Use the requestGuid
from before. Top stop the sharing you add more information on the original
message you sent to start this process. Stop sharing can be done when you definitely don’t need the
document to be shared any more. The sharing will in any case stop at the originally
specified max share duration automatically.
var shareDocumentsRequestState = await client.GetDocumentSharing(sender)
.GetShareDocumentsRequestState(requestGuid);
var additionalData = new AdditionalData(sender, new ShareDocumentsRequestSharingStopped());
client.AddAdditionalData(additionalData, shareDocumentsRequestState.GetStopSharingUri());
Receive messages
Client configuration
ClientConfig
is a container for all the connection specific paramters that you can set.
// The actual sender of the message. The broker is the owner of the organization certificate
// used in the library. The broker id can be retrieved from your Digipost organization account.
const int brokerId = 12345;
var broker = new Broker(brokerId);
var clientConfig = new ClientConfig(broker, Environment.Production);
var client = new DigipostClient(clientConfig, CertificateReader.ReadCertificate());
Get documents inbox
The inbox call outputs a list of documents ordered by DeliveryTime
. Offset
is the start index of the list, and limit
is the max number of documents to be returned. The offset
and limit
are therefore not in any way connected to InboxDocument.Id
.
The values offset
and limit
are meant for pagination so that one can fetch 100 and then the next 100.
var inbox = client.GetInbox(sender);
var first100 = inbox.Fetch(); //Default offset is 0 and default limit is 100
var next100 = inbox.Fetch(offset: 100, limit: 100);
We have now fetched the 200 newest inbox documents. As long as no new documents are received, the two API-calls shown above will always return the same result. If we now receive a new document, this will change. The first 100 will now contain 1 new document and 99 documents we have seen before. This means that as soon as you stumble upon a document you have seen before you can stop processing, given that all the following older ones have been processed.
Download document content
var inbox = client.GetInbox(sender);
var documentMetadata = (await inbox.Fetch()).First();
var documentStream = await inbox.FetchDocument(documentMetadata.GetGetDocumentContentUri());
Delete document
var inbox = client.GetInbox(sender);
var documentMetadata = (await inbox.Fetch()).First();
await inbox.DeleteDocument(documentMetadata.GetDeleteUri());
Archive
The archive API makes it possible for an organisation to manage documents in archives. These files are kept in separate archives, and the files belong to the sender organisation.
Archive a file
Let’s say you want to archive two documents eg. an invoice and an attachment and
you want to have some kind of reference to both documents. You can do that
by describing the two documents with ArchiveDocument
. Then you need to create an archive
and add the documents to the archive. In the following example we use a default archive.
readFileFromDisk
is just made up function to showcase how you might read a file to a byte[]
sender
is your sender, the one you use to create the client instance, if you are not a broker. For broker see below.
var archive = new Archive.Archive(sender, new List<ArchiveDocument>
{
new ArchiveDocument(Guid.NewGuid(), "invoice_123123.pdf", "pdf","application/psd", readFileFromDisk("invoice_123123.pdf")),
new ArchiveDocument(Guid.NewGuid(), "attachment_123123.pdf", "pdf","application/psd", readFileFromDisk("attachment_123123.pdf"))
});
var savedArchive = await client.GetArchive().ArchiveDocuments(archive);
You can name the archive if you wish to model your data in separate archives
var archive = new Archive.Archive(sender, new List<ArchiveDocument>(), "MyArchiveName");
Get a list of archives
An organisation can have many archives, or just the default unnamed archive. That is up to your design wishes. To get a list of the archives for a given Sender, you can do this:
IEnumerable<Archive.Archive> fetchedArchives = await client.GetArchive().FetchArchives();
Iterate documents in an archive
You can get content of an archive with paged requests. Under is an example of how to iterate an archive. However, its use is strongly discouraged because it leads to the idea that an archive can be iterated. We expect an archive to possibly reach many million rows so the iteration will possibly give huge loads. On the other hand being able to dump all data is a necessary feature of any archive.
Please use fetch document by UUID or referenceID instead to create functionality on top of the archive. You should on your side know where and how to get a document from an archive. You do this by knowing where you put a file you want to retrieve.
var current = (await client.GetArchive().FetchArchives()).First();
var documents = new List<ArchiveDocument>();
while (current.HasMoreDocuments())
{
var fetchArchiveDocuments = client.GetArchive().FetchArchiveDocuments(current.GetNextDocumentsUri()).Result;
documents.AddRange(fetchArchiveDocuments.ArchiveDocuments);
current = fetchArchiveDocuments;
}
// documents now have all ArchiveDocuments in the archive (not the actual bytes, just the meta data)
In c# 8 there is a language feature to produce a IAsyncEnumerable
so that you can implement a stream of
archive documents. But this has not been implemented in this client library since we are on dotnetstandard2.0.
For each iteration above you will get 100 documents.
Archive Document attributes
You can add optional attributes to documents. An attribute is a key/val string-dictionary that describe documents. You can add up to 15 attributes per archive document. The attribute’s key and value are case sensitive.
var archiveDocument = new ArchiveDocument(Guid.NewGuid(), "invoice_123123.pdf", "pdf", "application/psd", readFileFromDisk("invoice_123123.pdf"))
{
Attributes = {["invoicenumber"] = "123123"}
};
The attributes can be queried, so that you can get an iterable list of documents.
var archive = (await client.GetArchive().FetchArchives()).First();
var searchBy = new Dictionary<string, string>
{
["key"] = "val"
};
var fetchArchiveDocuments = client.GetArchive().FetchArchiveDocuments(archive.GetNextDocumentsUri(searchBy)).Result;
var documents = fetchArchiveDocuments.ArchiveDocuments;
// documents now have all ArchiveDocuments in the archive that has invoicenumber=123123
We recommend that the usage of attributes is made such that the number of results for a query on attributes is less than 100. If you still want that, it’s ok, but you need to iterate the pages to get all the results like we did in the example where we fetch all documents in an archive.
var current = (await client.GetArchive().FetchArchives()).First();
var documents = new List<ArchiveDocument>();
var searchBy = new Dictionary<string, string>
{
["key"] = "val"
};
while (current.HasMoreDocuments())
{
var fetchArchiveDocuments = client.GetArchive().FetchArchiveDocuments(current.GetNextDocumentsUri(searchBy)).Result;
documents.AddRange(fetchArchiveDocuments.ArchiveDocuments);
current = fetchArchiveDocuments;
}
// documents now have all ArchiveDocuments in the archive that has invoicenumber=123123
Get documents by referenceID
You can retrieve a set of documents by a given referenceID. You will then get the documents listed in their respective archives in return. ReferenceId is a string you can add to documents. The idea is that you might want to model documents in different archives and we able to fetch them combined. A good reference ID might be a unique technical process ID or a conversation ID of some kind. It is thus not meant to describe the actual document but is rather a join key of some kind.
IEnumerable<Archive.Archive> fetchArchiveDocumentsByReferenceId = await client.GetArchive().FetchArchiveDocumentsByReferenceId("MyProcessId[No12341234]");
As you can see, it is a list of Archives
containing ArchiveDocument
;
You should choose a ReferenceId in such a way to try and keep the count of documents low so as feasible.
Get documents by externalId or Guid/Uuid
You can retrieve a set of documents by the Guid/UUID that you give the document when you archive it. In the example above
we use Guid.NewGuid()
to generate an uuid/guid. You can either store that random uuid in your database for
retrieval later, or you can generate a deterministic uuid based on your conventions for later retrieval.
You will get in return an instance of Archive
which contains information on the archive the document is contained in
and the actual document. From this you can fetch the actual document.
ArchiveDocument archiveDocument = (await client.GetArchive(sender).FetchDocumentFromExternalId(Guid.Parse("10ff4c99-8560-4741-83f0-1093dc4deb1c"))).One();
Archive archive = await client.GetArchive(sender).FetchDocumentFromExternalId("MyExternalId");
You can store your guid that you used at upload time to fetch by Guid
.
Note:
The reason for having the string and Guid as a parameter is that with the java client it is possible to create an UUID
from a string. This is done deterministically. Under normal circumstance you should use Guid.NewGuid()
when you
upload an document and store this id for reference in your system. But if you want to fetch a document that you
know is stored by another entity that uses the deterministic UUID approach with java and all you have is the
original string, then you need to use the string parameterized method.
Java generates UUID in slightly different manner compared to .NET Guid. Therefore we have implemented the
same UUID-generation logic into the client library api. You can use the method Guid.Parse(UuidInterop.NameUuidFromBytes("MyExternalId""))
to create an exact equal value for a Guid compared to Uuid.
Get content of a document as a single-use link
You can get the actual content of a document after you have retrieved the archive document. Below is an example of how
you can achieve this with a given ArchiveDocument
. In the resulting ArchiveDocumentContent
, you will get a url to
the content which expires after 30 seconds.
//First fetch the archiveDocument
ArchiveDocument archiveDocument = await client.GetArchive(sender).FetchArchiveDocument(client.GetRoot(new ApiRootUri()).GetGetArchiveDocumentsByUuidUri(Guid.Parse("10ff4c99-8560-4741-83f0-1093dc4deb1c")));
ArchiveDocumentContent archiveDocumentContent = await client.GetArchive().GetDocumentContent(archiveDocument.DocumentContentUri());
Uri uri = archiveDocumentContent.Uri;
Get content of a document as a stream
In addition to a single-use link, you also have the option to retrieve the content of a document directly as a byte stream.
Stream streamDocumentFromExternalId = await client.GetArchive(sender).StreamDocumentFromExternalId(Guid.Parse("10ff4c99-8560-4741-83f0-1093dc4deb1c"));
Update document attributes and/or referenceId
You can add an attribute or change an attribute value, but not delete an attribute. You can however set the value to empty string. The value of the field for referenceID can be changed as well.
ArchiveDocument archiveDocument = await client.GetArchive(sender).FetchDocumentFromExternalId(Guid.Parse("10ff4c99-8560-4741-83f0-1093dc4deb1c"));
archiveDocument.WithAttribute("newKey", "foobar")
.WithReferenceId("MyProcessId[No12341234]Done");
ArchiveDocument updatedArchiveDocument = await client.GetArchive().UpdateDocument(archiveDocument, archiveDocument.GetUpdateUri());
Set autoDelete time
Sometimes you want to delete documents automatically. This can be done at upload time.
var archive = new Archive.Archive(sender, new List<ArchiveDocument>
{
new ArchiveDocument(Guid.NewGuid(), "invoice_123123.pdf", "pdf", "application/psd", readFileFromDisk("invoice_123123.pdf"))
.WithDeletionTime(DateTime.Today.AddYears(5))
});
var savedArchive = await client.GetArchive().ArchiveDocuments(archive);
Using archive as a broker
It is possible to be a broker for an actual sender. Most of the api described above also support the use of SenderId to specify who you are archiving for.
eg.:
client.GetArchive(new Sender(111111)).FetchArchives()
As you can see, this is typically done when you get the api part GetArchive
. This will use the feature of Root element
in the restful api and serve the client with url resources specific for that sender you are broker for.
At upload time, however, this is done in the actual message you send.
var archive = new Archive.Archive(new Sender(111111), new List<ArchiveDocument>
{
new ArchiveDocument(Guid.NewGuid(), "invoice_123123.pdf", "pdf","application/psd", readFileFromDisk("invoice_123123.pdf")),
new ArchiveDocument(Guid.NewGuid(), "attachment_123123.pdf", "pdf","application/psd", readFileFromDisk("attachment_123123.pdf"))
});
var savedArchive = await client.GetArchive().ArchiveDocuments(archive);
Logging
Debugging
Enabling Logging
The client library has the ability to log useful information that can be used for debug purposes.
To enable logging, supply the DigipostClient
with an implementation of Microsoft.Extensions.Logging.ILoggerFactory
.
This is Microsoft’s own logging API, and allows the user to chose their own logging framework.
Enabling logging on level DEBUG
will output positive results of requests and worse, WARN
only failed requests or worse, while ERROR
will only occur on failed requests.
These loggers will be under the Digipost.Api.Client namespace
.
Implementing using NLog
There are numerous ways to implement a logger, but the following examples will be based on NLog documentation.
- Install the Nuget-packages
NLog
,NLog.Extensions.Logging
andMicrosoft.Extensions.DependencyInjection
. - Create an
nlog.config
file. The following is an example that logs to both file and console:
In your application, do the following to create a logger and supply it to DigipostClient
:
private static IServiceProvider CreateServiceProviderAndSetUpLogging()
{
var services = new ServiceCollection();
services.AddSingleton<ILoggerFactory, LoggerFactory>();
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
services.AddLogging((builder) => builder.SetMinimumLevel(LogLevel.Trace));
var serviceProvider = services.BuildServiceProvider();
SetUpLoggingForTesting(serviceProvider);
return serviceProvider;
}
private static void SetUpLoggingForTesting(IServiceProvider serviceProvider)
{
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
loggerFactory.AddNLog(new NLogProviderOptions {CaptureMessageTemplates = true, CaptureMessageProperties = true});
NLog.LogManager.LoadConfiguration("./nlog.config");
}
static void Main(string[] args)
{
ClientConfig clientConfig = null;
var serviceProvider = CreateServiceProviderAndSetUpLogging();
var client = new DigipostClient(clientConfig, CertificateReader.ReadCertificate(), serviceProvider.GetService<ILoggerFactory>());
}
Request and Response Logging
For initial integration and debugging purposes, it can be useful to log the actual request and response going over the wire. This can be enabled by doing the following:
Set the property ClientConfig.LogRequestAndResponse = true
.
Warning: Enabling request logging should never be used in a production system. It will severely impact the performance of the client.
Error Handling
Error Handling
If you are communicating synchronously, the errors will be wrapped in an AggregateException
.
This is because a set of errors may have occured before you receive You can handle each exception within the aggregate by sending in an anonymous function to AggregateException.Handle()
:
try
{
var messageDeliveryResult = api.SendMessage(message);
}
catch (AggregateException ae)
{
ae.Handle((x) =>
{
if (x is ClientResponseException)
{
Console.WriteLine("A client response exception occured!");
return true;
}
return false;
});
}
If you are using methods in the client library that has async
in the name, it means that you are communicating asynchronously.
To correctly identify errors that might happen, for example during sending a message, you can catch errors like you normally would in an application:
try
{
var messageDeliveryResult = await api.SendMessageAsync(message);
}
catch(ClientResponseException e)
{
Console.WriteLine("A client response exception occured!");
}
Asynchronous communication with Digipost using the client library involves using methods with async
in method name, like SendMessageAsync
or IdentifyAsync
.