From 4e52cef73402e68c3b994dac0b0d2592d33e04c2 Mon Sep 17 00:00:00 2001 From: Meik Date: Sat, 27 Jun 2026 00:10:23 +0200 Subject: [PATCH] docs: document Matrix42 26.1 migration learnings --- docs/matrix42-26-1-extension-migration.md | 263 ++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 docs/matrix42-26-1-extension-migration.md diff --git a/docs/matrix42-26-1-extension-migration.md b/docs/matrix42-26-1-extension-migration.md new file mode 100644 index 0000000..a3920e5 --- /dev/null +++ b/docs/matrix42-26-1-extension-migration.md @@ -0,0 +1,263 @@ +# Matrix42 26.1 Extension/Webservice Migration + +Diese Notizen dokumentieren die Learnings aus der Migration der alten F4SD Matrix42 Extension inklusive Custom Web Service auf die Matrix42 26.1 Sandboxed Extension Struktur. + +Ausgangspunkt der eigentlichen 26.1-Migration war Commit `6c6ac272cd43` (`feat: support Matrix42 26.1 sandboxed web api`, 2026-06-26 11:44 +0200). Die Punkte hier sind bewusst als Checkliste fuer weitere Extensions formuliert. + +## Zielbild + +- Extension als .NET 8 Library mit `Matrix42.WebApi.Contracts`. +- Webservice-Controller von `Matrix42.WebApi.Contracts.ApiController` ableiten. +- Controller ueber `Matrix42.Hosting.Contracts.IDependencyResolver` konstruieren. +- Matrix42-Fachservices nicht direkt im Controller-Konstruktor injizieren. +- Host-Konfig so klein wie moeglich halten. +- Paketbau in den Visual-Studio-/MSBuild-Prozess integrieren. +- Runtime zuerst mit minimalen Endpoints pruefen (`isAlive`, einfache Datenabfrage), dann erst komplexere Services aktivieren. + +## Projektstruktur und Paketbau + +Bewaehrte Projekt-Eigenschaften: + +```xml +net8.0 +Library +win-x64;linux-x64 +true +false +... +C4ITF4SD*.* +... +true +``` + +Das Package wird ueber `M42SandboxedExtension.targets` gebaut. Ein erfolgreicher Release-Build erzeugt: + +```text +artifacts/ v/ +artifacts/ v.zip +``` + +Build-Befehl: + +```powershell +dotnet build "C:\Workspace\C4IT FASD\F4SD_M42WebApi\C4IT - F4SD - M42WebApi.sln" -c Release +``` + +## Host-Konfig + +Die Host-Konfig muss exakt zum Assembly-Namen passen: + +```text +C4ITF4SDM42WebApi.dll.host.config +``` + +Final bewaehrte Minimal-Konfig: + +```xml + + + + + + + +``` + +Wichtig: Module nicht auf Vorrat laden. Jedes zusaetzliche Modul kann transitive Unity-Registrierungen erzwingen, die fuer den konkreten Webservice gar nicht benoetigt werden. + +Problematische Module in dieser Migration: + +- `Matrix42.Pandora.Persistence` +- `Matrix42.Pandora.ServiceStore` +- `Matrix42.Pandora.BizLogic` +- `Matrix42.Pandora.Services` +- `Matrix42.ServiceManager.BizLogic` +- eigenes Webservice-Assembly als Host-Modul + +Diese Module fuehrten unter anderem zu Fehlern wie: + +```text +IEntityDataService is an interface and cannot be constructed +IUserProfileRepository is an interface and cannot be constructed +IApiExplorer is an interface and cannot be constructed +Could not load type 'System.Web.Routing.RouteTable' +IComplianceRuleManager is an interface and cannot be constructed +``` + +## Controller-Konstruktion + +Nicht so: + +```csharp +public MyController(IEntityDataService entityDataService, IJournalService journalService, IPandoraUserProfile userProfile) +``` + +Diese direkte Konstruktor-Injection zwingt Matrix42/Unity, alle Services schon beim Erzeugen des Controllers aufzuloesen. Dadurch kann bereits `isAlive` scheitern, obwohl der Endpoint selbst keinen dieser Services nutzt. + +Besser: + +```csharp +private readonly IDependencyResolver _resolver; + +public MyController(IDependencyResolver resolver) +{ + _resolver = resolver ?? throw new ArgumentNullException(nameof(resolver)); +} + +private T GetRequiredService() where T : class +{ + var service = _resolver.TryGet(); + if (service != null) + return service; + + throw new InvalidOperationException($"Required Matrix42 service is not registered: {typeof(T).FullName}"); +} +``` + +Fachservices dann nur im jeweiligen Endpoint oder Helper lazy aufloesen. + +## WebAPI-Rueckgaben + +Matrix42 serialisiert manche Rueckgabetypen anders als klassische ASP.NET Web API. + +Bewaehrt: + +- Datenendpoints geben direkt Nutzdaten zurueck, z. B. `Task` oder `Task`. +- `isAlive` gibt `IHttpActionResult` mit `StatusCodeResult(HttpStatusCode.NoContent)` zurueck. + +Nicht fuer normale Datenendpoints verwenden: + +```csharp +Task +HttpResponseExtensions.CreateResponse(...) +``` + +In dieser Runtime wurde ein `HttpResponseMessage` als JSON-Objekt serialisiert, z. B. mit `Version`, `Content`, `StatusCode`, `Headers`. + +Nicht fuer `isAlive` verwenden: + +```csharp +public void isAlive() +``` + +Das fuehrte zu: + +```json +{"result":null,"parameters":{}} +``` + +Bewaehrter Healthcheck: + +```csharp +[Route("isAlive"), HttpGet] +public IHttpActionResult isAlive() +{ + return new StatusCodeResult(HttpStatusCode.NoContent); +} +``` + +## Pickups/Enumerations + +`IEntityDataService.GetEnumeration(...)` ist bequem, zieht aber die Pandora-Service-Schicht herein. Das war in der Sandbox zu schwer und verursachte transitive Dependency-Probleme. + +Bewaehrt ist: + +```csharp +Matrix42.Persistence.Contracts.IEnumerationProvider +``` + +Dieser Service wird ueber `Matrix42.DataLayer.Persistence` bereitgestellt und liefert eine `DataTable`. Wenn der alte API-Vertrag `EntityEnumeration` bleiben soll, muss die `DataTable` selbst gemappt werden. + +Gepruefte Alternativen: + +- `Matrix42.Pandora.Contracts.IEntityDataService`: liefert direkt `EntityEnumeration`, benoetigt aber Pandora-Module. +- `update4u.SPS.DataLayer.Contracts.IPickupDataService`: liefert ebenfalls nur `DataTable`, bringt gegenueber `IEnumerationProvider` keinen klaren Vorteil. + +Pragmatische Empfehlung: + +- Fuer reine Pickup-Lesefunktionen `IEnumerationProvider` verwenden. +- Nur dann Pandora-Services laden, wenn es keine schlanke Alternative gibt. +- Route-Parameter defensiv behandeln. In dieser Migration kam der Pickup-Name einmal leer an, obwohl die URL ihn enthielt. Ein Fallback auf das letzte URL-Segment hat das stabilisiert. + +## Aktueller Benutzer + +`IPandoraUserProfile` nicht im Controller injizieren. Fuer `getMyRoleMemberships` reicht der Matrix42 Principal: + +```csharp +var principal = Thread.CurrentPrincipal as IM42Principal; +var userId = principal?.InteractivePrincipal?.M42Identity?.UserFragmentID + ?? principal?.M42Identity?.UserFragmentID + ?? Guid.Empty; +``` + +Danach kann der vorhandene ASQL-/Helper-Code mit der `Guid` weiterarbeiten. + +## Journal-Service + +`IJournalService` benoetigt zusaetzliche Matrix42-Service-Module. `Matrix42.ServiceManager.BizLogic` hat in dieser Migration wieder eine transitive `IComplianceRuleManager`-Abhaengigkeit erzeugt und darf deshalb nicht pauschal geladen werden. + +Status: + +- Ticket-Historie ueber `IJournalService` ist mit der Minimal-Host-Konfig nicht abgesichert. +- Fuer weitere Migrationen zuerst pruefen, ob ein kleineres Modul wie `Matrix42.ServiceManager.Services` reicht. +- Wenn nicht, Journal-Daten alternativ ueber DataLayer/ASQL lesen oder den Endpoint bewusst ausklammern. + +## Typische Fehlersymptome und Ursache + +```text +Cannot create instance of : The current type, , is an interface and cannot be constructed. +``` + +Meistens direkte Konstruktor-Injection oder ein zu breit geladenes Host-Modul. + +```text +Could not load type 'System.Web.Routing.RouteTable' +``` + +Ein altes `System.Web`-/WebApi-Modul wurde in der Sandbox aktiviert. Nicht durch eigene `IApiExplorer`-Registrierung kompensieren, sondern die Ursache entfernen. + +```json +{"Version":"1.1","Content":{...},"StatusCode":200} +``` + +Ein `HttpResponseMessage` wurde als Nutzdatenobjekt serialisiert. Action-Signatur auf direkten Nutzdatentyp umstellen. + +```json +{"result":null,"parameters":{}} +``` + +Ein `void`-Endpoint wurde als Matrix42-Result ohne Nutzdaten serialisiert. Fuer echten NoContent `StatusCodeResult(HttpStatusCode.NoContent)` verwenden. + +## Smoke-Test-Reihenfolge + +Nach jeder Paketinstallation in Matrix42: + +1. `GET /m42Services/api//isalive` + Erwartung: HTTP 204 No Content. +2. `GET /m42Services/api//getpickup/` + Erwartung: JSON-Nutzdaten, keine `HttpResponseMessage`-Huelle. +3. `GET /m42Services/api//getMyRoleMemberships` + Erwartung: aktueller Benutzer und Rollen, kein `IPandoraUserProfile`-Fehler. +4. Komplexere Ticket-/Overview-Endpoints. +5. Journal-/History-Endpoints separat pruefen, weil hier zusaetzliche Services benoetigt werden koennen. + +## Migrationsablauf fuer weitere Extensions + +1. Alten Package-Inhalt entpacken und Assembly-/Extension-ID identifizieren. +2. Neues 26.1-Projekt ueber Scaffolder/Beispielstruktur anlegen oder bestehendes Projekt angleichen. +3. `Matrix42.WebApi.Contracts` und `Matrix42.Hosting.Contracts` referenzieren. +4. Controller auf `IDependencyResolver` umstellen. +5. Direkte Konstruktor-Injection von Matrix42-Fachinterfaces entfernen. +6. Host-Konfig mit minimalen Modulen starten. +7. Rueckgaben pruefen: direkte Nutzdaten statt `HttpResponseMessage`, `StatusCodeResult` fuer 204. +8. Package-Build ueber MSBuild automatisieren. +9. Paket installieren und Smoke-Tests ausfuehren. +10. Fehlende Services einzeln und begruendet nachladen, nie ganze Pandora-/ServiceManager-Stacks auf Vorrat. + +## Git-/Build-Hinweise in diesem Repo + +- Vor Aenderungen `git status` pruefen. +- Geaenderte Textdateien mit CRLF speichern. +- Nach Aenderungen Release-Build ausfuehren. +- Danach committen und pushen. +