Development 29. September 2026 7 min Lesezeit

Server Logic aus Liquid aufrufen: der Full Circle in Power Pages

Der serverlogic-Liquid-Tag rendert wiederverwendbare Server-Logik zur Render-Zeit, ohne Client-Call, ohne CSRF. Der N:M-Read aus Teil eins, geschlossen. Live verifiziert.

Das ist der dritte und letzte Teil eines kleinen Bogens ums Lesen einer Many-to-many-Beziehung in Power Pages, und er schließt den Kreis auf befriedigende Weise.

Teil eins las N:M mit Liquid FetchXML: serverseitig gerendert, aber beim Seitenaufbau vorgeladen. Teil zwei verlagerte denselben Read in Server Logic, on demand vom Client aufgerufen, keine Abfrage im Browser, dafür ein Client-Roundtrip mit CSRF-Token. Jede Seite hatte ihren Preis.

Der serverlogic-Liquid-Tag nimmt beide Preise auf einmal weg. Dieselbe serverseitige Operation aus Teil zwei, aufgerufen aus Liquid, während die Seite rendert. Kein Client-Call, kein CSRF-Token, und die Abfrage berührt den Browser trotzdem nie. Microsoft hat das 2026 ausgeliefert, und es ist genau das Stück, das aus dem Ganzen einen Kreis macht: wiederverwendbare, testbare Server-Logik, direkt in die Seite gerendert wie damals das FetchXML.

Der Tag

{% serverlogic name: 'installation-products',
             operation: 'getInstallationProducts',
             output: result %}

{% if result.success %}
  <ul>
  {% for inst in result.data.installations %}
    <li>{{ inst.name | escape }}</li>
  {% endfor %}
  </ul>
{% endif %}

Vier Parameter: name (der Server-Logic-Record), operation (die auszuführende JavaScript-Funktion, jede von Ihnen definierte, nicht nur get), input (optionaler String, JSON wenn Sie Struktur brauchen) und output (die Liquid-Variable, die das Ergebnis erhält). Das Ergebnisobjekt trägt success, status_code (500 bei Ausführungsfehler), data (das geparste JSON Ihrer Operation) und raw_result (die ungeparste Antwort, falls der Rückgabewert kein gültiges JSON ist).

Die entscheidende Zeile aus Microsofts Doku: dieser serverseitige Aufruf "doesn't require a client-side HTTP request or Cross-Site Request Forgery (CSRF) token." Weisen Sie den Server-Logic-Record einer Web Role zu, auf die der aktuelle Nutzer Zugriff hat, den Rest erledigt der Tag zur Render-Zeit.

Der Full Circle, live

Ich habe genau das im Demo-Portal gebaut: ein Web Template mit dem obigen Tag, das eine Operation aufruft, die die N:M-Beziehung aus Teil eins liest und eine kompakte Liste zurückgibt. Als angemeldeter Portal-Nutzer rendert die Seite die verknüpften Produkte serverseitig:

Ein Power-Pages-Web-Template nutzt den serverlogic-Liquid-Tag und rendert die N:M-Liste Installation-zu-Produkt serverseitig, result.success true, status_code 200

Dieselben Daten wie in Teil eins (Pump A, Valve B unter einer Installation), derselbe serverseitige Dataverse-Connector wie in Teil zwei, jetzt direkt von Liquid in die Seite gerendert. Das ist der geschlossene Kreis.

Eine Funktion, zwei Kontexte

Hier ist das Detail, das Ihnen einen Nachmittag spart. Dieselbe Server-Logic-Funktion lässt sich auf zwei Wegen aufrufen, vom Client über HTTP und aus Liquid, aber sie liest ihre Eingabe aus zwei verschiedenen Quellen:

  • Über HTTP aufgerufen: Server.Context.QueryParameters["id"], dazu Body, Headers, HttpMethod.
  • Aus Liquid aufgerufen: Server.Context.Input, der String, den Sie im input-Parameter des Tags übergeben haben.

Wenn Sie eine Operation für beide Einstiegspunkte schreiben, verzweigen Sie nach der Eingabequelle. QueryParameters ist bei einem Liquid-Aufruf leer, und Input ist über HTTP leer.

Zwei Dinge, die die Doku nicht ausbuchstabiert

Ich bin beim Bauen des Live-Beispiels über beide gestolpert, damit Sie es nicht müssen.

Der Connector liefert einen Envelope. Wenn Sie das Ergebnis innerhalb der Operation verarbeiten (statt es durchzureichen), gab mir Server.Connector.Dataverse.RetrieveMultipleRecords einen String der Form { "StatusCode": 200, "Body": "..." }, wobei Body die OData-JSON als String ist. Sie parsen zweimal: den Envelope, dann Body, dann erreichen Sie value. Die Doku-Beispiele geben das Connector-Ergebnis direkt zurück und zeigen das nie, deshalb bekommen Sie beim ersten Versuch, über result.value zu iterieren, nichts.

function getInstallationProducts() {
  var options = "$select=pwrprtl_installation_name" +
    "&$expand=pwrprtl_installation_product($select=pwrprtl_product_name)";
  var res = Server.Connector.Dataverse.RetrieveMultipleRecords("pwrprtl_installations", options, true);
  var envelope = (typeof res === "string") ? JSON.parse(res) : res;
  var odata = (typeof envelope.Body === "string") ? JSON.parse(envelope.Body) : envelope.Body;
  var rows = (odata && odata.value) ? odata.value : [];

  var installations = [];
  for (var i = 0; i < rows.length; i++) {
    var prods = rows[i].pwrprtl_installation_product || [];
    var names = [];
    for (var j = 0; j < prods.length; j++) { names.push(prods[j].pwrprtl_product_name); }
    installations.push({ name: rows[i].pwrprtl_installation_name, products: names });
  }
  return JSON.stringify({ installations: installations });
}

Kompilierte Server Logic wird gecacht. Nachdem Sie den Code geändert haben, liefern der Endpunkt und der Liquid-Tag weiter die vorige Version, bis der Site-Cache geleert wird (/_services/about, Clear cache, mit Administrator-Web-Rolle). Wenn eine Änderung nicht zu greifen scheint, ist es der Cache, nicht Ihr Code.

Sicherheit: kein CSRF heißt nicht keine Verantwortung

Auf das CSRF-Token zu verzichten ist hier sicher, weil der Aufruf den Server nie verlässt; es gibt keine Cross-Site-Anfrage, die man fälschen könnte. Aber die Operation läuft weiter im Kontext des aktuellen Nutzers und respektiert Web Roles und Tabellenberechtigungen, und die Autorisierungsentscheidungen liegen weiter bei Ihnen. Microsoft ist deutlich: Behandeln Sie Eingabe und Rückgaben als untrusted. Validieren Sie Server.Context.Input, bevor er eine Abfrage berührt, und wenden Sie den escape-Filter an, bevor Sie einen zurückgegebenen String in HTML rendern, genau wie im Tag oben.

Und der eine echte Preis: der Tag läuft während des Seitenaufbaus, eine langsame Dataverse-Abfrage oder ein externer Call erhöht also die Page-Response-Zeit. Das ist der Handel fürs serverseitige Rendern. Wenn Reaktionsschnelligkeit wichtiger ist als der erste Seitenaufbau, bleiben Sie beim Client-Call aus Teil zwei.

Drei serverseitige Wege, eine Entscheidung

Sie wollen Nutzen Sie
N:M (oder jeden Read) beim Seitenaufbau gerendert, einfache Abfrage Liquid FetchXML (link-entity / intersect)
Wiederverwendbare/testbare Logik beim Seitenaufbau gerendert {% serverlogic %}-Tag (Server.Context.Input)
Dieselbe Logik on demand, nach dem Laden Server Logic über HTTP (safeAjax, CSRF, QueryParameters)
N:M schreiben (verknüpfen / trennen) Portal Web API $ref

FetchXML ist der schnellste Weg für eine einfache Abfrage. Sobald die Logik wiederverwendbar oder testbar sein soll, gibt Ihnen {% serverlogic %} das zur Render-Zeit, und dieselbe Operation beantwortet nach dem Laden einen Client-Call. Drei Werkzeuge, ein Denkmodell: entscheiden Sie danach, wann der Nutzer die Daten braucht und wie viel Logik dahintersteckt. Das ist der Bogen, geschlossen.

Verwandte Artikel

Quellen

Tino Rabe

Tino Rabe

Power Pages Spezialist · Former Microsoft MVP

Power Pages Spezialist, former Microsoft MVP. Ich unterstütze Unternehmen bei sicheren Kundenportalen: vom Architektur-Workshop über Coaching bis zum Security-Audit.

Wann wurde Ihr Portal zuletzt unabhängig geprüft?

Security-Audit zum Festpreis, oder einfach erstmal sprechen.

Termin buchen