Im vorigen Beitrag habe ich eine Many-to-many-Beziehung mit Liquid FetchXML gelesen. Das funktioniert, aber die Daten werden beim Seitenaufbau geholt: vorgeladen, ob der Besucher sie je ansieht oder nicht.
Manchmal wollen Sie das Gegenteil, nämlich die verknüpften Datensätze on demand laden, erst wenn jemand eine Zeile aufklappt oder ein Detailpanel öffnet, ohne kompletten Reload und ohne die Abfrage im Browser offenzulegen. Genau dafür ist Server Logic da. Und sie beantwortet eine Frage, die der FetchXML-Beitrag offengelassen hat: Kann ein serverseitiger Aufruf N:M über $expand lesen, also über den bequemen Weg, an dem die Portal Web API stolpert? Ich habe es live getestet. Er kann.
Was Server Logic ist
Mit Server Logic führen Sie JavaScript serverseitig als Teil der Site-Laufzeit aus. Sie ist seit April 2026 allgemein verfügbar (Public Preview seit Oktober 2025). Code und Konfiguration liegen in Dataverse, wandern also mit Ihren Solutions und Pipelines wie jede andere Power-Pages-Komponente.
Zwei Eigenschaften sind hier entscheidend:
- Sie läuft serverseitig, der Browser sieht weder die Abfrage noch die Verbindung. Sie schreiben natives JavaScript (ECMAScript 2023) ohne Browser-APIs: kein DOM, kein
fetch, keinXMLHttpRequest. - Sie ist an Web Roles und Tabellenberechtigungen gebunden, genau wie der Rest des Portals. Sie ist keine erhöhte Hintertür. Dazu unten mehr, denn das ist der Teil, den viele falsch einschätzen.
Angelegt wird sie im Design Studio unter Einrichten, Server logic, den Code bearbeiten Sie dann in Visual Studio Code mit IntelliSense und Kompilierprüfung. Jede HTTP-Methode entspricht einer Funktion, die Sie definieren: get(), post(), put(), patch(), del().
N:M aus Server Logic lesen
Innerhalb von get() sprechen Sie Dataverse über das Objekt Server.Connector.Dataverse an. RetrieveMultipleRecords nimmt den Entity-Set-Namen und einen OData-Optionsstring:
function get() {
try {
var options =
"$select=pwrprtl_installation_name" +
"&$expand=pwrprtl_installation_product($select=pwrprtl_product_name)";
return Server.Connector.Dataverse.RetrieveMultipleRecords(
"pwrprtl_installations",
options
);
} catch (err) {
Server.Logger.Error("GET failed: " + err.message);
return JSON.stringify({ status: "error", message: err.message });
}
}
Die Microsoft-Doku zeigt für diese Methode nur $select und $top. Ob $expand über eine N:M-Navigation-Property funktioniert, ist nicht dokumentiert. Deshalb habe ich genau das im Demo-Portal aufgebaut (dieselben zwei benutzerdefinierten Tabellen Installation und Product mit N:M-Beziehung aus dem letzten Beitrag) und aufgerufen.
Das Ergebnis kommt sauber verschachtelt zurück:
Eine Installation, ihre zwei verknüpften Produkte (Pump A, Valve B), verschachtelt unter pwrprtl_installation_product, in einem einzigen Aufruf. Das ist genau der bequeme Lesezugriff, den die Portal Web API unter den Berechtigungs-Scopes Contact und Account als Known Issue dokumentiert. Server Logic setzt die Abfrage über den serverseitigen Dataverse-Connector ab, nicht über die Anfragepipeline der Portal Web API, damit ist dieses Problem hier vom Tisch.
Aufruf aus der Seite, on demand
Der Client ruft die Logik über ihren Namen unter /_api/serverlogics/<name> auf. Nutzen Sie den safeAjax-Wrapper des Portals, damit das Anti-Forgery-Token mitgeschickt wird:
webapi.safeAjax({
type: "GET",
url: "/_api/serverlogics/installation-products",
contentType: "application/json",
success: function (res) {
// res.data enthält die Server-Logic-Antwort; parsen und rendern
}
});
Das hängen Sie an einen Zeilen-Aufklapp-Handler oder einen Detail-Button. Nichts von der Abfrage liegt im Browser: Der Client sieht einen benannten Endpunkt und ein Ergebnis, nicht die Tabellennamen oder den Optionsstring. (Sie können Logik auch aus Liquid mit dem Tag {% serverlogic %} aufrufen, aber das läuft beim Seitenaufbau und bringt genau das Vorladen zurück, das Sie vermeiden wollten.)
Sicherheit: berechtigungsgebunden, und die Autorisierung ist Ihre Aufgabe
Server Logic läuft im Sicherheitskontext des Aufrufers und respektiert dessen Web Role und Tabellenberechtigungen. Das hat eine bequeme und eine scharfe Seite.
Die bequeme Seite: Ein nicht angemeldeter Besucher, oder einer, dessen Web Role keine Leseberechtigung auf den beteiligten Tabellen hat, bekommt nichts zurück. Die Leeres-Ergebnis-Falle aus dem FetchXML-Beitrag gilt auch hier. Wenn der Aufruf nichts liefert, prüfen Sie zuerst, als wer die Seite läuft, bevor Sie den Code auseinandernehmen.
Die scharfe Seite: berechtigungsgebunden heißt nicht sicher. Sobald Sie einen Parameter vom Client annehmen, etwa einen Entity-Set-Namen oder eine Datensatz-ID, tragen Sie die Autorisierungsentscheidung. Validieren Sie jede Eingabe, geben Sie clientgelieferte Werte niemals ungeprüft in eine Abfrage, und erzwingen Sie Tabellenberechtigungen (oder eine gleichwertige Prüfung) für die konkret betroffenen Datensätze. Serverseitig heißt nicht automatisch vertrauenswürdig.
Grenzen, die Sie vorher kennen sollten
- Kein
fetch,eval,require; unsichere Muster sind blockiert. Für externe Systeme nutzen Sie den bereitgestelltenHttpClient, und ein Administrator kann externe HTTP-Aufrufe ganz abschalten. - Die Ausführung hat ein Timeout: standardmäßig 120 Sekunden, maximal 240 Sekunden. Server Logic ist für fokussierte Operationen gedacht, nicht für lange Batch-Läufe.
- Der Aufruf aus Liquid mit
{% serverlogic %}verlängert den Seitenaufbau und untergräbt damit das On-demand-Ziel. Bleiben Sie bei Client-Aufrufen, wenn Sie Reaktionsschnelligkeit wollen.
Welcher Weg für welche Aufgabe
| Aufgabe | Empfohlener Weg |
|---|---|
| Lesen N:M, Liste beim Seitenaufbau sichtbar | Liquid FetchXML (link-entity / intersect) |
| Lesen N:M, on demand (Zeile aufklappen, Detailpanel) | Server Logic + Server.Connector.Dataverse ($expand) |
| Schreiben N:M (verknüpfen / trennen) | Portal Web API $ref (POST / DELETE) |
| Lesen 1:N / N:1 | Portal Web API $expand (eine Ebene) oder Liquid |
FetchXML und Server Logic sind weniger Konkurrenten als zwei Zeitpunkte desselben Lesezugriffs: vorgeladen gegen on demand. Entscheiden Sie danach, wann der Nutzer die Daten wirklich braucht, und halten Sie die Autorisierungsdisziplin für beide gleich streng.
Verwandte Artikel
N:M lesen: Web API vs. FetchXML
Der Vorgänger: Was die Portal Web API mit N:M kann, und der Liquid-FetchXML-Workaround.
Artikel lesen → DevelopmentLiquid FetchXML vs. Web API
Der grundlegende Vergleich der beiden Datenzugriffswege: Entscheidungsbaum, Performance, Sicherheit.
Artikel lesen → SecurityPower Pages Security-Architektur
Tabellenberechtigungen, Web Roles und Scopes: das Modell hinter dem Autorisierungspunkt in diesem Beitrag.
Artikel lesen →Quellen
- Microsoft Learn: Server logic overview (allgemeine Verfügbarkeit, API-URL, Zuordnung HTTP-Methode zu Funktion, "protected by web roles and table permissions")
- Microsoft Learn: Server objects (
Server.Connector.Dataverse.RetrieveMultipleRecords) - Microsoft Learn: Interact with Dataverse tables using server logic (das Methoden-Grundgerüst und der safeAjax-Client-Aufruf)
- Microsoft Learn: Author server logic (Grenzen: kein fetch/eval, Timeout, blockierte Muster)
- Microsoft Learn: Overview of the portals Web API (das N:M-GET-Known-Issue und der FetchXML-Workaround)
- Hands-on im Power-Portals-Demo-Portal: eine Server Logic, die die N:M-Beziehung per
$expandliest, live verifiziert (HTTP 200, verschachtelte verknüpfte Datensätze)