Consume APIs in an app

An application can consume open and closed APIs available through the Internet.

On this page:

ASP.NET Core has good opportunities to consume different types of API.

This can be useful if you want to expose your organization’s own APIs via an app or need help from external APIs in the application logic.

There are many ways to do this, and on this page you will find examples of how to consume REST APIs in an app.

Kalle et eksternt API direkte i en metode

I dette eksemplet brukes et åpent API til å kalkulere et felt i skjemaet basert på input i et annet felt. Klientkallet implementeres direkte i kalkuleringsmetoden og det implementeres en modell for å kunne parse API responsen.

REST Countries tilbyr et API som returnerer fakta om et land dersom man sender med navnet på landet. Vi ønsker å lage en app som tilbyr en bruker å søke opp hovedstaden i et land ved å bruke deres åpne API-endepunkt: https://restcountries.eu/rest/v2/name/.

App example animation

App example

API-kallet er lagt til i kalkuleringsmetoden i App/logic/CalculationHandler slik at et nytt søk trigges hver gang man endrer et felt i appen.

Forutsetninger:

  1. Det er laget en app i Altinn Studio.
  2. Det er lastet opp en datamodell som beskriver verden bestående av land med et navn og en hovedstad.
  3. Autogenerert C# klasse av datamodellen er utvidet med JSON-property tags for å kunne gjenbruke klassen i parsing av API responsen.

Implementasjon:

/// <summary>
/// Perform calculations and update data model
/// </summary>
/// <param name="instance">The data</param>
public async Task<bool> Calculate(object instance)
{
    if (instance.GetType() == typeof(Verden))
    {
        Verden verden = (Verden)instance;
        string navn = verden?.land?.Navn;

        if (!string.IsNullOrEmpty(navn))
        {
            using var client = new HttpClient
            {
                BaseAddress = new Uri("https://restcountries.eu/rest/v2/name/");
            };

            HttpResponseMessage response = await client.GetAsync(navn);

            if (response.StatusCode == System.Net.HttpStatusCode.OK)
            {
                var land = await response.Content.ReadAsAsync<List<Land>>();
                Land l = land.FirstOrDefault();
                verden.land.Hovedstad = l.Hovedstad;
            }
            else {
                verden.land.Hovedstad =
                    $"Hmm.. Du skrev {navn}. Er det et land?";
            }
        }
    }
    
    return true;
}

Konsumere REST API uten klientbibliotek

I dette eksemplet setter man opp en klient som konsumerer et åpent API til bruk i ulike deler av appen. Vi dekker oppsett av et klient interface, implementasjon av klient, tilgjengeliggjøring av klienten i appen og dependency injection inn i ulike klasser.

Hvis REST API’et ikke tilbyr et klientbibliotek for sitt API må dette opprettes som en del av applikasjonen eller som et ekstern bibliotek.

Definere API modeller

Hvis API-et som skal konsumeres er dokumentert ved hjelp av Swagger eller OpenAPI kan man relativt lett genere C# klasser basert på datamodellen. Dette kan gjøres manuelt eller ved hjelp av verktøy som tilbyr slik generering. Bruker man Visual Studio kan man konvertere dette direkte. Velg “Paste JSON as classes”.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Altinn.App.services.br.models
{
    public class Enhet
    {
        public string organisasjonsnummer { get; set; }
        public string navn { get; set; }
        public Organisasjonsform organisasjonsform { get; set; }
        public Adresse postadresse { get; set; }
        public string registreringsdatoEnhetsregisteret { get; set; }
        public bool registrertIMvaregisteret { get; set; }
        public Naeringskode naeringskode1 { get; set; }
        public int antallAnsatte { get; set; }
        public Adresse forretningsadresse { get; set; }
        public string stiftelsesdato { get; set; }
        public Institusjonellsektorkode institusjonellSektorkode { get; set; }
        public bool registrertIForetaksregisteret { get; set; }
        public bool registrertIStiftelsesregisteret { get; set; }
        public bool registrertIFrivillighetsregisteret { get; set; }
        public string sisteInnsendteAarsregnskap { get; set; }
        public bool konkurs { get; set; }
        public bool underAvvikling { get; set; }
        public bool underTvangsavviklingEllerTvangsopplosning { get; set; }
        public string maalform { get; set; }
        public Links _links { get; set; }
    }
}

Eksempel modeller for API ses her.

Definere Klient Interface

Det anbefales at det defineres et interface for klienten som skal kalle API. Dette gjør at man kan benytte seg av dependency injection ved enhetstesting for å kunne mocke vekk API kall. Definer interface som vist nedenfor.

using Altinn.App.services.br.models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Altinn.App.services.br.client
{
    public interface IEnhetsregisteret
    {
        Task<Enhet> GetEnhetAsync(string orgnr);
    }
}

Eksempel interface kan sees her.

Implementere klient

Klienten er selve koden som gjøre kallene mot API og omformer resultatet til gitt datamodell.

using Altinn.App.services.br.models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;

namespace Altinn.App.services.br.client
{
    public class EnhetsregistreretCI : IEnhetsregisteret
    {
        private HttpClient _apiClient;

        public async Task<Enhet> GetEnhetAsync(string orgnr)
        {
            string apiUrl = $"enheter/" + orgnr;
            var result = new Enhet();

            HttpResponseMessage respons = await ApiClient.GetAsync(apiUrl);

            if (respons.StatusCode == System.Net.HttpStatusCode.OK)
            {
                string data = respons.Content.ReadAsStringAsync().Result;
                result = JsonConvert.DeserializeObject<Enhet>(data);
            }

            return result;
        }

        public HttpClient ApiClient
        {
            get
            {
                if (_apiClient != null)
                {
                    return _apiClient;
                }

                string url = "https://data.brreg.no/enhetsregisteret/api/";
                _apiClient = GetNewHttpClient(url);

                return _apiClient;
            }
        }

        private HttpClient GetNewHttpClient(string apiEndpoint)
        {
            var httpClient = new HttpClient
            {
                BaseAddress = new Uri(apiEndpoint)
            };

            return httpClient;
        }
    }
}

Eksempel kan sees her.

Sett opp klient i applikasjon

Når tjenesten med interface og klient er implementert kan den settes opp for bruk av applikasjonen.

Dette gjøres i App/Startup.cs hvor det settes opp interface og implementasjon av interface som tilbyr en gitt service til applikasjonen.

// Custom service used by this application
services.AddTransient<IEnhetsregisteret, EnhetsregistreretCI>();

Eksempel kan sees her

Konsumere API fra applikasjonslogikk/API kontrollere

For å få tak i service som er satt opp i applikasjonen må disse “injectes” inn i konstrukturøen på kontrolleren eller applikasjonslogikken.

using System.Threading.Tasks;
using Altinn.App.services.br.client;
using Altinn.App.services.br.models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace Altinn.App.controllers
{
    [ApiController]
    [Route("{org}/{app}/enhetsregisteret")]
    public class EnhetsregisteretController : ControllerBase
    {
        private IEnhetsregisteret _enhetsregisteret;

        public EnhetsregisteretController(IEnhetsregisteret enhetsregisteret)
        {
            _enhetsregisteret = enhetsregisteret;
        }

        [HttpGet("enhet/{orgnr}")]
        public async Task<ActionResult<Enhet>> GetEnhet([FromRoute] string orgnr)
        {
            Enhet enhet = await _enhetsregisteret.GetEnhetAsync(orgnr);
            return Ok(enhet);
        }
    }
}

Eksempel kan sees her.