298 lines
12 KiB
Markdown
298 lines
12 KiB
Markdown
# 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
|
|
<TargetFramework>net8.0</TargetFramework>
|
|
<OutputType>Library</OutputType>
|
|
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
|
|
<AppendRuntimeIdentifierToOutputPath>true</AppendRuntimeIdentifierToOutputPath>
|
|
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
|
<M42ExtensionId>...</M42ExtensionId>
|
|
<M42AssemblyPattern>C4ITF4SD*.*</M42AssemblyPattern>
|
|
<M42BuildPackage>true</M42BuildPackage>
|
|
```
|
|
|
|
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/<Configuration>/<PackageName> v<Version>/
|
|
artifacts/<Configuration>/<PackageName> v<Version>.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="<thumbprint>"
|
|
/p:M42SignCertificateFile="C:\Path\To\certificate.pfx"
|
|
/p:M42SignCertificatePassword="<password>"
|
|
/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
|
|
<?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.
|
|
|
|
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<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:
|
|
|
|
```csharp
|
|
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:
|
|
|
|
```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 <Controller>: The current type, <Interface>, 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/<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.
|
|
|