Sende meldinger med Maskinporten-autorisasjon
Slik sender du meldinger fra appen med appens Maskinporten-klient.
Appen sender meldinger på vegne av tjenesteeieren, ikke på vegne av brukeren som er logget inn. Derfor bruker appen Maskinporten når den snakker med meldingstjenesten.
Før du starter, må du ha satt opp en Altinn-ressurs for meldingene.
Legge til scopet for meldingstjenesten
Appens Maskinporten-klient må ha scopet altinn:correspondence.write.
- Legg til
altinn:correspondence.writei Altinn Studio. Se Legge til Maskinporten-scopes. Dialogen viser bare scopes virksomheten har tilgang til. Finner du ikkealtinn:correspondence.write, må virksomheten først få tilgang til scopet. - Bygg en ny versjon av appen, og publiser den. Scopene blir med i versjonen når du bygger den. Publiserer du en eldre versjon på nytt, får den ikke det nye scopet.
Standardscopene for tjenesteeier, som altinn:serviceowner, legger Altinn Studio til automatisk når du bygger en v9-app. De trenger du ikke å legge til.
Med CorrespondenceAuthenticationMethod.Default() bruker meldingsklienten appens innebygde Maskinporten-klient. Du konfigurerer ikke noe i koden for å få et token. Se Integrere Altinn-app med Maskinporten for hvordan appens Maskinporten-identitet fungerer.
Sende en melding fra appkoden
Appen har meldingsklienten ICorrespondenceClient innebygd. Du trenger ikke registrere den selv, bare be om den i konstruktøren til klassen din.
En melding har mange mulige innstillinger, men bare noen få er påkrevd. CorrespondenceRequestBuilder hjelper deg å bygge meldingen steg for steg. Eksempelet under bygger en melding med
- selve meldingsteksten
- et varsel til mottakeren på e-post eller SMS
- et vedlegg
Du finner alle innstillingene og forklaringen til dem via IntelliSense i kodeeditoren din.
Når du sender meldingen, angir du hvordan appen skal hente tokenet. Med CorrespondenceAuthenticationMethod.Default() henter appen et token fra Maskinporten og veksler det inn i et tjenesteeiertoken fra Altinn med scopet altinn:correspondence.write.
App/CorrespondenceClientDemo.csusing System;
using System.IO;
using System.Threading.Tasks;
using Altinn.App.Core.Features;
using Altinn.App.Core.Features.Correspondence;
using Altinn.App.Core.Features.Correspondence.Builder;
using Altinn.App.Core.Features.Correspondence.Models;
namespace Altinn.App;
internal sealed class CorrespondenceClientDemo(
ICorrespondenceClient correspondenceClient
) : ITheInterfaceYouAreImplementing
{
public async Task<SendCorrespondenceResponse> SendMessage()
{
// await using lukker strømmen hvis sendingen feiler før opplastingen.
await using Stream attachmentData = File.OpenRead("path/to/attachment.txt");
CorrespondenceRequest request = CorrespondenceRequestBuilder
.Create()
.WithResourceId("A valid resource registry identifier")
.WithSendersReference("Sender's arbitrary reference for the correspondence")
.WithRecipient("Recipient's organisation number")
.WithContent(
language: "en",
title: "Hello from .NET 👋🏻",
summary: "The message summary",
body: "The message body with markdown support"
)
.WithNotification(
CorrespondenceNotificationBuilder
.Create()
.WithNotificationTemplate(CorrespondenceNotificationTemplate.CustomMessage)
.WithEmailSubject("New Altinn message")
.WithEmailBody("You have a new message in your Altinn inbox, log in to see what's new.")
.WithSmsBody("Got 📨 in Altinn")
.WithNotificationChannel(CorrespondenceNotificationChannel.EmailPreferred)
)
.WithAttachment(
CorrespondenceAttachmentBuilder
.Create()
.WithFilename("attachment.txt")
.WithSendersReference("Sender's arbitrary reference for the attachment")
.WithData(attachmentData)
)
.Build();
return await correspondenceClient.Send(
new SendCorrespondencePayload(request, CorrespondenceAuthenticationMethod.Default())
);
}
public async Task<GetCorrespondenceStatusResponse> GetMessageStatus(Guid correspondenceId)
{
return await correspondenceClient.GetStatus(
new GetCorrespondenceStatusPayload(
correspondenceId,
CorrespondenceAuthenticationMethod.Default()
)
);
}
}Når Send er ferdig, har meldingstjenesten godtatt meldingen, men den er ikke nødvendigvis levert til mottakeren ennå. Med GetStatus kan du sjekke om meldingen er publisert.
Legge ved filer
Du gir vedlegget til WithData som en Stream, for eksempel rett fra en fil. Da holder ikke appen hele vedlegget i minnet, noe som er viktig for store vedlegg. Har du vedlegget som en byte-array, pakker du det inn selv med WithData(new MemoryStream(bytes)).
Klienten lukker strømmen når den har lastet opp vedlegget. Feiler sendingen før det, for eksempel når appen ikke får token, lukker ikke klienten strømmen. Da må du lukke den selv, for eksempel med await using som i eksempelet. Det gjør ingenting om strømmen allerede er lukket.
Prøver du å sende på nytt, må du lage både meldingen og strømmen på nytt. Strømmen fra et tidligere forsøk kan allerede være lukket.
Hindre at meldingen blir sendt to ganger
Hvis koden din prøver å sende på nytt etter en feil, kan meldingen bli sendt to ganger. Det gjelder for eksempel kode i en prosess-hook, som kan kjøre flere ganger. Gi meldingen en fast nøkkel med WithIdempotentKey(Guid), og bruk den samme nøkkelen hver gang du prøver på nytt. En ny Guid.NewGuid() for hvert forsøk hindrer ingenting.
Nøkkelen må være unik i hele meldingstjenesten, ikke bare i appen din, og den må være den samme hver gang du prøver på nytt. Sender du én melding fra en systemoppgave, kan du bruke context.StepId som nøkkel. Se Gjøre oppgaven trygg å kjøre om igjen. Sender appen bare én melding per instans, kan du bruke GUID-en til instansen.
Finnes det allerede en melding med samme nøkkel, får du en CorrespondenceRequestException med statuskoden 409 Conflict. Det betyr bare at meldingstjenesten har tatt imot en melding med denne nøkkelen tidligere, ikke at den er publisert. Svaret inneholder ikke meldings-ID-en. Vil du sjekke status, må du ha lagret meldings-ID-en (CorrespondenceId) fra svaret på det første forsøket og kalle GetStatus med den. Meldingsklienten i appen kan ikke slå opp en melding ut fra nøkkelen. Fikk du aldri svaret på det første forsøket, kan du altså ikke sjekke status fra appen.
Klienten laster opp vedleggene før den oppretter meldingen. Prøver du på nytt, laster den derfor opp vedleggene en gang til, også når du får 409 Conflict. Disse vedleggene hører ikke til noen melding.
Du kan ikke bruke nøkkelen når meldingen har flere mottakere.
Registrere klassen
Registrer klassen din i Program.cs. I eksempelet heter grensesnittet ITheInterfaceYouAreImplementing. Bytt det ut med grensesnittet klassen din faktisk implementerer.
App/Program.csvoid RegisterCustomAppServices(IServiceCollection services, IConfiguration config, IWebHostEnvironment env)
{
// ...
services.AddTransient<ITheInterfaceYouAreImplementing, CorrespondenceClientDemo>();
}Bruke et eget token
CorrespondenceAuthenticationMethod.Default() passer for de fleste. Trenger du å hente tokenet på en annen måte, for eksempel med en egen Maskinporten-klient, kan du bruke CorrespondenceAuthenticationMethod.Custom med en delegat som returnerer tokenet.
Delegaten skal returnere et tjenesteeiertoken fra Altinn. Tokenet må ha scopet altinn:correspondence.write og bør ha altinn:serviceowner. Mangler tokenet altinn:serviceowner, må policyen til ressursen gi virksomheten din write, også når virksomheten din eier ressursen.
Appen kaller delegaten før hvert kall til meldingstjenesten. For en melding med vedlegg kan det bli mange kall, fordi appen sjekker status for hvert vedlegg opptil 60 ganger. Lagre derfor tokenet mellom kallene, og hent et nytt først når det er i ferd med å gå ut.
new SendCorrespondencePayload(
request,
CorrespondenceAuthenticationMethod.Custom(async () =>
{
// Hent et Altinn-token med scopene altinn:serviceowner og altinn:correspondence.write.
// Gjenbruk tokenet til det er i ferd med å gå ut.
string accessToken = await GetTokenFromYourOwnClient();
return JwtToken.Parse(accessToken);
})
);
JwtToken ligger i navnerommet Altinn.App.Core.Models. GetCorrespondenceStatusPayload tar imot CorrespondenceAuthenticationMethod.Custom på samme måte.
Kommer du fra v8?
De viktigste endringene fra v8:
CorrespondenceAuthorisationfinnes ikke lenger. BrukCorrespondenceAuthenticationMethod.Default()i stedet forCorrespondenceAuthorisation.Maskinporten.- Payloadene tar ikke lenger imot en delegat direkte. Pakk delegaten inn i
CorrespondenceAuthenticationMethod.Custom(...). WithData(ReadOnlyMemory<byte>)finnes ikke lenger. BrukWithData(Stream). Har du en byte-array, bruker duWithData(new MemoryStream(bytes)).CorrespondenceAuthenticationMethod.Default()ber også om scopenealtinn:serviceowner/instances.readogaltinn:serviceowner/instances.write. Appens Maskinporten-klient må derfor ha dem. Altinn Studio legger dem til automatisk når du bygger en v9-app.ConfigureMaskinportenClientfinnes ikke lenger, ogMaskinportenSettingser ikke lenger offentlig. Du kan ikke lenger peke appens innebygde Maskinporten-klient mot egne innstillinger. MedDefault()bruker meldingsklienten denne klienten, såaltinn:correspondence.writemå ligge på den.- Noen builder-metoder finnes ikke lenger:
WithSender,WithRequestedSendTime,WithAllowSystemDeleteAfter,WithDataLocationTypeogWithRecipientToOverride. CustomRecipientheter nåCustomRecipientsog er en liste.
studioctl app upgrade v9 skriver om noen av disse kallene for deg og lister opp hver endring, slik at du kan se over dem:
CorrespondenceAuthorisation.Maskinportenblir tilCorrespondenceAuthenticationMethod.Default()når verdien står rett i kallet til payload-konstruktøren.- Et lambdauttrykk som står rett i kallet, blir pakket inn i
CorrespondenceAuthenticationMethod.Custom(...). Er delegaten en metode eller en variabel, sier verktøyet bare fra om kallet. WithDatamed en byte-array blir tilWithData(new MemoryStream(...))når verktøyet kan se at det er en byte-array.- Verktøyet fjerner kall til
WithSender,WithRequestedSendTime,WithAllowSystemDeleteAfterogWithDataLocationType. Siden v8 ikke brukte verdiene, blir meldingen den samme.CustomRecipientblir til listenCustomRecipients, ogICorrespondenceRequestBuilderSenderblir tilICorrespondenceRequestBuilderSendersReference.
Verktøyet sier fra om kall det ikke kan skrive om på en trygg måte, for eksempel når CorrespondenceAuthorisation ligger i en egen variabel, eller kall til WithRecipientToOverride. De retter du selv.