docs: document matrix42 sandbox migration learnings
This commit is contained in:
@@ -1,20 +1,28 @@
|
|||||||
# Matrix42 26.1 Extension/Webservice Migration
|
# Matrix42 26.1 Legacy Extension 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.
|
Diese Notizen dokumentieren die bisherigen Learnings aus der Migration einer alten Matrix42 Extension inklusive Custom Web Service auf eine Matrix42 26.1 Sandboxed Extension.
|
||||||
|
|
||||||
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.
|
Die Anleitung ist bewusst als wiederverwendbare Checkliste fuer weitere Legacy-Pakete formuliert. Sie trennt zwischen belastbaren Ergebnissen, verworfenen Zwischenansaetzen und Punkten, die bei jeder Extension erneut geprueft werden muessen.
|
||||||
|
|
||||||
## Zielbild
|
## Zielbild
|
||||||
|
|
||||||
- Extension als .NET 8 Library mit `Matrix42.WebApi.Contracts`.
|
- Extension als .NET 8 Library mit `Matrix42.WebApi.Contracts`.
|
||||||
- Webservice-Controller von `Matrix42.WebApi.Contracts.ApiController` ableiten.
|
- Webservice-Controller von `Matrix42.WebApi.Contracts.ApiController` ableiten.
|
||||||
- Controller ueber `Matrix42.Hosting.Contracts.IDependencyResolver` konstruieren.
|
- Controller ueber `Matrix42.Hosting.Contracts.IDependencyResolver` konstruieren.
|
||||||
- Matrix42-Fachservices nicht direkt im Controller-Konstruktor injizieren.
|
- Matrix42-Fachservices nur dort lazy aufloesen, wo sie wirklich gebraucht werden.
|
||||||
- Host-Konfig so klein wie moeglich halten.
|
- Host-Konfig mit minimalen Modulen starten und gezielt erweitern.
|
||||||
- Paketbau in den Visual-Studio-/MSBuild-Prozess integrieren.
|
- Paketbau in MSBuild/Visual Studio integrieren.
|
||||||
- Runtime zuerst mit minimalen Endpoints pruefen (`isAlive`, einfache Datenabfrage), dann erst komplexere Services aktivieren.
|
- Paketversion aus `AssemblyVersion` ableiten.
|
||||||
|
- Signing nur fuer eine eigene signed Build-Konfiguration aktivieren.
|
||||||
|
- Postman-Collection als Regressionstest fuer alte Client-URLs pflegen.
|
||||||
|
|
||||||
## Projektstruktur und Paketbau
|
## Ausgangspunkt
|
||||||
|
|
||||||
|
Bei einer Portierung eines alten Produktstands muss der Zielstand klar sein. Fuer diesen Branch war der relevante alte Stand `26eac54f94`, nicht der spaetere neue Codezweig.
|
||||||
|
|
||||||
|
Wichtiges Learning: Bei einem 26.1-Kompatibilitaetsport keine neuen fachlichen Methoden, Obsolete-Markierungen oder Verhaltensaenderungen aus einem anderen Codezweig uebernehmen. Erst die alte Funktionalitaet lauffaehig machen, dann separate fachliche Aenderungen behandeln.
|
||||||
|
|
||||||
|
## Basisprojekt
|
||||||
|
|
||||||
Bewaehrte Projekt-Eigenschaften:
|
Bewaehrte Projekt-Eigenschaften:
|
||||||
|
|
||||||
@@ -23,24 +31,53 @@ Bewaehrte Projekt-Eigenschaften:
|
|||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
|
||||||
<AppendRuntimeIdentifierToOutputPath>true</AppendRuntimeIdentifierToOutputPath>
|
<AppendRuntimeIdentifierToOutputPath>true</AppendRuntimeIdentifierToOutputPath>
|
||||||
|
<Nullable>disable</Nullable>
|
||||||
|
<ImplicitUsings>disable</ImplicitUsings>
|
||||||
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||||
<M42ExtensionId>...</M42ExtensionId>
|
<M42ExtensionId>...</M42ExtensionId>
|
||||||
<M42AssemblyPattern>C4ITF4SD*.*</M42AssemblyPattern>
|
<M42AssemblyPattern>...</M42AssemblyPattern>
|
||||||
<M42BuildPackage>true</M42BuildPackage>
|
<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.
|
Referenzen auf Matrix42-DLLs sollen gegen die 26.1 Libraries zeigen und normalerweise `Private=false` haben, damit keine unnoetigen Matrix42-Plattform-DLLs ins Paket kopiert werden.
|
||||||
|
|
||||||
Ein erfolgreicher Release-Build erzeugt:
|
Die Host-Konfig muss exakt zum Assembly-Namen passen:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
artifacts/<Configuration>/<PackageName> v<Version>/
|
<AssemblyName>.dll.host.config
|
||||||
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.
|
## Paketbau
|
||||||
|
|
||||||
Konfigurierbare MSBuild-Properties:
|
Das Paket wird ueber `M42SandboxedExtension.targets` gebaut.
|
||||||
|
|
||||||
|
Aktuelle Regeln in diesem Repo:
|
||||||
|
|
||||||
|
- `Debug` baut Debug-DLLs und ein Debug-Paket.
|
||||||
|
- `Release` baut ein unsigniertes Release-Paket.
|
||||||
|
- `Release_signed` baut ein Release-Paket und aktiviert `M42SignAssemblies=true`.
|
||||||
|
- Artefakte liegen getrennt nach Konfiguration unter `artifacts/<Configuration>/`.
|
||||||
|
- Die Package-Version wird aus der `AssemblyVersion` der gebauten WebApi-Assembly gelesen.
|
||||||
|
- `M42PackageVersion` wurde entfernt und soll nicht wieder eingefuehrt werden.
|
||||||
|
|
||||||
|
Die zentrale Version liegt in `SharedAssemblyInfo.cs`.
|
||||||
|
|
||||||
|
Beispiele:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
dotnet build "C:\Workspace\C4IT FASD\F4SD_M42WebApi\C4IT - F4SD - M42WebApi.sln" -c Debug
|
||||||
|
dotnet build "C:\Workspace\C4IT FASD\F4SD_M42WebApi\C4IT - F4SD - M42WebApi.sln" -c Release
|
||||||
|
dotnet build "C:\Workspace\C4IT FASD\F4SD_M42WebApi\C4IT - F4SD - M42WebApi.sln" -c Release_signed
|
||||||
|
```
|
||||||
|
|
||||||
|
Visual Studio:
|
||||||
|
|
||||||
|
- `Debug`: Debugging und Testpakete.
|
||||||
|
- `Release`: Release ohne Signierung.
|
||||||
|
- `Release_signed`: Release mit Signierung.
|
||||||
|
|
||||||
|
Signierbare MSBuild-Properties:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
/p:M42SignAssemblies=true
|
/p:M42SignAssemblies=true
|
||||||
@@ -52,29 +89,32 @@ Konfigurierbare MSBuild-Properties:
|
|||||||
/p:M42SignOptions="/a"
|
/p:M42SignOptions="/a"
|
||||||
```
|
```
|
||||||
|
|
||||||
Build-Befehl:
|
## Package-Struktur
|
||||||
|
|
||||||
```powershell
|
Das neue Paket basiert auf einem `PackageTemplate` mit:
|
||||||
dotnet build "C:\Workspace\C4IT FASD\F4SD_M42WebApi\C4IT - F4SD - M42WebApi.sln" -c Release
|
|
||||||
```
|
|
||||||
|
|
||||||
Signierter Release-Build:
|
- `package.json`
|
||||||
|
- `install.xml`
|
||||||
|
- `install/...`
|
||||||
|
- `BasePackage/Assemblies` als Build-Ziel fuer Assemblies
|
||||||
|
|
||||||
```powershell
|
Beim Portieren eines Legacy-Pakets:
|
||||||
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.
|
1. Altes Paket entpacken.
|
||||||
|
2. WebAPI-Service (`PLSLServiceTypeWebAPI`) und Operationen (`PLSLWebServiceOperation`) identifizieren.
|
||||||
|
3. Extension-/Assembly-ID beibehalten, wenn ein Update statt einer Neuinstallation gewollt ist.
|
||||||
|
4. Operation-IDs nur dann aendern, wenn bewusst neue Operationen entstehen sollen.
|
||||||
|
5. Obsolete oder entfernte Schema-Attribute aus 26.1 entfernen.
|
||||||
|
|
||||||
## Host-Konfig
|
Konkretes 26.1-Learning:
|
||||||
|
|
||||||
Die Host-Konfig muss exakt zum Assembly-Namen passen:
|
- `UsedInTypeSPSActivityTypeAlert` gibt es in 26.1 nicht mehr und muss aus dem Paket entfernt werden.
|
||||||
|
|
||||||
```text
|
## Host-Konfig und Module
|
||||||
C4ITF4SDM42WebApi.dll.host.config
|
|
||||||
```
|
|
||||||
|
|
||||||
Final bewaehrte Minimal-Konfig:
|
Mit so wenig Modulen wie moeglich starten. Jedes zusaetzliche Modul kann transitive Unity-Registrierungen erzwingen, die bereits beim Controller- oder Service-Aufbau scheitern.
|
||||||
|
|
||||||
|
Minimaler Startpunkt fuer einfache Endpoints:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
@@ -86,31 +126,35 @@ Final bewaehrte Minimal-Konfig:
|
|||||||
</host>
|
</host>
|
||||||
```
|
```
|
||||||
|
|
||||||
Wichtig: Module nicht auf Vorrat laden. Jedes zusaetzliche Modul kann transitive Unity-Registrierungen erzwingen, die fuer den konkreten Webservice gar nicht benoetigt werden.
|
Der aktuelle Stand dieser Extension benoetigt fuer alle Funktionen mehr Module, insbesondere wegen Journal, Storage, Pandora, Teams Notification, ServiceConnection, Auth und Aurora Search. Aktuelle Host-Konfig:
|
||||||
|
|
||||||
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:
|
```xml
|
||||||
|
<modules>
|
||||||
- `Matrix42.Pandora.Persistence`
|
<module assembly="C4ITF4SDM42WebApi" />
|
||||||
- `Matrix42.Pandora.ServiceStore`
|
<module assembly="Matrix42.DataLayer.Persistence" />
|
||||||
- `Matrix42.Pandora.BizLogic`
|
<module assembly="Matrix42.Services.WebApi" />
|
||||||
- `Matrix42.Pandora.Services`
|
<module assembly="Matrix42.ServiceRepository.BizLogic" />
|
||||||
- `Matrix42.MsTeamsNotification.BizLogic`
|
<module assembly="Matrix42.Pandora.ServiceStore" />
|
||||||
- `Matrix42.Integration.Aurora.General`
|
<module assembly="Matrix42.Pandora.Persistence" />
|
||||||
- `Matrix42.Integration.Aurora.Search.BizLogic`
|
<module assembly="Matrix42.Pandora.BizLogic" />
|
||||||
- `Matrix42.ServiceManager.BizLogic`
|
<module assembly="Matrix42.Pandora.Services" />
|
||||||
- eigenes Webservice-Assembly als Host-Modul
|
<module assembly="Matrix42.Distributed.Redis.Cache" />
|
||||||
|
<module assembly="Matrix42.StorageService.BizLogic" />
|
||||||
Waehrend der Migration zeigten fehlende Module oder fehlende Sandbox-Host-Registrierungen unter anderem folgende Fehler:
|
<module assembly="Matrix42.MsTeamsNotification.BizLogic" />
|
||||||
|
<module assembly="Matrix42.ServiceConnection.Persistence" />
|
||||||
```text
|
<module assembly="Matrix42.Auth.BizLogic" />
|
||||||
IEntityDataService is an interface and cannot be constructed
|
<module assembly="Matrix42.ServiceConnection.BizLogic" />
|
||||||
IUserProfileRepository is an interface and cannot be constructed
|
<module assembly="Matrix42.Integration.Aurora.General" />
|
||||||
IApiExplorer is an interface and cannot be constructed
|
<module assembly="Matrix42.Integration.Aurora.Search.BizLogic" />
|
||||||
IComplianceRuleManager is an interface and cannot be constructed
|
<module assembly="update4u.SPS.ComplianceRules.BizLogic" />
|
||||||
IRequestPropertiesProvider is an interface and cannot be constructed
|
<module assembly="update4u.SPS.Security" />
|
||||||
IExternalSearchService is an interface and cannot be constructed
|
<module assembly="Matrix42.BizLogic.Journal" />
|
||||||
|
<module assembly="Matrix42.ServiceManager.BizLogic" />
|
||||||
|
</modules>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Regel: Wenn ein weiteres Interface nicht aufgeloest werden kann, nicht raten. In den Matrix42-DLLs nach dem Registrator/der Implementierung fuer genau dieses Interface suchen und nur das passende Modul laden.
|
||||||
|
|
||||||
## Controller-Konstruktion
|
## Controller-Konstruktion
|
||||||
|
|
||||||
Nicht so:
|
Nicht so:
|
||||||
@@ -119,16 +163,17 @@ Nicht so:
|
|||||||
public MyController(IEntityDataService entityDataService, IJournalService journalService, IPandoraUserProfile userProfile)
|
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.
|
Das zwingt Unity, alle Services schon beim Erzeugen des Controllers aufzuloesen. Dadurch kann bereits `isAlive` scheitern, obwohl der Endpoint selbst keinen dieser Services braucht.
|
||||||
|
|
||||||
Besser:
|
Bewaehrt:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
private readonly IDependencyResolver _resolver;
|
private readonly IDependencyResolver _resolver;
|
||||||
|
|
||||||
public MyController(IDependencyResolver resolver)
|
public MyController(IDependencyResolver resolver, IEnumerationProvider enumerationProvider)
|
||||||
{
|
{
|
||||||
_resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));
|
_resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));
|
||||||
|
_enumerationProvider = enumerationProvider ?? throw new ArgumentNullException(nameof(enumerationProvider));
|
||||||
}
|
}
|
||||||
|
|
||||||
private T GetRequiredService<T>() where T : class
|
private T GetRequiredService<T>() where T : class
|
||||||
@@ -141,16 +186,23 @@ private T GetRequiredService<T>() where T : class
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Fachservices dann nur im jeweiligen Endpoint oder Helper lazy aufloesen.
|
Fachservices im jeweiligen Endpoint oder Helper lazy aufloesen.
|
||||||
|
|
||||||
|
## Dependency-Registrator
|
||||||
|
|
||||||
|
Die Extension kann eigene Sandbox-Registrierungen bereitstellen, wenn der Matrix42-Sandbox-Host eine Plattform-Instanz nicht selbst registriert.
|
||||||
|
|
||||||
|
Konkretes Learning:
|
||||||
|
|
||||||
|
- Fuer den Journal-Pfad fehlte `System.Web.Http.Description.IApiExplorer`.
|
||||||
|
- Die Extension registriert eine konkrete `ApiExplorer`-Instanz ueber `IDependencyRegistrator`.
|
||||||
|
- Das eigene Assembly muss dafuer als Host-Modul geladen werden.
|
||||||
|
|
||||||
|
Das ist kein Workaround im fachlichen Code, sondern eine Host-Registrierung fuer eine fehlende Infrastruktur-Dependency.
|
||||||
|
|
||||||
## WebAPI-Rueckgaben
|
## WebAPI-Rueckgaben
|
||||||
|
|
||||||
Matrix42 serialisiert manche Rueckgabetypen anders als klassische ASP.NET Web API.
|
Matrix42 serialisiert `HttpResponseMessage` in dieser Runtime nicht wie klassische ASP.NET Web API, sondern als Nutzdatenobjekt.
|
||||||
|
|
||||||
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:
|
Nicht fuer normale Datenendpoints verwenden:
|
||||||
|
|
||||||
@@ -159,21 +211,19 @@ Task<HttpResponseMessage>
|
|||||||
HttpResponseExtensions.CreateResponse(...)
|
HttpResponseExtensions.CreateResponse(...)
|
||||||
```
|
```
|
||||||
|
|
||||||
In dieser Runtime wurde ein `HttpResponseMessage` als JSON-Objekt serialisiert, z. B. mit `Version`, `Content`, `StatusCode`, `Headers`.
|
Symptom:
|
||||||
|
|
||||||
Nicht fuer `isAlive` verwenden:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public void isAlive()
|
|
||||||
```
|
|
||||||
|
|
||||||
Das fuehrte zu:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"result":null,"parameters":{}}
|
{"Version":"1.1","Content":{...},"StatusCode":200}
|
||||||
```
|
```
|
||||||
|
|
||||||
Bewaehrter Healthcheck:
|
Bewaehrt:
|
||||||
|
|
||||||
|
- Datenendpoints geben direkte Nutzdaten zurueck, z. B. `Task<List<T>>`, `Task<object>` oder `Task<EntityEnumeration>`.
|
||||||
|
- Leere Listen duerfen nicht `null` sein.
|
||||||
|
- `isAlive` gibt `IHttpActionResult` mit `StatusCodeResult(HttpStatusCode.NoContent)` zurueck.
|
||||||
|
|
||||||
|
Healthcheck:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
[Route("isAlive"), HttpGet]
|
[Route("isAlive"), HttpGet]
|
||||||
@@ -183,32 +233,94 @@ public IHttpActionResult isAlive()
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Pickups/Enumerations
|
Nicht verwenden:
|
||||||
|
|
||||||
`IEntityDataService.GetEnumeration(...)` ist bequem, zieht aber die Pandora-Service-Schicht herein. Das war in der Sandbox zu schwer und verursachte transitive Dependency-Probleme.
|
```csharp
|
||||||
|
public void isAlive()
|
||||||
|
```
|
||||||
|
|
||||||
Bewaehrt ist:
|
Symptom:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"result":null,"parameters":{}}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Null-Rueckgaben
|
||||||
|
|
||||||
|
Matrix42/Pandora behandelt `null` als fehlerhafte Operation-Response.
|
||||||
|
|
||||||
|
Symptom:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Method getTicketList returned null
|
||||||
|
```
|
||||||
|
|
||||||
|
Regel:
|
||||||
|
|
||||||
|
- Listenendpoints geben bei "nichts gefunden" `new List<T>()` zurueck.
|
||||||
|
- Objekte geben ein leeres DTO oder 204 zurueck, aber nicht unbewusst `null`.
|
||||||
|
- Strings koennen bei Bedarf `string.Empty` statt `null` liefern.
|
||||||
|
|
||||||
|
## Routen und Parameterbindung
|
||||||
|
|
||||||
|
Wichtiges spaetes Learning: Der Matrix42 `m42Services`-Gateway kann Query-Parameter auch dann an die Methode binden, wenn die Controller-Route path-basierte Platzhalter hat.
|
||||||
|
|
||||||
|
Beispiel im Code:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Route("getDirectLinkF4SD/{eoid}/{type}"), HttpGet]
|
||||||
|
public async Task<string> getDirectLinkF4SD(Guid eoid, string type)
|
||||||
|
```
|
||||||
|
|
||||||
|
Funktionierende Legacy-URL:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/m42Services/api/c4itf4sdwebapi/getdirectlinkf4sd/?eoid=<guid>&type=user
|
||||||
|
```
|
||||||
|
|
||||||
|
Daraus folgt fuer diese Migration:
|
||||||
|
|
||||||
|
- Controller nicht doppeln.
|
||||||
|
- Keine Query-Helper einbauen.
|
||||||
|
- Kein `[FromUri]` als Rettungsversuch verwenden, wenn das Matrix42-Binding ohnehin funktioniert.
|
||||||
|
- Die Postman-Collection testet bewusst die alten Query-Routen, damit Client-Kompatibilitaet erhalten bleibt.
|
||||||
|
- `PLSLWebServiceOperation.RouteTemplate` darf path-basiert sein, die alten Query-Aufrufe muessen trotzdem getestet werden.
|
||||||
|
|
||||||
|
Betroffene alte Testformate:
|
||||||
|
|
||||||
|
```text
|
||||||
|
getTicketList?sid=...&hours=...&queueoption=...&queues=...
|
||||||
|
getDirectLinkCreateTicket?sid=...&assetname=...
|
||||||
|
getDirectLinkF4SD?eoid=...&type=...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pickups und Enumerations
|
||||||
|
|
||||||
|
`IEntityDataService.GetEnumeration(...)` ist bequem, zieht aber Pandora-Services herein. Das war fuer reine Pickups zu schwer.
|
||||||
|
|
||||||
|
Bewaehrt:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
Matrix42.Persistence.Contracts.IEnumerationProvider
|
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.
|
Dieser Service kommt ueber `Matrix42.DataLayer.Persistence` und liefert eine `DataTable`. Wenn der alte API-Vertrag `EntityEnumeration` bleiben soll, muss die `DataTable` selbst gemappt werden.
|
||||||
|
|
||||||
Gepruefte Alternativen:
|
Gepruefte Alternativen:
|
||||||
|
|
||||||
- `Matrix42.Pandora.Contracts.IEntityDataService`: liefert direkt `EntityEnumeration`, benoetigt aber Pandora-Module.
|
- `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.
|
- `update4u.SPS.DataLayer.Contracts.IPickupDataService`: liefert ebenfalls `DataTable`, ohne klaren Vorteil gegenueber `IEnumerationProvider`.
|
||||||
|
|
||||||
Pragmatische Empfehlung:
|
Empfehlung:
|
||||||
|
|
||||||
- Fuer reine Pickup-Lesefunktionen `IEnumerationProvider` verwenden.
|
- Fuer reine Pickup-Lesefunktionen `IEnumerationProvider` verwenden.
|
||||||
- Nur dann Pandora-Services laden, wenn es keine schlanke Alternative gibt.
|
- Pandora nur laden, wenn ein Endpoint es wirklich benoetigt.
|
||||||
- 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
|
## Aktueller Benutzer
|
||||||
|
|
||||||
`IPandoraUserProfile` nicht im Controller injizieren. Fuer `getMyRoleMemberships` reicht der Matrix42 Principal:
|
`IPandoraUserProfile` nicht im Controller injizieren.
|
||||||
|
|
||||||
|
Fuer `getMyRoleMemberships` reicht der Matrix42 Principal:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var principal = Thread.CurrentPrincipal as IM42Principal;
|
var principal = Thread.CurrentPrincipal as IM42Principal;
|
||||||
@@ -219,52 +331,81 @@ var userId = principal?.InteractivePrincipal?.M42Identity?.UserFragmentID
|
|||||||
|
|
||||||
Danach kann der vorhandene ASQL-/Helper-Code mit der `Guid` weiterarbeiten.
|
Danach kann der vorhandene ASQL-/Helper-Code mit der `Guid` weiterarbeiten.
|
||||||
|
|
||||||
## Journal-Service
|
## Ticket-Historie und Journal
|
||||||
|
|
||||||
`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.
|
Ziel: den Matrix42-Service nutzen, nicht eigene Journal-Logik nachbauen.
|
||||||
|
|
||||||
Status:
|
Finaler Ansatz:
|
||||||
|
|
||||||
- Ticket-Historie laeuft ueber `IJournalService`.
|
- `IJournalService` ueber den Matrix42-Resolver laden.
|
||||||
- Der Aufruf erfolgt per Reflection gegen die zur Laufzeit geladene `GetJournalList`-Signatur, damit 26.1 Patch-Level-Unterschiede keine direkte MethodRef brechen.
|
- Keine direkte Konstruktion von `JournalService`.
|
||||||
- Keine manuellen `JournalService`-Konstruktionen, Proxies, `JournalManager`-Fallbacks oder HTTP-Forwards verwenden.
|
- Keine eigenen DispatchProxy-/NoOp-Services.
|
||||||
- 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.
|
- Keine HTTP-Weiterleitung an interne Admin-APIs.
|
||||||
|
- Keine Storage-Fallback-Journal-Implementierung als finaler Pfad.
|
||||||
|
- `GetJournalList` per Reflection gegen die zur Laufzeit vorhandene Signatur aufrufen, weil sich die Signatur zwischen 26.1 Patch-Leveln unterscheiden kann.
|
||||||
|
|
||||||
## Typische Fehlersymptome und Ursache
|
Warum Reflection hier akzeptiert wurde:
|
||||||
|
|
||||||
```text
|
- Der alte direkte Compile-Time-MethodRef brach mit `MissingMethodException`.
|
||||||
Cannot create instance of <Controller>: The current type, <Interface>, is an interface and cannot be constructed.
|
- Die geladene Runtime-DLL hatte eine andere `IJournalService.GetJournalList`-Signatur.
|
||||||
```
|
- Die Reflection sucht kompatible Journal-Reader-Methoden und liefert klare Diagnose, wenn keine passende Signatur existiert.
|
||||||
|
|
||||||
Meistens direkte Konstruktor-Injection oder ein zu breit geladenes Host-Modul.
|
Typische Fehler auf dem Weg:
|
||||||
|
|
||||||
```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
|
```text
|
||||||
|
Method not found: IJournalService.GetJournalList(...)
|
||||||
|
IJournalService could not be resolved
|
||||||
|
IExternalAuthService is an interface and cannot be constructed
|
||||||
|
IOAuth2AuthorizeService is an interface and cannot be constructed
|
||||||
IApiExplorer is an interface and cannot be constructed
|
IApiExplorer is an interface and cannot be constructed
|
||||||
|
IRequestPropertiesProvider is an interface and cannot be constructed
|
||||||
|
IExternalSearchService 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`.
|
Learning: Bei diesen Fehlern nicht weiter im Endpoint herumprobieren, sondern die fehlende Host-Registrierung bzw. das passende Matrix42-Modul identifizieren.
|
||||||
|
|
||||||
```json
|
## Overview-Endpoints
|
||||||
{"Version":"1.1","Content":{...},"StatusCode":200}
|
|
||||||
|
`getTicketOverviewCounts` liefert Counts fuer mehrere Keys.
|
||||||
|
|
||||||
|
`getTicketOverviewRelations` nimmt genau einen `key` und ein `count`.
|
||||||
|
|
||||||
|
Postman-Learning:
|
||||||
|
|
||||||
|
- `getTicketOverviewCounts personal` und `getTicketOverviewCounts role` leiten `scope`, `overviewKey` und `overviewRelationExpectedCount` ab.
|
||||||
|
- `getTicketOverviewRelations` nutzt diese Variablen.
|
||||||
|
- Der Relations-Test erwartet, dass `response.length` dem vorher abgeleiteten Count entspricht.
|
||||||
|
- Im normalen Collection-Lauf ueberschreibt `role` die Werte von `personal`, weil `role` nach `personal` ausgefuehrt wird.
|
||||||
|
|
||||||
|
## Direct Link
|
||||||
|
|
||||||
|
Fuer F4SD Direct Links ist der Typ in der alten Integration nicht der Matrix42 ActivityType, sondern der F4SD-Typ.
|
||||||
|
|
||||||
|
Postman setzt deshalb nach `getTicketList`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
pm.collectionVariables.set("type", "ticket");
|
||||||
```
|
```
|
||||||
|
|
||||||
Ein `HttpResponseMessage` wurde als Nutzdatenobjekt serialisiert. Action-Signatur auf direkten Nutzdatentyp umstellen.
|
`objectType` bleibt separat auf `ticket.ActivityType`, falls der technische Matrix42-Typ fuer andere Tests gebraucht wird.
|
||||||
|
|
||||||
```json
|
## Postman-Collection
|
||||||
{"result":null,"parameters":{}}
|
|
||||||
```
|
|
||||||
|
|
||||||
Ein `void`-Endpoint wurde als Matrix42-Result ohne Nutzdaten serialisiert. Fuer echten NoContent `StatusCodeResult(HttpStatusCode.NoContent)` verwenden.
|
Die Collection ist Teil der Migration und Regression.
|
||||||
|
|
||||||
|
Regeln:
|
||||||
|
|
||||||
|
- Basic Auth ueber Variablen `basicUsername` und `basicPassword`.
|
||||||
|
- `baseUrl` enthaelt `/m42Services`.
|
||||||
|
- Legacy-Query-Routen testen, nicht nur neue path-basierte Formen.
|
||||||
|
- `getTicketList` zieht `eoid` und `ticketObjectId` aus `TicketObjectId`.
|
||||||
|
- `type` fuer Direct Link bleibt `ticket`.
|
||||||
|
- `isAlive` erwartet 204 und keinen Body.
|
||||||
|
- Allgemeiner Test erkennt versehentlich serialisierte `HttpResponseMessage`-Objekte.
|
||||||
|
|
||||||
## Smoke-Test-Reihenfolge
|
## Smoke-Test-Reihenfolge
|
||||||
|
|
||||||
Nach jeder Paketinstallation in Matrix42:
|
Nach jeder Paketinstallation:
|
||||||
|
|
||||||
1. `GET /m42Services/api/<prefix>/isalive`
|
1. `GET /m42Services/api/<prefix>/isalive`
|
||||||
Erwartung: HTTP 204 No Content.
|
Erwartung: HTTP 204 No Content.
|
||||||
@@ -272,26 +413,89 @@ Nach jeder Paketinstallation in Matrix42:
|
|||||||
Erwartung: JSON-Nutzdaten, keine `HttpResponseMessage`-Huelle.
|
Erwartung: JSON-Nutzdaten, keine `HttpResponseMessage`-Huelle.
|
||||||
3. `GET /m42Services/api/<prefix>/getMyRoleMemberships`
|
3. `GET /m42Services/api/<prefix>/getMyRoleMemberships`
|
||||||
Erwartung: aktueller Benutzer und Rollen, kein `IPandoraUserProfile`-Fehler.
|
Erwartung: aktueller Benutzer und Rollen, kein `IPandoraUserProfile`-Fehler.
|
||||||
4. Komplexere Ticket-/Overview-Endpoints.
|
4. `GET /m42Services/api/<prefix>/getTicketList?...`
|
||||||
5. Journal-/History-Endpoints separat pruefen, weil hier zusaetzliche Services benoetigt werden koennen.
|
Erwartung: leere Liste oder Tickets, aber kein `returned null`.
|
||||||
|
5. Direct Links mit Legacy-Query-Routen.
|
||||||
|
6. Overview Counts und Relations.
|
||||||
|
7. Journal-/History-Endpoints zuletzt, weil hier die meisten Matrix42-Services transitiv benoetigt werden.
|
||||||
|
|
||||||
|
## Typische Fehlersymptome
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cannot create instance of <Controller>: The current type, <Interface>, is an interface and cannot be constructed.
|
||||||
|
```
|
||||||
|
|
||||||
|
Meist direkte Konstruktor-Injection oder fehlendes Host-Modul.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Could not load type 'System.Web.Routing.RouteTable'
|
||||||
|
```
|
||||||
|
|
||||||
|
Ungeeignetes altes `System.Web`-/WebApi-Modul in der Sandbox. Ursache entfernen, nicht weitere Alt-Abhaengigkeiten nachladen.
|
||||||
|
|
||||||
|
```text
|
||||||
|
IApiExplorer is an interface and cannot be constructed
|
||||||
|
```
|
||||||
|
|
||||||
|
Sandbox-Host stellt diese WebApi-Explorer-Instanz nicht automatisch bereit. Ueber eigenen `IDependencyRegistrator` registrieren.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"Version":"1.1","Content":{...},"StatusCode":200}
|
||||||
|
```
|
||||||
|
|
||||||
|
Ein `HttpResponseMessage` wurde als DTO serialisiert. Rueckgabe auf direkten Nutzdatentyp umstellen.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"result":null,"parameters":{}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Void-Endpoint oder falsch behandelte leere Rueckgabe. Fuer NoContent `StatusCodeResult(HttpStatusCode.NoContent)` nutzen.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Method <name> returned null
|
||||||
|
```
|
||||||
|
|
||||||
|
Endpoint gibt `null` zurueck. Leeres DTO oder leere Liste zurueckgeben.
|
||||||
|
|
||||||
|
## Verworfene Zwischenansaetze
|
||||||
|
|
||||||
|
Diese Ansaetze haben nicht nachhaltig geholfen und sollen nicht erneut aufgebaut werden:
|
||||||
|
|
||||||
|
- Matrix42-Fachinterfaces direkt im Controller-Konstruktor injizieren.
|
||||||
|
- Immer mehr Module auf Vorrat laden, ohne die konkrete fehlende Registrierung zu kennen.
|
||||||
|
- `HttpResponseMessage` als Datenantwort verwenden.
|
||||||
|
- Query-Parameter per eigenem Helper aus `RequestUri` lesen.
|
||||||
|
- `[FromUri]` pauschal an alle GET-Parameter setzen.
|
||||||
|
- `JournalService` direkt konstruieren.
|
||||||
|
- NoOp-/Proxy-Abhaengigkeiten fuer Journal-Services erzeugen.
|
||||||
|
- Journal-Historie aus unvollstaendigen Kommentarfeldern nachbauen.
|
||||||
|
- Interne Matrix42 Admin-Webservices als finalen Journal-Pfad weiterleiten.
|
||||||
|
|
||||||
## Migrationsablauf fuer weitere Extensions
|
## Migrationsablauf fuer weitere Extensions
|
||||||
|
|
||||||
1. Alten Package-Inhalt entpacken und Assembly-/Extension-ID identifizieren.
|
1. Ziel-Commit der Legacy-Funktionalitaet festlegen.
|
||||||
2. Neues 26.1-Projekt ueber Scaffolder/Beispielstruktur anlegen oder bestehendes Projekt angleichen.
|
2. Altes Paket entpacken und IDs dokumentieren.
|
||||||
3. `Matrix42.WebApi.Contracts` und `Matrix42.Hosting.Contracts` referenzieren.
|
3. Herstellerbeispiel/Scaffolder-Struktur als 26.1-Referenz verwenden.
|
||||||
4. Controller auf `IDependencyResolver` umstellen.
|
4. Projekt auf .NET 8 und `Matrix42.WebApi.Contracts.ApiController` umstellen.
|
||||||
5. Direkte Konstruktor-Injection von Matrix42-Fachinterfaces entfernen.
|
5. Controller-Konstruktor auf minimale Infrastruktur-Services reduzieren.
|
||||||
6. Host-Konfig mit minimalen Modulen starten.
|
6. Host-Konfig minimal starten.
|
||||||
7. Rueckgaben pruefen: direkte Nutzdaten statt `HttpResponseMessage`, `StatusCodeResult` fuer 204.
|
7. Package-Build in MSBuild integrieren.
|
||||||
8. Package-Build ueber MSBuild automatisieren.
|
8. AssemblyVersion als einzige Package-Versionsquelle verwenden.
|
||||||
9. Paket installieren und Smoke-Tests ausfuehren.
|
9. WebAPI-Rueckgaben auf direkte DTOs/Listen umstellen.
|
||||||
10. Fehlende Services einzeln und begruendet nachladen, nie ganze Pandora-/ServiceManager-Stacks auf Vorrat.
|
10. `isAlive` auf echten 204 umstellen.
|
||||||
|
11. Pickups zuerst ueber schlanke Services loesen.
|
||||||
|
12. Komplexe Matrix42-Services erst bei Bedarf laden.
|
||||||
|
13. Bei Unity-Fehlern konkrete Implementierung/Registrator suchen.
|
||||||
|
14. Postman-Collection fuer alte Client-Routen aufbauen.
|
||||||
|
15. Paket installieren, Service synchronisieren und Smoke-Tests ausfuehren.
|
||||||
|
16. Erst nach erfolgreicher Kompatibilitaet fachliche Weiterentwicklungen einbringen.
|
||||||
|
|
||||||
## Git-/Build-Hinweise in diesem Repo
|
## Git- und Datei-Hinweise
|
||||||
|
|
||||||
- Vor Aenderungen `git status` pruefen.
|
- Vor Aenderungen `git status` pruefen.
|
||||||
- Geaenderte Textdateien mit CRLF speichern.
|
- Unrelated/untracked Dateien nicht mitcommitten.
|
||||||
- Nach Aenderungen Release-Build ausfuehren.
|
- Textdateien in diesem Repo mit CRLF speichern.
|
||||||
- Danach committen und pushen.
|
- `.dat`-Dateien behalten ihren UTF-8-BOM.
|
||||||
|
- Controller-/JSON-Dateien ohne unnoetigen BOM lassen.
|
||||||
|
- Debug-Build fuer Debugging, Release/Release_signed fuer Auslieferung.
|
||||||
|
- Postman-Collection nach JSON-Aenderungen immer parsen/validieren.
|
||||||
|
|||||||
Reference in New Issue
Block a user