# 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. Die Paketversion wird automatisch aus der `AssemblyVersion` der gebauten WebApi-Assembly gelesen. In diesem Repo wird diese zentral in `SharedAssemblyInfo.cs` gepflegt. Ein erfolgreicher Release-Build erzeugt: ```text artifacts/ v/ artifacts/ v.zip ``` Builds signieren die DLLs nicht automatisch. Signieren ist optional und muss explizit aktiviert werden. Wenn `M42SignAssemblies=true` gesetzt ist, werden die DLLs im Package vor dem Package-Build per Authenticode signiert. Konfigurierbare MSBuild-Properties: ```powershell /p:M42SignAssemblies=true /p:M42SignTool="C:\Path\To\signtool.exe" /p:M42SignCertificateThumbprint="" /p:M42SignCertificateFile="C:\Path\To\certificate.pfx" /p:M42SignCertificatePassword="" /p:M42SignTimestampUrl="http://rfc3161timestamp.globalsign.com/advanced" /p:M42SignOptions="/a" ``` Build-Befehl: ```powershell dotnet build "C:\Workspace\C4IT FASD\F4SD_M42WebApi\C4IT - F4SD - M42WebApi.sln" -c Release ``` Signierter Release-Build: ```powershell dotnet build "C:\Workspace\C4IT FASD\F4SD_M42WebApi\C4IT - F4SD - M42WebApi.sln" -c Release_signed ``` In Visual Studio kann oben in der Konfiguration `Release` fuer einen unsignierten Build oder `Release_signed` fuer einen signierten Build gewaehlt werden. ## 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. Module duerfen nicht auf Vorrat geladen werden. Fuer Endpunkte, die Matrix42-Services nutzen, muessen die transitiven Abhaengigkeiten aber vollstaendig als Host-Module registriert werden. Fuer den finalen `IJournalService`-Pfad wurden unter anderem folgende Module benoetigt: - `Matrix42.Pandora.Persistence` - `Matrix42.Pandora.ServiceStore` - `Matrix42.Pandora.BizLogic` - `Matrix42.Pandora.Services` - `Matrix42.MsTeamsNotification.BizLogic` - `Matrix42.Integration.Aurora.General` - `Matrix42.Integration.Aurora.Search.BizLogic` - `Matrix42.ServiceManager.BizLogic` - eigenes Webservice-Assembly als Host-Modul Waehrend der Migration zeigten fehlende Module oder fehlende Sandbox-Host-Registrierungen unter anderem folgende Fehler: ```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 IComplianceRuleManager is an interface and cannot be constructed IRequestPropertiesProvider is an interface and cannot be constructed IExternalSearchService 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` ist der bevorzugte Pfad fuer die Ticket-Historie. In 26.1 haengt der konkrete `JournalService` transitiv an Teams Notification, Pandora Services und Aurora Search. Diese Abhaengigkeiten muessen im Host vollstaendig geladen werden. Status: - Ticket-Historie laeuft ueber `IJournalService`. - Der Aufruf erfolgt per Reflection gegen die zur Laufzeit geladene `GetJournalList`-Signatur, damit 26.1 Patch-Level-Unterschiede keine direkte MethodRef brechen. - Keine manuellen `JournalService`-Konstruktionen, Proxies, `JournalManager`-Fallbacks oder HTTP-Forwards verwenden. - Wenn Unity ein weiteres Interface nicht aufloesen kann, zuerst in den Matrix42-DLLs nach einem `IDependencyRegistrator` fuer genau dieses Interface suchen und das zugehoerige BizLogic-/Services-Modul laden. ## 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 ungeeignetes altes `System.Web`-/WebApi-Modul wurde in der Sandbox aktiviert. Ursache entfernen, statt weitere Abhaengigkeiten darum herum zu laden. ```text IApiExplorer is an interface and cannot be constructed ``` Der Matrix42-Sandbox-Host stellt nicht automatisch dieselbe WebApi-Explorer-Instanz wie der normale WebApi-Host bereit. Fuer den finalen Journal-Pfad registriert die Extension eine konkrete `System.Web.Http.Description.IApiExplorer`-Instanz per eigenem `IDependencyRegistrator`. ```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.