Files
C4IT-F4SD-M42WebApi/docs/matrix42-26-1-extension-migration.md

9.4 KiB

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:

<TargetFramework>net8.0</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
<AppendRuntimeIdentifierToOutputPath>true</AppendRuntimeIdentifierToOutputPath>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<M42ExtensionId>...</M42ExtensionId>
<M42AssemblyPattern>C4ITF4SD*.*</M42AssemblyPattern>
<M42PackageVersion>...</M42PackageVersion>
<M42BuildPackage>true</M42BuildPackage>

Das Package wird ueber M42SandboxedExtension.targets gebaut. Ein erfolgreicher Release-Build erzeugt:

artifacts/<PackageName> v<Version>/
artifacts/<PackageName> v<Version>.zip

Build-Befehl:

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:

C4ITF4SDM42WebApi.dll.host.config

Final bewaehrte Minimal-Konfig:

<?xml version="1.0" encoding="utf-8" ?>
<host xmlns="urn:m42/host.config">
  <modules>
    <module assembly="Matrix42.DataLayer.Persistence" />
  </modules>
  <sections></sections>
</host>

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:

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:

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:

private readonly IDependencyResolver _resolver;

public MyController(IDependencyResolver resolver)
{
    _resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));
}

private T GetRequiredService<T>() where T : class
{
    var service = _resolver.TryGet<T>();
    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<EntityEnumeration> oder Task<object>.
  • isAlive gibt IHttpActionResult mit StatusCodeResult(HttpStatusCode.NoContent) zurueck.

Nicht fuer normale Datenendpoints verwenden:

Task<HttpResponseMessage>
HttpResponseExtensions.CreateResponse(...)

In dieser Runtime wurde ein HttpResponseMessage als JSON-Objekt serialisiert, z. B. mit Version, Content, StatusCode, Headers.

Nicht fuer isAlive verwenden:

public void isAlive()

Das fuehrte zu:

{"result":null,"parameters":{}}

Bewaehrter Healthcheck:

[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:

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:

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

Cannot create instance of <Controller>: The current type, <Interface>, is an interface and cannot be constructed.

Meistens direkte Konstruktor-Injection oder ein zu breit geladenes Host-Modul.

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.

{"Version":"1.1","Content":{...},"StatusCode":200}

Ein HttpResponseMessage wurde als Nutzdatenobjekt serialisiert. Action-Signatur auf direkten Nutzdatentyp umstellen.

{"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/<prefix>/isalive Erwartung: HTTP 204 No Content.
  2. GET /m42Services/api/<prefix>/getpickup/<PickupName> Erwartung: JSON-Nutzdaten, keine HttpResponseMessage-Huelle.
  3. GET /m42Services/api/<prefix>/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.