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_integrationOTEL_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:
- I din repo, gå in på Repository Settings / Pipelines / SSH Keys och skapa en nyckel.
- Kopiera den publika nyckeln
- Öppna GILCommon Access Keys
- 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.