Beide existieren, beide funktionieren — und trotzdem taucht in fast jedem Power Pages Projekt irgendwann die gleiche Frage auf: Liquid FetchXML oder Web API? Wer die Antwort mit "kommt drauf an" abtut, hat recht, hilft aber niemandem. Dieser Artikel gibt dir einen konkreten Entscheidungsbaum, erklärt die technischen Unterschiede und zeigt, wann der Hybrid-Ansatz die sauberste Lösung ist.
Liquid FetchXML: Server-Side Rendering
Liquid FetchXML ist der ältere der beiden Ansätze — und oft der bessere. Der Mechanismus ist einfach: Beim Rendern der Seite führt Power Pages den FetchXML-Query serverseitig aus und schreibt das Ergebnis direkt in den HTML-Output. Wenn der Browser die Seite empfängt, sind die Daten bereits im DOM.
{% fetchxml accounts_query %}
<fetch top="50">
<entity name="account">
<attribute name="name" />
<attribute name="accountid" />
<filter>
<condition attribute="statecode" operator="eq" value="0" />
</filter>
<link-entity name="contact" from="accountid"
to="accountid" link-type="inner">
<filter>
<condition attribute="contactid" operator="eq"
value="{{ user.id }}" />
</filter>
</link-entity>
</entity>
</fetch>
{% endfetchxml %}
{% for account in accounts_query.results.entities %}
<div>{{ account.name }}</div>
{% endfor %}
Vorteile
Kein CORS, kein CSRF
Da alles serverseitig passiert, gibt es keine Cross-Origin-Probleme und du brauchst keinen Anti-Forgery-Token. Der Request läuft nicht vom Browser, sondern vom Portal-Server selbst.
N:M-Beziehungen kein Problem
Beide nutzen dasselbe Table Permissions Modell. Der Unterschied: FetchXML traversiert N:M-Beziehungen direkt im Query per link-entity — der Scope der Permission spielt dabei keine Rolle. Die Web API übersetzt Scope-Bedingungen in OData-Filter, was bei N:M-Traversal mit Parental-, Contact- oder Account-Scope zu CDS-Fehlern führt (Workaround: FetchXML-Parameter im OData-Query).
Schnellerer Initial Load
Daten kommen mit dem ersten HTML-Response. Keine Extra-Requests, kein Flackern, kein Spinner. Time to Interactive ist messbar niedriger — besonders bei großen Datensätzen.
SEO-freundlich
Suchmaschinen-Crawler sehen die Daten direkt im HTML. Was über JavaScript nachgeladen wird, kann von Crawlern ignoriert werden — besonders kritisch für Produktseiten oder öffentliche Verzeichnisse.
Nachteile
Kein dynamisches Nachladen
Was beim Page Load nicht da ist, bleibt nicht da — ohne einen vollständigen Page Reload.
Nur Lesen — kein Create, Update, Delete
Mit Liquid kannst du Daten lesen (auch über komplexe Beziehungen), aber nicht schreiben, updaten oder löschen. Für Mutationen brauchst du immer die Web API.
Template-Komplexität
Verschachtelte Beziehungen, bedingte Logik und Transformationen werden in Liquid schnell unübersichtlich.
Web API: Client-Side Data Access
Die Power Pages Web API ist ein OData-kompatibler REST-Endpunkt unter /_api/. Anfragen laufen vom Browser aus — nach dem Page Load, ausgelöst durch User-Interaktionen oder Timer. Das macht sie zur richtigen Wahl für alles, was dynamisch ist.
webapi.safeAjax({
type: "GET",
url: "/_api/accounts?$select=name,accountid&$filter=statecode eq 0",
contentType: "application/json"
}).done(function(data) {
data.value.forEach(function(account) {
console.log(account.name);
});
}).fail(function(jqXHR) {
console.error("Fehler:", jqXHR.status);
});
Wichtig: webapi.safeAjax() ist die Power Pages eigene Wrapper-Funktion. Sie setzt automatisch den Anti-Forgery-Token (__RequestVerificationToken), der für alle schreibenden Operationen (POST, PATCH, DELETE) verpflichtend ist. Direkte $.ajax()-Aufrufe ohne diesen Token scheitern mit 403.
Vorteile
Vollständige CRUD-Unterstützung
GET, POST, PATCH, DELETE — alles ist möglich. Dazu Associate und Disassociate für N:M-Beziehungen über den $ref-Endpunkt.
Dynamisch ohne Reload
Inhalte aktualisieren sich, Filter greifen sofort, Formulare speichern ohne Seitenwechsel. Moderne UX-Patterns wie Infinite Scroll oder Live-Updates sind nur so umsetzbar.
Bekannte Technologie
OData ist ein etablierter Standard. Entwickler mit Dataverse-Erfahrung kennen die Query-Syntax. Debugging im Browser-DevTools ist einfacher als Liquid-Fehlersuche.
Selektive Datenlast
Du kannst gezielt nur die Daten nachladen, die gerade gebraucht werden — statt alles beim Page Load zu rendern.
Nachteile
Table Permissions + Known Issue bei N:M
Table Permissions und Site Setting (Webapi/<entity>/enabled = true) sind zwingend. Bei N:M-Lesezugriffen mit Parental-, Contact- oder Account-Scope gibt es ein bekanntes CDS-Fehlerproblem — Workaround: FetchXML-Parameter im OData-Query oder disableodatafilter = true.
n+1 Request-Problem
Jeder API-Call ist ein eigener HTTP-Request. Bei vielen Datenpunkten beim Page Load summiert sich das zu merklicher Latenz — und der User sieht Spinner statt Inhalte.
Der Entscheidungsbaum
Vier Fragen reichen in der Praxis aus, um die richtige Methode zu wählen. Beantworte sie der Reihe nach:
Sonderfall N:M lesen: Die Web API hat laut offizieller Microsoft-Dokumentation ein bekanntes Problem: GET-Requests auf Tabellen mit N:M-Table Permissions schlagen fehl, wenn der Scope Parental, Contact oder Account ist — der Portal-Server erzeugt einen CDS-Fehler. Microsofts empfohlene Lösung ist, FetchXML als Parameter im OData-Query zu verwenden (/_api/entity?fetchXml=...) statt OData-Filterung. Als Alternative existiert das Site Setting Webapi/<table>/disableodatafilter = true, das jedoch Performance-Einbußen mit sich bringt. Liquid FetchXML umgeht dieses Problem vollständig — kein Workaround nötig.
Der Hybrid-Ansatz: Das Beste aus beiden Welten
In der Praxis sind die meisten anspruchsvollen Power Pages Seiten Hybride. Das Muster ist simpel und bewährt: Liquid lädt Daten beim Page Load in eine JavaScript-Variable — die Web API übernimmt nur Mutationen.
Ein konkretes Beispiel: Eine Seite zeigt alle Produkte, die einem Incident zugeordnet sind (N:M). Der Nutzer kann Produkte hinzufügen und entfernen. Ohne Hybrid-Ansatz würde man bei jedem Klick einen Full-Reload auslösen oder kämpft mit Global Permissions. Mit dem Hybrid-Pattern geht es sauber:
{% fetchxml products_query %}
<fetch>
<entity name="product">
<attribute name="name" />
<attribute name="productid" />
<attribute name="productnumber" />
<filter>
<condition attribute="statecode" operator="eq" value="0" />
</filter>
</entity>
</fetch>
{% endfetchxml %}
<script>
// Alle verfügbaren Produkte – ein Request, kein Spinner
var AVAILABLE_PRODUCTS = [
{% for p in products_query.results.entities %}
{ id: "{{ p.productid }}", name: "{{ p.name | escape }}", number: "{{ p.productnumber | escape }}" }{% unless forloop.last %},{% endunless %}
{% endfor %}
];
</script>
// Produkt einem Incident zuordnen (N:M Associate)
function associateProduct(incidentId, productId) {
return webapi.safeAjax({
type: "POST",
url: "/_api/incidents(" + incidentId + ")/product_incidents/$ref",
contentType: "application/json",
data: JSON.stringify({
"@odata.id": "/_api/products(" + productId + ")"
})
});
}
// Zuordnung entfernen (N:M Disassociate)
function disassociateProduct(incidentId, productId) {
return webapi.safeAjax({
type: "DELETE",
url: "/_api/incidents(" + incidentId + ")/product_incidents(" + productId + ")/$ref"
});
}
// JavaScript filtert die preloaded Daten — kein Extra-Request
function renderFilteredProducts(searchTerm) {
return AVAILABLE_PRODUCTS.filter(function(p) {
return p.name.toLowerCase().includes(searchTerm.toLowerCase());
});
}
Das Ergebnis: Der initiale Page Load ist schnell (ein einziger Server-Request via Liquid), clientseitiges Filtern und Suchen braucht kein Netzwerk, und nur die tatsächlichen Änderungen (Associate/Disassociate) gehen über die Web API. Table Permissions werden nur für die Mutation konfiguriert — nicht für das Lesen der N:M-Daten.
Performance im Vergleich
Die Performance-Implikationen sind direkter als die meisten Entwickler erwarten. Die entscheidende Metrik ist Time to Interactive — wann kann der Nutzer tatsächlich mit der Seite arbeiten?
| Szenario | Liquid FetchXML | Web API |
|---|---|---|
| 100 Datensätze beim Page Load | 1 Request (HTML) | 2 Requests (HTML + API) |
| Daten sichtbar ab | Sofort (im HTML) | Nach API-Response |
| Clientseitiges Filtern/Suchen | Sofort (kein Netzwerk) | Extra-Request je Suche |
| Daten nach User-Aktion updaten | Page Reload nötig | Kein Reload |
| Große Datenmenge (1.000+ Zeilen) | Langsamer Page Load | Pagination möglich |
Faustregel: Bis ca. 200 Datensätze ist Liquid beim initialen Load klar im Vorteil. Darüber hinaus oder wenn Pagination gefragt ist, lohnt sich die Web API — idealerweise kombiniert mit einem Liquid-Preload der ersten Seite.
Security im Vergleich
Beide Ansätze — Liquid FetchXML und Web API — nutzen dasselbe Table Permissions Modell. Es gibt kein separates Permission-System je nach Methode. Der Unterschied liegt darin, wie die Permission-Prüfung technisch umgesetzt wird und welche Konsequenzen das für bestimmte Szenarien hat.
Liquid FetchXML
Table Permissions — serverseitige Query-Filterung
- + Permission-Check: Darf der Nutzer die Zieltabelle lesen?
- + Beziehungsfilterung übernimmt FetchXML selbst — Scope-Grenzen greifen bei N:M-Traversal nicht ein
- + Alle Scopes (Global, Account, Contact, Self, Parental) funktionieren ohne Workaround
- ~ Konfiguration in Power Pages Studio unter "Sicherheit"
Web API
Table Permissions — als OData-Filter übersetzt
- + CSRF-Token-Schutz automatisch via safeAjax
- + Einfach zu konfigurieren für Standard-CRUD
- - Scope-Bedingungen werden als OData-Filter in die Query injiziert — bei N:M mit Parental/Contact/Account-Scope Known Issue (CDS-Fehler)
- - Site Settings pro Tabelle erforderlich (
Webapi/entity/enabled)
Sicherheitswarnung: Global Table Permissions sind häufig die "schnelle Lösung" für das N:M-Problem mit der Web API — und ein ernstes Sicherheitsrisiko. Sie geben authentifizierten Nutzern Lesezugriff auf alle Datensätze der Tabelle, unabhängig von Beziehungen. Microsofts empfohlener Weg ist stattdessen der Einsatz von FetchXML als Parameter im OData-Query, oder Liquid FetchXML als einfachste Option ohne Workaround. Mehr zu den Sicherheitsebenen im Artikel zur Power Pages Sicherheitsarchitektur.
Quick Reference: Wann was?
| Use Case | Empfehlung | Begründung |
|---|---|---|
| Initial Data Load | Liquid | 1 Request, sofort im DOM, SEO-kompatibel |
| N:M-Beziehung lesen | Liquid empfohlen | Web API möglich, aber Known Issue mit engen Scopes — FetchXML-Parameter als Workaround nötig |
| N:M-Beziehung schreiben | Web API | $ref-Endpunkt für Associate / Disassociate |
| CRUD nach User-Interaktion | Web API | Kein Page Reload, modernes UX-Pattern |
| Dropdown / Lookup befüllen | Liquid | Referenzdaten ändern sich selten, Preload effizienter |
| Infinite Scroll / Pagination | Web API | $top / $skip für seitenweises Laden |
| SEO-relevante Daten | Liquid | Crawler sehen server-gerenderten HTML-Inhalt |
| Live-Updates (Polling) | Web API | Intervall-basierte API-Calls ohne Reload |
| Clientseitiges Filtern / Suchen | Hybrid | Liquid preloaded alle Daten, JS filtert ohne Netzwerk |
Die Cheat Sheets zu Power Pages Web API und Liquid Templates findest du im Download-Bereich.
Power Pages Architektur-Fragen?
Liquid vs. Web API ist eine von vielen Architekturentscheidungen, die über Erfolg oder Frust in Power Pages Projekten entscheiden. Im Erstgespräch schaue ich mir deine konkrete Situation an.
Kostenloses Erstgespräch buchen