Skip to main content

Skapa GIL-integration med ASPNET Core

Genomgående i exemplena nedan används "MyIntegration", "my-integration" o.s.v. som behöver ersättas med namnet på integrationen. Exempelvis lakemedel, svf, battreberoendevard osv.

Förvillkor​

För att kommunicera externt med INCA krävs SITHS-funktionscertifikat. För ASPNET är det enklast om du har ett i p12- eller pfx-format. Om du har cert och nyckel i pem-format (ofta .pem eller .cer) kan du konvertera till pfx.

openssl pkcs12 -inkey key.pem -in cert.pem -export -out out.pfx

Du behöver också ha ASPNET Core 8 eller senare.

Du behöver också skapa ett systemkonto på INCA. Se wiki GIL-wikin.

Du hittar HSA-id:t i certets subject.

openssl x509 -in cert.pem -noout -subject

Filstruktur​

Förslag på filstruktur är:

ext-my-integration/
├─ gilcommon/
├─ Integration/
│ ├─ Controllers/
| | ├─ HealthController.cs
| | ├─ ...
│ ├─ Models/
| | ├─ ...
│ ├─ AppSettings.cs
│ ├─ appsettings.json
│ ├─ Program.cs
│ ├─ Integration.csproj
│ ├─ ...
├─ .gitignore
├─ bitbucket-pipelines.yml
├─ Containerfile
├─ MyIntegration.sln
├─ README.md

GILCommon​

Det finns en repo som kan användas som en git-submodul med en hel del gemensam kod.

git submodule add git@bitbucket.org:cancercentrum/gilcommon.git

Lägg till projektet i din solution och länka in det i ditt projekt.

Generera modeller och mappers​

Det finns en Generera C#-klasser-customsida som kan vara värd att titta på. Där kan man mata in ett utformnings-id och få hjälp att skapa vissa modellklasser och mappers.

AppSettings​

ASPNET har väldigt bra stöd för appsettings i json-filer.

Börja med att definiera en klass som motsvarar inställnigarna som behövs. Inställningar för användare, roll och region behöver inte vara med eftersom de finns i gilcommon-projektet.

public class AppSettings
{
public const string Key = "MyIntegration";

public required string RegisterShortname { get; set; }
public required string FormShortname { get; set; }
}

Skapa upp appsettings.json med defaultvärden. Lägg också med inställningar för GILCommon. Du kan sen också skapa fördefinierade presets med inställningar för olika INCA-miljöer. Exempelvis appsettings.Development.json, appsettings.Sprint.json och appsettings.Production.json.

appsettings.Development.json

{
"AllowedHosts": "*",
"GILCommon": {
"BaseUrl": "https://rccsprint.qa-incanet.se/",
"CertFile": null,
"CertPassphrase": null,
"CertPassphraseFile": null,
"Org": "OC",
"Role": "Inrapportor(Tjanstekontrakt)",
"User": "EXT_USERNAME",
"DefaultPositionFullCode": "0"
},
"MyIntegration": {
"RegisterShortname": "ipo",
"FormShortname": "ext_lakemedel"
}
}

OBS! Hårdkoda inte in sökvägen till ditt eget cert i appsettings-filen utan den ska sparas i din user secret.

Konfigurera AppSettings, autentisering etc.​

Redigera Program.cs och använd hjälpmetoder fråm GILCommon.

// Program.cs

// ...

builder.Services.AddGilServices(builder.Configuration);

builder.Services.Configure<AppSettings>(builder.Configuration.GetSection(AppSettings.Key));

// ...

app.UseGIL();

// ...

/v1/health​

Alla GIL-integrationer måste ha denna endpoint. Den används bland annat av ITS för health checks.

// Controllers/HealthController.cs

using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using GILCommon;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;

namespace MyIntegration.Controllers;

[Route("v1/[controller]")]
[ApiExplorerSettings(IgnoreApi = true)]
[ApiController]
public class HealthController(IOptions<AppSettings> appConfig)
: ControllerBase
{
[HttpGet]
[Route("")]
public IActionResult Get()
{
var version = GetType().Assembly.GetName().Version?.ToString();

return new JsonResult(new
{
name = GetType().Assembly.GetName().Name,
version,
time = DateTime.UtcNow.ToString("O"),
appsettings = User.IsInRole(GilRoles.Admin) ? appConfig.Value : null,
env = User.IsInRole(GilRoles.Admin) ? Environment.GetEnvironmentVariables() : null
}, new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
});
}
}

Skapa route som skickar data till INCA​

Börja med att skapa en modell som motsvarar datat som ska tas emot.

// src/models/SurveysModels.cs

public class SurveyData
{
[Required]
[JsonPropertyName("name")]
public required string Name { get; set; }
}

Skapa sen en controller, injecta IIncaRestServices och lägg till en POST-endpoint.

// src/controllers/SurveysController.cs

[Route("v1/[controller]")]
[ApiController]
[Authorize(AuthenticationSchemes = GilAuthenticationHandler.Name)]
[EnableGilLogMeta]
public class SurveysController(
IIncaRestServices incaRestService,
IOptions<AppSettings> appSettings)
: ControllerBase
{
[HttpPost]
[Route("")]
[GilAllowLogBody]
public async Task<PostResult> Post(RequestV1<SurveyData> model)
{
var sender = User.FindFirst(GilClaimTypes.Sender)!.Value;

// Loggas i GIL för enklare sökning
this.HttpContext.SetGilLogMeta("record_id", model.RecordId);
this.HttpContext.SetGilLogMeta("clinic", model.Clinic);
this.HttpContext.SetGilLogMeta("name", model.Payload.Name);

await incaRestService.SubmitFormData(...)
}
}

TODO: exemplet ovan behöver kanske vara lite mer komplett.

Loggning​

För loggning rekommenderas Serilog.

I test och produktion är det bra om loggning görs till fil.

Exempel i appsettings.Production.json.

{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"WriteTo": [
{
"Name": "Console"
},
{
"Name": "File",
"Args": {
"path": "/var/log/my-integration/log-.txt",
"rollingInterval": "Day"
}
}
]
},
"GILCommon": {
"BaseUrl": "https://rcc.incanet.se/"
}
}

Open Telemetry​

Se exempel https://learn.microsoft.com/en-us/dotnet/core/diagnostics/observability-otlp-example.

När containern startas behöver följande miljövariabler sättas:

  • OTEL_SERVICE_NAME=ext-namn_pa_integration
  • OTEL_EXPORTER_OTLP_ENDPOINT=http://otel.test-incanet-srv.its.umu.se:4317.
  • OTEL_EXPORTER_OTLP_PROTOCOL=grpc.

Läkemedel är en integration som använder sig av OTEL. Här går det att kika på hur det är uppsatt.

Swagger / OpenAPI​

Lägg till nuget-paketet Swashbuckle.AspNetCore.

Aktivera sen swagger i slutet av Program.cs.

// Program.cs

// ...

app.UseSwagger();
app.UseSwaggerUI();

// ...

Alla endpoints behöver inte exponeras i swagger. Exempelvis kan det vara klokt att ignorera HealthControllern genom att lägga på attributet [ApiExplorerSettings(IgnoreApi = true)].

Docker-compose​

Innan integrationen är redo att publiceras i GIL är det bra att förbereda en docker-compose.yml som kan skickas till den med server-access.

version: '3.7'
services:
extintegration:
image: rcccrrg1we.azurecr.io/ext/namn_pa_image:1.2.3
ports:
- "5123:80"
secrets:
- incacert_cert.pfx
- incacert_pwd.pfx
volumes:
- logs:/var/log/my-integration
environment:
- GILCommon__CertFile=/run/secrets/incacert_cert.pfx
- GILCommon__CertPassphraseFile=/run/secrets/incacert_pwd.txt
- ASPNETCORE_ENVIRONMENT=Sprint

volumes:
logs:

secrets:
incacert_cert.pfx:
file: /path/to/incacert_cert.pfx
incacert_pwd.txt:
file: /path/to/incacert_pwd.pfx

Bitbucket pipelines och bygge av Docker Image​

GILCommon​

För att kunna använda GILCommon i Bitbucket Pipelines behövs lite initiala förberedelser:

  1. I din repo, gå in på Repository Settings / Pipelines / SSH Keys och skapa en nyckel.
  2. Kopiera den publika nyckeln
  3. Öppna GILCommon Access Keys
  4. Tryck på "Add key" och pasta in den publika nyckeln från steg 1

Efter detta kan du i ett script-steg i pipelinen köra git submodule update --init --recursive.

Containerfile​

Skapa en Containerfile.

# Containerfile / Dockerfile

FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
WORKDIR /src
COPY gilcommon/ ./gilcommon
COPY MyIntegration/ ./MyIntegration
RUN dotnet restore "gilcommon/gilcommon.csproj"
RUN dotnet restore "MyIntegration/MyIntegration.csproj"
RUN dotnet publish "MyIntegration/MyIntegration.csproj" -c Release /p:EnvironmentName=Sprint -o /app

# final stage/image
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
WORKDIR /app
EXPOSE 8080
COPY --from=build /app .
ENV TZ=Europe/Stockholm
ENV ASPNETCORE_ENVIRONMENT=Sprint
ENV ASPNETCORE_HTTP_PORTS=8080
ENTRYPOINT ["dotnet", "MyIntegration.dll"]

Pipeline​

Sista steget är att skapa en bitbucket-pipelines.yml så att en image automatiskt byggs och pushas till RCC:s container registry på Azure då nya tag sätts.

I din repo, gå in på Repository settings / Pipelines / Repository Variables och lägg till ACR_IMAGE med imagens namn. Namnet ska prefixas med ext/. Exempelvis ext/lakemedel.

# bitbucket-pipelines.yml

image: atlassian/default-image:3

pipelines:
tags:
'*':
- step:
name: Build and publish image
script:
- git submodule update --init --recursive
- TAG_NAME="$ACR_LOGIN_SERVER/$ACR_IMAGE:${BITBUCKET_TAG//v}" # Remove eventual 'v' that might be prepended
- docker build --file Containerfile --tag $TAG_NAME .
- echo $ACR_PASSWORD | docker login $ACR_LOGIN_SERVER --username "$ACR_USERNAME" --password-stdin
- docker push $TAG_NAME
services:
- docker
caches:
- docker

Konfigurera integrationen i GIL​

Se wiki för GIL.