Sist endret: 29. okt. 2026
Preview: Dette er dokumentasjonen for Altinn Studio v9, som er en forhåndsversjon. Gjeldende versjon er v8. For å se dokumentasjonen for gjeldende versjon, velg v8 i versjonsvelgeren i menyen til venstre.

Lage en egendefinert systemoppgave

Slik lager du en systemoppgave som kjører egen kode i prosessen

En egendefinert systemoppgave lar appen gjøre arbeid på serveren midt i prosessen, uten at brukeren gjør noe. Oppgaven består av

  • en C#-klasse som implementerer grensesnittet IServiceTask
  • et nytt steg i prosessen (BPMN)
  • en tilgangsregel som lar appen flytte prosessen forbi steget
Grensesnittet endret seg mellom v8 og v9. Har du en egendefinert systemoppgave fra før, se avsnittet om systemoppgaver i oppgraderingsveiledningen. Du finner den tilsvarende veiledningen for v8 i dokumentasjonen for gjeldende versjon.

Skrive systemoppgaven i C#

Klassen sier hvilken oppgavetype den håndterer, gjennom Type-egenskapen, og hva oppgaven skal gjøre, gjennom Execute. Verdien du gir Type-egenskapen, er navnet du bruker i prosessen og i tilgangsregelen.

App/logic/ServiceTasks/ExampleServiceTask.cs
using Altinn.App.Core.Features;
using Altinn.App.Core.Features.Process;
using Altinn.App.Models;

namespace Altinn.App.Logic.ServiceTasks;

public class ExampleServiceTask : IServiceTask
{
    public string Type => "exampleServiceTask";

    public async Task<ServiceTaskResult> Execute(ServiceTaskContext context)
    {
        Datamodell? skjemadata = await context.InstanceDataMutator.GetFormData<Datamodell>();

        if (skjemadata is null)
        {
            return ServiceTaskResult.FailedPermanent("Instansen mangler skjemadata.");
        }

        if (skjemadata.Godkjent != true)
        {
            return ServiceTaskResult.FailedPermanent("Skjemaet er ikke godkjent, og prosessen kan ikke fortsette.");
        }

        return ServiceTaskResult.Success();
    }
}

Du leser og endrer data gjennom context.InstanceDataMutator. Plattformen lagrer endringene når oppgaven svarer at den gikk bra. Husk at dataelementer fra tidligere oppgaver er låst.

Registrere systemoppgaven

Registrer klassen i Program.cs, slik at plattformen finner den:

App/Program.cs
void RegisterCustomAppServices(IServiceCollection services, IConfiguration config, IWebHostEnvironment env)
{
    // Register your apps custom service implementations here.
    services.AddTransient<IServiceTask, ExampleServiceTask>();
}

Legge til oppgaven i prosessen

Legg inn en serviceTask-node der du vil at arbeidet skal skje. Verdien i taskType må være den samme som i Type-egenskapen på klassen.

App/config/process/process.bpmn
<bpmn:serviceTask id="ExampleServiceTask" name="Example service task">
    <bpmn:extensionElements>
        <altinn:taskExtension>
            <altinn:taskType>exampleServiceTask</altinn:taskType>
        </altinn:taskExtension>
    </bpmn:extensionElements>
    <bpmn:incoming>Flow_1yq6g64</bpmn:incoming>
    <bpmn:outgoing>Flow_1xowpt0</bpmn:outgoing>
</bpmn:serviceTask>

Gi tilgang til oppgaven

Når plattformen flytter prosessen ut av en systemoppgave, gjør den det som tjenesteeier, ikke som brukeren. Kaller en bruker eller et annet system process/next selv, kontrollerer appen i tillegg at den som kaller, har handlingen for oppgaven den står på.

Tjenesteeieren må derfor ha tilgang til handlingen som hører til oppgavetypen. Mangler den, feiler overgangen ut av steget. Plattformen prøver på nytt i opptil ett døgn mens brukeren ser lastevisningen, og loggen forteller hvilken rettighet tjenesteeieren mangler.

Tilgangsfilen fra appmalen gir tjenesteeieren de grunnleggende handlingene, blant annet read, write, instantiate og complete. Den gir ingen egendefinert handling, så en egen oppgavetype må du åpne selv.

Handlingen prosessen krever

OppgavetypeHandlingen prosessen krever
data, feedback, pdf, eFormidling, fiksArkiv, subformPdfwrite
paymentpay eller write
signingsign eller write
confirmationconfirm
Egendefinert oppgavetypeHandling med samme navn som oppgavetypen

Har oppgaven en reject-flyt ut av seg, trenger tjenesteeieren reject i tillegg. Det gjelder når du bruker Success("reject"). Plattformen autoriserer en avbrytende overgang med reject, uansett hvilken type oppgaven har.

Oppgraderingen fra v8 legger inn de grunnleggende reglene for tjenesteeieren, men gir ikke tilgang til egendefinerte oppgavetyper automatisk. Den leser prosessen og sier fra om handlingene du må legge inn selv. Appen kontrollerer det samme når du bygger, gjennom regelen ALTINNAPP0800.

Regelen tjenesteeieren trenger

[ORG] og [APP] skal stå som de er. Det er plassholdere plattformen bytter ut med org- og appnavnet når appen blir satt i drift, så den samme filen virker i alle miljøer. De små variantene [org] og [app] virker likt.

Du fyller bare inn to ting selv: nummeret på regelen, som må være ledig i filen, og oppgavetypen, som er den samme verdien som Type-egenskapen på klassen har.

App/config/authorization/policy.xml
<xacml:Rule RuleId="urn:altinn:resource:app_[ORG]_[APP]:policyid:1:ruleid:3" Effect="Permit">
  <xacml:Description>Tjenesteeier kan flytte prosessen forbi systemoppgaven [TASK_TYPE].</xacml:Description>
  <xacml:Target>
    <xacml:AnyOf>
      <xacml:AllOf>
        <xacml:Match MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
          <xacml:AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">[ORG]</xacml:AttributeValue>
          <xacml:AttributeDesignator AttributeId="urn:altinn:org" Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject" DataType="http://www.w3.org/2001/XMLSchema#string" MustBePresent="false" />
        </xacml:Match>
      </xacml:AllOf>
    </xacml:AnyOf>
    <xacml:AnyOf>
      <xacml:AllOf>
        <xacml:Match MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
          <xacml:AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">[ORG]</xacml:AttributeValue>
          <xacml:AttributeDesignator AttributeId="urn:altinn:org" Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource" DataType="http://www.w3.org/2001/XMLSchema#string" MustBePresent="false" />
        </xacml:Match>
        <xacml:Match MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
          <xacml:AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">[APP]</xacml:AttributeValue>
          <xacml:AttributeDesignator AttributeId="urn:altinn:app" Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource" DataType="http://www.w3.org/2001/XMLSchema#string" MustBePresent="false" />
        </xacml:Match>
      </xacml:AllOf>
    </xacml:AnyOf>
    <xacml:AnyOf>
      <xacml:AllOf>
        <xacml:Match MatchId="urn:oasis:names:tc:xacml:3.0:function:string-equal-ignore-case">
          <xacml:AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">[TASK_TYPE]</xacml:AttributeValue>
          <xacml:AttributeDesignator AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id" Category="urn:oasis:names:tc:xacml:3.0:attribute-category:action" DataType="http://www.w3.org/2001/XMLSchema#string" MustBePresent="false" />
        </xacml:Match>
      </xacml:AllOf>
      <xacml:AllOf> <!-- Tas med når oppgaven har en reject-flyt ut av seg. -->
        <xacml:Match MatchId="urn:oasis:names:tc:xacml:3.0:function:string-equal-ignore-case">
          <xacml:AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">reject</xacml:AttributeValue>
          <xacml:AttributeDesignator AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id" Category="urn:oasis:names:tc:xacml:3.0:attribute-category:action" DataType="http://www.w3.org/2001/XMLSchema#string" MustBePresent="false" />
        </xacml:Match>
      </xacml:AllOf>
    </xacml:AnyOf>
  </xacml:Target>
</xacml:Rule>

Se tilgangsregler for mer om filen.

Når brukeren skal kunne prøve igjen

Skal brukeren kunne trykke Prøv igjen på en oppgave som har feilet, trenger brukeren den samme handlingen. Legg den på samme sted som de andre handlingene brukeren skal ha tilgang til.

App/config/authorization/policy.xml
<xacml:AllOf>
    <xacml:Match MatchId="urn:oasis:names:tc:xacml:3.0:function:string-equal-ignore-case">
    <xacml:AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">exampleServiceTask</xacml:AttributeValue>
    <xacml:AttributeDesignator AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id" Category="urn:oasis:names:tc:xacml:3.0:attribute-category:action" DataType="http://www.w3.org/2001/XMLSchema#string" MustBePresent="false" />
    </xacml:Match>
</xacml:AllOf>

Handlingen gir ikke brukeren mulighet til å hoppe over oppgaven. Så lenge oppgaven jobber eller venter, svarer plattformen 409 på process/next, og når den er ferdig, flytter plattformen prosessen videre selv.

Hva oppgaven kan svare

Execute svarer alltid med et ServiceTaskResult. Svaret bestemmer hva plattformen gjør videre. Et svar som sier at oppgaven er ferdig, flytter alltid prosessen videre:

SvarDette skjer
ServiceTaskResult.Success()Oppgaven er ferdig, og prosessen går videre langs standardflyten.
ServiceTaskResult.Success("reject")Oppgaven er ferdig, og prosessen går videre med handlingen reject — for eksempel tilbake til utfylling.
ServiceTaskResult.FailedRetryable("melding")Noe gikk galt, men det kan gå bedre om litt. Plattformen kjører oppgaven på nytt med økende pause mellom forsøkene.
ServiceTaskResult.FailedPermanent("melding")Noe gikk galt som ikke retter seg selv. Plattformen gir opp med en gang, og steget står som feilet.
ServiceTaskResult.Defer(TimeSpan.FromMinutes(5), "venter på svar fra fagsystemet")Oppgaven gikk bra, men svaret den venter på har ikke kommet. Prosessen står på steget, og plattformen kjører oppgaven på nytt om fem minutter.

Kaster koden en feil du ikke håndterer selv, tolker plattformen det som en feil den kan prøve på nytt. Skriv derfor FailedPermanent selv når du vet at nye forsøk er nytteløse. Da slipper du en lang rekke forsøk som likevel ikke fører noe sted.

Et forsøk som venter (Defer), lagrer ingenting. Endrer oppgaven data og venter i samme forsøk, avviser plattformen forsøket som et brudd på kontrakten. Skal oppgaven huske noe, må den lagre det i et forsøk som svarer Success, eller i et eget arbeidssteg. Se Systemoppgaver med flere steg.

Vente på svar fra et annet system

Venter oppgaven på et annet system, har du to mekanismer å velge mellom. Valget kommer an på om det andre systemet kan si fra selv, eller om oppgaven må gå og se etter.

Få svaret som en melding. Oppgaven åpner en postkasse, sender adressen til postkassen med forespørselen, og venter på meldingen som kommer tilbake. Dette passer når det andre systemet kan svare. Prosessen står på steget til oppgaven har konkludert ut fra svaret, eller til fristen i MailboxOptions.Timeout går ut. En postkasse krever en oppgave med flere arbeidssteg. Se Få svaret som en melding.

Sjekke selv. Svar Defer(delay, reason). Prosessen står på steget, og plattformen kjører oppgaven på nytt etter pausen du oppgir, så mange ganger som den trenger. Dette passer når ingenting kan kalle tilbake. WaitBudget setter tak på ventetiden til sammen, og steget feiler når taket er nådd. Bruk context.Wait.IsFinalCheck til å gi din egen forklaring på hva som aldri kom, i stedet for et generisk tidsavbrudd.

Et forsøk som venter, lagrer ingenting, og oppgaven kjører fra starten hver gang. Det oppgaven må huske mellom sjekkene, først og fremst at forespørselen alt er sendt, hører derfor i et eget arbeidssteg som fullfører. Se Systemoppgaver med flere steg.

eFormidling-oppgaven er det innebygde eksempelet: den sender meldingen i ett arbeidssteg, og venter deretter til integrasjonspunktet bekrefter at meldingen er levert.

Uansett hvilken av dem du velger, kan ingen hoppe over ventingen. Brukeren ser lastevisningen eller din egen side, og appen sender brukeren videre av seg selv når oppgaven er ferdig. Se Hva brukeren ser mens en systemoppgave kjører.

Gjøre oppgaven trygg å kjøre om igjen

Plattformen kan kjøre oppgaven på nytt etter en feil. Sender oppgaven en melding, oppretter en sak eller trekker et beløp, må den kjenne igjen arbeid den allerede har gjort:

  • Bruk context.StepId som nøkkel mot systemet du kaller. Verdien er den samme gjennom alle forsøk på det samme steget, og ny neste gang prosessen kommer til oppgaven.
  • Bruk context.WorkflowId når du trenger å skille et nytt besøk i oppgaven fra et nytt forsøk på det samme besøket.
  • Ikke la koden avgjøre noe ut fra context.Attempt eller context.Wait for å hindre dobbeltarbeid. Et forsøk som rakk å sende noe før det stoppet, kjører på nytt med de samme verdiene.
  • Trenger oppgaven varig spor av noe den har gjort, lagrer du det i instansdataene gjennom context.InstanceDataMutator.

Styre tidsbruk og nye forsøk

Trenger oppgaven andre tidsrammer enn standard, overstyrer du StepOptions:

App/logic/ServiceTasks/ExampleServiceTask.cs
public ProcessStepOptions? StepOptions =>
    new()
    {
        MaxExecutionTime = TimeSpan.FromMinutes(2),
        WaitBudget = TimeSpan.FromHours(6),
    };

Feltene betyr dette:

  • MaxExecutionTime er hvor lenge ett forsøk får bruke før plattformen avbryter det og regner forsøket som feilet. Hold den nær det kallet realistisk trenger. Et langt tidsavbrudd på et tregt kall kan forsinke andre instanser.
  • WaitBudget er hvor lenge oppgaven til sammen får vente når den svarer Defer. Når fristen er ute, feiler steget.
  • RetryStrategy styrer hvor mange nye forsøk plattformen gjør, og hvor lang pausen mellom dem er.

Standardverdiene og grensene ligger i plattformen, og kan avvike mellom miljøene:

VerdiStandardGrense
MaxExecutionTime for en systemoppgave10 minutter2 timer
WaitBudgetett døgn14 døgn
Nye forsøk etter FailedRetryableøkende pause fra ett sekund, maks fem minutter mellom forsøkene, i opptil ett døgn—
Pausen du oppgir i Defer—Minst ett sekund
MailboxOptions.Timeout—21 døgn

Setter du MaxExecutionTime eller WaitBudget høyere enn grensen, avviser plattformen overgangen med en gang. Det samme gjelder en postkassefrist over 21 døgn, men den avvisningen kommer først når oppgaven prøver å åpne postkassen. Oppgir du en kortere pause i Defer enn ett sekund, venter plattformen ett sekund likevel.

Oppgaver med flere steg

Skal oppgaven gjøre flere ting etter hverandre, for eksempel sende noe først og vente på svar etterpå, bør hvert arbeidsstykke få sitt eget arbeidssteg. Se Systemoppgaver med flere steg.