Power Pages JSON-Konfiguration: Liquid, React, JavaScript
Power Pages Branchen-Konfiguration verbindet Liquid, JavaScript und React. Stabile IDs, unabhängige Prüfungen und serverseitige Allowlists sichern den Vertrag.
Inhalt
- Warum erzeugt eine editierbare Power Pages Liste mehrere Verträge?
- Wie wird ein Power Pages Web Template zur Browserquelle?
- Wie erzeugt Liquid einen sicheren Power Pages JSON-Vertrag?
- Wie wird JSON im Power Pages HTML-Attribut sicher ausgegeben?
- Warum muss React Power Pages Konfiguration als unknown lesen?
- Wie reagiert React auf fehlende Power Pages Laufzeitkonfiguration?
- Wie trennt Power Pages Branchen-JSON Gruppierung und Identität?
- Wie prüft React eine vollständige Branchen-ID-Partition?
- Wie prüfen React und JavaScript dieselbe Power Pages Konfiguration?
- Was bedeutet Fail Closed für Power Pages Consumer?
- Wie kommt die Locale in der Power Pages Konfiguration aus dem Dokument?
- Warum bleiben Power Pages Labels reine Darstellung?
- Warum besitzt Power Pages Server Logic eine eigene Allowlist?
- Wie verhindert eine Änderungsmatrix blinde Konfigurationsupdates?
- Welche Werte besitzt jede Power Pages Konfigurationsschicht?
- Warum bleiben unbekannte Power Pages Konfigurationseigenschaften wirkungslos?
- Wo lässt sich das Power Pages Konfigurationsmuster wiederverwenden?
- Warum ist Power Pages Konfiguration keine Sicherheitsgrenze?
- Power Pages JSON-Konfiguration: Erfahrungen aus dem Enterprise-Projekt
Warum erzeugt eine editierbare Power Pages Liste mehrere Verträge?
Eine Branchenliste wirkt zunächst wie redaktioneller Inhalt.
Ein Label ändert sich. Eine Branche wechselt in eine andere Gruppe. Ein neues Angebot kommt hinzu.
In einer Power Pages Anwendung betrifft diese Änderung aber nicht nur Text. Die Landingpage baut Links. React stellt Auswahlfelder dar. Die URL speichert IDs. Power Pages Server Logic prüft den Browser-Request. Dataverse verarbeitet nur bestätigte Parameter.
Wenn alle Schichten denselben kopierten JSON-Block besitzen, driften sie auseinander. Wenn nur der Browser eine gemeinsame Liste besitzt, fehlt die verbindliche serverseitige Prüfung.
Ich trenne deshalb editierbare Gruppierung, stabile Identität und Sicherheitsregeln.
Die Architekturübersicht der Power Pages Enterprise-Suche zeigt den vollständigen Datenfluss. Dieser Beitrag konzentriert sich auf den Konfigurationsvertrag zwischen Liquid, HTML, JavaScript und React.
Wie wird ein Power Pages Web Template zur Browserquelle?
Power Pages Web Templates sind für diesen Vertrag geeignet. Sie liegen zentral im Portal und können von mehreren Seiten eingebunden werden.
Microsoft beschreibt Power Pages Web Templates als wiederverwendbare Liquid Inhalte. Ein aufrufendes Template kann Parameter über den dokumentierten Liquid include Tag übergeben.
Ich nutze zwei getrennte JSON-Kanäle.
Der erste Kanal liegt im Attribut data-config. Er enthält nur den kleinen Laufzeitvertrag für das Widget.
Der zweite Kanal ist ein application/json Element. Er enthält die editierbare Gruppierung bekannter Branchen-IDs.
Aspekt | data-config | application/json |
|---|---|---|
Inhalt | Kleiner Laufzeitvertrag des Widgets | Editierbare Cluster und bekannte Branchen-IDs |
Beispiele |
|
|
Ausgabe | HTML Attribut mit Liquid | Nicht ausführbarer JSON Block |
Lesender Consumer | React Bootstrap über | React und Landingpage JavaScript über |
Vertrauensniveau | Nicht vertrauenswürdige Browsereingabe | Nicht vertrauenswürdige Browsereingabe |
{% include 'Industry Cluster Config' %}
<div data-widget="portal-search" data-config="{{ config | escape }}"></div>
<script src="/portal-search.js" defer></script>Die Reihenfolge ist verbindlich. Das Cluster JSON steht vor dem Bundle. React kann es beim Start sofort lesen.
Der vorhandene Beitrag zur modularen React Einbindung mit Web Templates und JSON-Konfiguration erklärt den Host, den Mount Point und den Bundle Pfad. Hier erweitere ich denselben Host um editierbare Fachdaten.
Wie erzeugt Liquid einen sicheren Power Pages JSON-Vertrag?
Das aufrufende Seitentemplate entscheidet, welches Verhalten die aktuelle Seite benötigt.
Eine Landingpage verwendet navigate:
{%- capture search_config -%}
{"type":"navigate","targetUrl":"/search-results"}
{%- endcapture -%}
{% include 'Portal Search Host', config: search_config %}Eine Ergebnisseite verwendet update-url:
{%- capture search_config -%} {"type":"update-url","historyMode":"push","eventName":"portal:search-changed"} {%-
endcapture -%} {% include 'Portal Search Host', config: search_config %}navigate benötigt eine nicht leere targetUrl.
update-url benötigt exakt historyMode: "push".
eventName ist optional. Fehlt der Wert oder ist er leer, verwendet React den dokumentierten Standardnamen.
Diese Konfiguration ändert das Verhalten desselben Bundles. Es entsteht kein zweiter Build für Landingpage und Ergebnisseite.
Wie beide Modi URL und Browserhistorie unterschiedlich behandeln, zeige ich im Beitrag über Power Pages React URL Parameter.
Wie wird JSON im Power Pages HTML-Attribut sicher ausgegeben?
Der Laufzeitvertrag liegt in einem HTML Attribut.
Deshalb reicht gültiges JSON allein nicht aus. Anführungszeichen und andere Sonderzeichen müssen auch im Attributkontext sicher bleiben.
Die MDN Dokumentation zu data Attributen beschreibt den Zugriff über dataset.
data-config='{{ config | escape }}'Das Liquid escape verhindert, dass ein JSON-Wert das Attribut beendet und neues Markup erzeugt.
Ich lese den Wert später über dataset. Der Browser liefert den dekodierten Attributwert. Erst danach folgt JSON.parse.
const rawConfig = host.dataset['config'];
if (rawConfig === undefined || rawConfig.trim() === '') {
throw new Error('Portal Search configuration is missing.');
}
const parsed: unknown = JSON.parse(rawConfig);Ein application/json Element besitzt einen anderen Kontext. Dort lese ich textContent.
<script type="application/json" id="portal-industry-clusters">
{
"clusters": [
{
"slug": "retail",
"label": "Handel",
"industryIds": ["industry-fashion", "industry-food"]
},
{
"slug": "services",
"label": "Dienstleistungen",
"industryIds": ["industry-health", "industry-leisure"]
}
]
}
</script>Das Beispiel enthält statisches JSON. Wenn redaktionelle Werte dynamisch in diesen Block geschrieben werden, müssen sie mit einem geprüften JSON-Serializer erzeugt werden. String-Konkatenation mit ungeprüften Labels ist kein sicherer Ersatz.
Ich verwende weder innerHTML noch ausführbaren JavaScript Code für diese Daten.
Warum muss React Power Pages Konfiguration als unknown lesen?
Ein data-* Attribut besitzt keinen TypeScript Vertrag zur Laufzeit.
Ich parse deshalb zuerst nach unknown und prüfe danach den Discriminator.
type SearchSubmitBehavior =
| {
readonly type: 'navigate';
readonly targetUrl: string;
}
| {
readonly type: 'update-url';
readonly historyMode: 'push';
readonly eventName?: string;
};
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function readWidgetConfig(host: HTMLElement): SearchSubmitBehavior {
const rawConfig = host.dataset['config'];
if (rawConfig === undefined || rawConfig.trim() === '') {
throw new Error('Portal Search configuration is missing.');
}
let parsed: unknown;
try {
parsed = JSON.parse(rawConfig) as unknown;
} catch (error: unknown) {
if (error instanceof SyntaxError) {
throw new Error('Portal Search configuration must be valid JSON.');
}
throw error;
}
if (!isRecord(parsed)) {
throw new Error('Portal Search configuration must be an object.');
}
if (parsed['type'] === 'navigate') {
const targetUrl = parsed['targetUrl'];
if (typeof targetUrl !== 'string' || targetUrl.trim() === '') {
throw new Error('Portal Search targetUrl is required for navigate behavior.');
}
return {
type: 'navigate',
targetUrl,
};
}
if (parsed['type'] === 'update-url') {
if (parsed['historyMode'] !== 'push') {
throw new Error('Portal Search historyMode must be "push" for update-url behavior.');
}
const eventName = parsed['eventName'];
if (eventName === undefined || eventName === '' || (typeof eventName === 'string' && eventName.trim() === '')) {
return {
type: 'update-url',
historyMode: 'push',
};
}
if (typeof eventName !== 'string') {
throw new Error('Portal Search eventName must be a string when provided.');
}
return {
type: 'update-url',
historyMode: 'push',
eventName,
};
}
throw new Error('Portal Search configuration type must be "navigate" or "update-url".');
}Der Parser baut ein neues Objekt aus bekannten Feldern.
Ein zusätzliches Feld wie debug, language oder endpoint erhält dadurch keine Wirkung. Es wird nicht in den geprüften Vertrag übernommen.
Diese Regel verhindert keine Manipulation des DOM. Sie begrenzt nur, welche Browserwerte das Widget interpretiert.
Wie reagiert React auf fehlende Power Pages Laufzeitkonfiguration?
Der Bootstrap erwartet genau einen Host.
Fehlt der Host, existieren mehrere Hosts oder ist data-config ungültig, startet React nicht.
const hosts = document.querySelectorAll<HTMLElement>('[data-widget="portal-search"]');
if (hosts.length !== 1) {
throw new Error('Exactly one Portal Search host element is required.');
}
const host = hosts[0];
if (host === undefined) {
throw new Error('Portal Search host element was not found.');
}
const config = readWidgetConfig(host);Ich verwende hier keinen erfundenen Standardmodus.
Eine fehlende targetUrl darf nicht zu einer Navigation auf die aktuelle Seite führen. Ein falsches historyMode darf nicht stillschweigend Browserhistorie anders behandeln.
Der Fehler bleibt ein sichtbarer Integrationsfehler.
Wie trennt Power Pages Branchen-JSON Gruppierung und Identität?
Die editierbare Clusterdatei enthält drei Arten von Werten:
- Der
slugist die stabile technische Identität einer Gruppe. - Das
labelist der sichtbare Text. industryIdsverweist auf stabile Branchen-IDs.
Wert | Aufgabe | Darf sich ändern | Wird übertragen |
|---|---|---|---|
slug | Stabile technische Identität des Clusters | Nur als Vertragsänderung | Nicht in Ergebnis-URL oder Server Request |
label | Sichtbarer redaktioneller Gruppenname | Ohne Änderung stabiler IDs | Nur für Darstellung |
industryIds | Stabile Identitäten einzelner Branchen | Nur mit Anpassung aller betroffenen Verträge | Als URL- und Request-Werte |
Ein Label darf sich ändern, ohne URLs oder Requests umzuschreiben.
Eine Branchen-ID darf nicht durch ihr sichtbares Label ersetzt werden. Labels können übersetzt, umbenannt oder redaktionell korrigiert werden.
Der öffentliche Beispielkatalog bleibt bewusst klein:
const KNOWN_INDUSTRY_IDS = ['industry-fashion', 'industry-health', 'industry-food', 'industry-leisure'] as const;
const SUPPORTED_CLUSTER_SLUGS = ['retail', 'services'] as const;
type IndustryId = (typeof KNOWN_INDUSTRY_IDS)[number];
type ClusterSlug = (typeof SUPPORTED_CLUSTER_SLUGS)[number];
interface IndustryCluster {
readonly slug: ClusterSlug;
readonly label: string;
readonly industryIds: readonly IndustryId[];
}Der reale Katalog bleibt im Projekt. Der Artikel veröffentlicht weder vollständige IDs noch die kundenspezifische Gruppierung.
Wie prüft React eine vollständige Branchen-ID-Partition?
Die Clusterkonfiguration ist nur gültig, wenn sie den bekannten Katalog vollständig und ohne Überschneidung partitioniert.
Das bedeutet:
- Jeder unterstützte Cluster existiert genau einmal.
- Jede bekannte Branchen-ID kommt genau einmal vor.
- Keine unbekannte ID wird akzeptiert.
- Kein Cluster besitzt ein leeres Label.
- Kein Cluster besitzt eine leere ID-Liste.
function hasCompletePartition(clusters: readonly IndustryCluster[], knownIds: readonly IndustryId[]): boolean {
const configuredIds = clusters.flatMap(({ industryIds }) => industryIds);
return (
new Set(configuredIds).size === configuredIds.length &&
configuredIds.length === knownIds.length &&
knownIds.every((industryId) => configuredIds.includes(industryId))
);
}Nur eine Mengenprüfung auf bekannte IDs reicht nicht.
Ohne Längenprüfung könnten doppelte IDs eine fehlende ID verdecken. Ohne Eindeutigkeitsprüfung könnte dieselbe Branche in mehreren Gruppen erscheinen.
Die vollständige Partition macht die redaktionelle Änderung überprüfbar.
Wie prüfen React und JavaScript dieselbe Power Pages Konfiguration?
React liest das JSON Element über textContent.
type ClusterConfigResult =
| {
readonly isValid: true;
readonly clusters: readonly IndustryCluster[];
}
| {
readonly isValid: false;
readonly error: string;
};
function isClusterSlug(value: unknown): value is ClusterSlug {
return typeof value === 'string' && SUPPORTED_CLUSTER_SLUGS.some((slug) => slug === value);
}
function isIndustryId(value: unknown): value is IndustryId {
return typeof value === 'string' && KNOWN_INDUSTRY_IDS.some((industryId) => industryId === value);
}
function readClusterConfig(documentRef: Pick<Document, 'getElementById'>): ClusterConfigResult {
const element = documentRef.getElementById('portal-industry-clusters');
if (element === null) {
return {
isValid: false,
error: 'Industry cluster config element is missing.',
};
}
let parsed: unknown;
try {
parsed = JSON.parse(element.textContent ?? '') as unknown;
} catch (error: unknown) {
if (error instanceof SyntaxError) {
return {
isValid: false,
error: 'Industry cluster config must be valid JSON.',
};
}
throw error;
}
if (!isRecord(parsed) || !Array.isArray(parsed['clusters'])) {
return {
isValid: false,
error: 'Industry cluster config must contain a clusters array.',
};
}
const clusters: IndustryCluster[] = [];
for (const value of parsed['clusters']) {
if (!isRecord(value)) {
return {
isValid: false,
error: 'Every industry cluster must be an object.',
};
}
const slug = value['slug'];
const label = value['label'];
const industryIds = value['industryIds'];
if (!isClusterSlug(slug)) {
return {
isValid: false,
error: 'Industry cluster config contains an unsupported slug.',
};
}
if (typeof label !== 'string' || label.trim() === '') {
return {
isValid: false,
error: `Industry cluster ${slug} must have a non-empty label.`,
};
}
if (!Array.isArray(industryIds) || industryIds.length === 0 || !industryIds.every(isIndustryId)) {
return {
isValid: false,
error: `Industry cluster ${slug} contains invalid industry IDs.`,
};
}
clusters.push({
slug,
label: label.trim(),
industryIds,
});
}
const configuredSlugs = clusters.map(({ slug }) => slug);
if (
clusters.length !== SUPPORTED_CLUSTER_SLUGS.length ||
SUPPORTED_CLUSTER_SLUGS.some(
(slug) => configuredSlugs.filter((configuredSlug) => configuredSlug === slug).length !== 1
)
) {
return {
isValid: false,
error: 'Industry cluster config must contain each supported slug exactly once.',
};
}
if (!hasCompletePartition(clusters, KNOWN_INDUSTRY_IDS)) {
return {
isValid: false,
error: 'Industry cluster config must assign every known industry ID exactly once.',
};
}
return {
isValid: true,
clusters: SUPPORTED_CLUSTER_SLUGS.map((slug) => {
const cluster = clusters.find((item) => item.slug === slug);
if (cluster === undefined) {
throw new Error(`Validated industry cluster ${slug} is missing.`);
}
return cluster;
}),
};
}Der Parser prüft Slugs, Labels, ID-Listen, Duplikate und vollständige Abdeckung, bevor ein Consumer die Konfiguration erhält.
Landingpage JavaScript führt dieselbe fachliche Prüfung mit einer eigenen ID-Allowlist aus. Es übernimmt nicht das React Ergebnis.
Diese doppelte Browserprüfung ist beabsichtigt. Beide Laufzeiten können getrennt geladen und geändert werden.
Was bedeutet Fail Closed für Power Pages Consumer?
Ein Fehler im kleinen Laufzeitvertrag verhindert den gesamten React Start.
Ein Fehler im Branchenvertrag betrifft nur die clusterbasierte Bedienung.
React protokolliert den Fehler. Das Widget rendert weiter. Der Cluster-Dropdown ist deaktiviert. Die individuelle Branchenauswahl aus dem React Katalog bleibt verfügbar.
Das Landingpage JavaScript behandelt denselben Fehler anders. Es entfernt die Ziele aller Clusterlinks, setzt aria-disabled="true" und verhindert Klicks.
function disableQuickFilters($links, error) {
console.error(error);
$links
.removeAttr('href')
.attr('aria-disabled', 'true')
.addClass('disabled')
.on('click.portalIndustryClusters', function (event) {
event.preventDefault();
});
}Das ist kein stiller Fallback auf eine möglicherweise falsche Gruppierung.
Die sichere Ersatzfunktion ist jeweils kleiner als die fehlerhafte Funktion. React behält die bekannte Einzelauswahl. Die Landingpage deaktiviert die nicht mehr belegbaren Schnellfilter.
Wie kommt die Locale in der Power Pages Konfiguration aus dem Dokument?
Die Sprache gehört nicht in einen kundenspezifischen Widget-Vertrag, wenn Power Pages sie bereits im Dokument ausgibt.
React liest deshalb das lang Attribut:
type SupportedLcid = 1031 | 1033;
const LANGUAGE_TO_LCID: Readonly<Record<string, SupportedLcid>> = {
de: 1031,
'de-de': 1031,
en: 1033,
'en-us': 1033,
'en-gb': 1033,
};
function resolvePowerPagesLcid(): SupportedLcid {
const language = document.documentElement.lang.trim().toLowerCase();
return LANGUAGE_TO_LCID[language] ?? 1033;
}Ein zusätzliches Feld language in data-config ändert diesen Ablauf nicht. Der Laufzeitparser kennt das Feld nicht und verwirft es.
Diese Trennung verhindert zwei konkurrierende Sprachquellen.
Das Dokument bestimmt die Locale. Der Branchenkatalog liefert stabile IDs und sichtbare Labels. Server Logic besitzt eigene Regeln für unterstützte LCIDs.
Warum bleiben Power Pages Labels reine Darstellung?
React besitzt einen eigenen Katalog aus stabiler ID und aktuellem Label.
interface IndustryDefinition {
readonly id: IndustryId;
readonly label: string;
}
const INDUSTRY_CATALOG: readonly IndustryDefinition[] = [
{ id: 'industry-fashion', label: 'Mode' },
{ id: 'industry-health', label: 'Gesundheit' },
{ id: 'industry-food', label: 'Gastronomie' },
{ id: 'industry-leisure', label: 'Freizeit' },
];Die URL serialisiert IDs. Die Oberfläche rendert Labels.
Das Clusterlabel beschreibt nur eine Gruppe. Es wird nicht als Branche an Server Logic gesendet.
Auf der Landingpage erweitert JavaScript einen Cluster in seine IDs. Es schreibt nur den Parameter industries. Der Cluster-Slug bleibt aus der Ergebnis-URL.
So kann ich ein Cluster umbenennen oder eine bekannte ID verschieben, ohne einen zweiten Bundle-Build zu erzeugen.
Warum besitzt Power Pages Server Logic eine eigene Allowlist?
Browserdaten bleiben veränderbar.
Ein Portalnutzer kann das application/json Element ändern. Ein Skript kann data-config ersetzen. Eine URL kann unbekannte Branchen-IDs enthalten.
Power Pages Server Logic prüft deshalb die eingehenden IDs erneut gegen eine eigene Allowlist.
Die Clusterzugehörigkeit spielt dort keine autoritative Rolle.
const ALLOWED_INDUSTRY_IDS = ['industry-fashion', 'industry-health', 'industry-food', 'industry-leisure'];
function sanitizeIndustryIds(value) {
if (!Array.isArray(value)) {
return undefined;
}
const sanitized = value.filter(
(item, index) => typeof item === 'string' && ALLOWED_INDUSTRY_IDS.includes(item) && value.indexOf(item) === index
);
return sanitized.length > 0 ? sanitized : undefined;
}Server Logic übersetzt die validierten Branchen-IDs in einen Dataverse Branchenfilter. Die ausgewählten IDs grenzen damit die Suchergebnisse ein. Die editierbare Clusterzuordnung steuert die Gruppierung im Browser, nicht die serverseitige Bedeutung einer Branchen-ID.
Der Beitrag über Geschäftsregeln und Filter Sanitization erklärt die Verarbeitung vom Browserwert bis zum Dataverse Filter. Die Power Pages Server Logic Sicherheitsgrenze zeigt, warum Browser-Allowlisting keine serverseitige Prüfung ersetzt.
Wie verhindert eine Änderungsmatrix blinde Konfigurationsupdates?
Nicht jede redaktionelle Änderung betrifft dieselben Dateien.
Änderung | Web Template | React Katalog | Landingpage Allowlist | Server Logic |
|---|---|---|---|---|
Bekannte ID in anderes Cluster verschieben | Ändern | Unverändert | Unverändert | Unverändert |
Clusterlabel umbenennen | Ändern | Unverändert | Unverändert | Unverändert |
Branchenlabel umbenennen | Unverändert | Ändern | Unverändert | Nur bei serverseitigem Textvertrag ändern |
Bekannte ID hinzufügen oder entfernen | Ändern | Ändern | Ändern | Ändern |
Bestehende ID ersetzen | Ändern | Ändern | Ändern | Ändern |
Eine neue ID ist keine reine Inhaltsänderung. Sie erweitert mehrere Verträge und braucht deshalb einen neuen Build sowie ein Server Logic Update.
Eine bekannte ID zwischen bestehenden Clustern zu verschieben ist dagegen reine Browserkonfiguration. Das Bundle bleibt gleich.
Welche Werte besitzt jede Power Pages Konfigurationsschicht?
Ich ordne jeden Wert der Schicht zu, die ihn fachlich kontrolliert.
Schicht | Gehört hierher | Gehört nicht hierher |
|---|---|---|
Liquid und Web Template | Seitenmodus, Ziel-URL, optionaler Eventname, Clusterlabels und Zuordnung bekannter IDs | React Komponenten, Autorisierung und fertige Backend-Abfragen |
React | Stabile IDs, Branchenlabels, Reihenfolge, URL-Allowlist und UI-Verhalten | Sitemarker, Geheimnisse und verbindliche Benutzerrechte |
Landingpage JavaScript | Unabhängige ID-Allowlist, Clusterauflösung und Aufbau der Ziel-URL | Dataverse Schemanamen und serverseitige Berechtigungen |
Power Pages Server Logic | Verbindliche ID-Allowlist, Nutzerkontext, Request-Sanitization und API-Mapping | Browserlabels und Cluster als Autorität |
Diese Zuordnung verhindert eine zentrale Konfigurationsdatei, die zu viele Aufgaben übernehmen soll.
Gemeinsam ist nur die editierbare Browserdarstellung. Jede Vertrauensgrenze bleibt unabhängig.
Warum bleiben unbekannte Power Pages Konfigurationseigenschaften wirkungslos?
Die Parser erlauben zusätzliche JSON-Eigenschaften, verwenden sie aber nicht.
Das ist nützlich für eine kontrollierte Weiterentwicklung. Ein Web Template kann ein neues Feld vorbereiten, bevor eine ältere Bundle-Version es kennt.
Diese Vorwärtskompatibilität besitzt eine Grenze.
Ein unbekanntes Feld darf keine sicherheitsrelevante Nebenwirkung erzeugen. Deshalb übernehmen Parser nie das vollständige Objekt. Sie bauen neue Objekte aus bekannten Feldern.
Wenn ein neues Feld Verhalten ändern soll, braucht es:
- einen dokumentierten Typ
- eine Laufzeitprüfung
- definierte Fehlerfälle
- Tests oder Assertions
- eine klare Zuständigkeit
Nur das Vorhandensein im JSON reicht nicht.
Wo lässt sich das Power Pages Konfigurationsmuster wiederverwenden?
Die Architektur ist nicht auf Branchen beschränkt.
Ein Konfigurator kann editierbare Produktgruppen bereitstellen. Ein Terminwidget kann bekannte Standortgruppen laden. Ein Dashboard kann sichtbare Ansichten und Gruppierungen aus einem Web Template lesen.
Das Muster bleibt gleich:
- Liquid und Web Templates liefern kleinen Seitenkontext.
- HTML transportiert Daten in einem dokumentierten Format.
- JavaScript und React parsen
unknown. - Stabile IDs bleiben getrennt von Labels.
- Jeder Browser-Consumer besitzt eine eigene Prüfung.
- Server Logic validiert alle sicherheitsrelevanten Werte erneut.
Für ein statisches Widget mit einem einzigen Kontext wäre dieser Aufbau zu groß. Er lohnt sich, wenn mehrere Seiten oder Laufzeiten dieselbe editierbare Gruppierung verwenden.
Warum ist Power Pages Konfiguration keine Sicherheitsgrenze?
data-* Attribute und application/json Elemente sind Teil des ausgelieferten HTML.
Jeder Browsernutzer kann sie lesen und verändern.
Deshalb gehören dort keine Secrets, internen Schlüssel, verbindlichen Berechtigungen, fertigen OData-Ausdrücke oder ungeprüften Dataverse Parameter hinein.
HTML Escaping schützt den Markup-Kontext. JSON Parsing schützt die erwartete Datenform. Eine Allowlist begrenzt bekannte Werte.
Keine dieser Maßnahmen macht Browserdaten vertrauenswürdig.
Die serverseitige Prüfung bleibt verbindlich. Content Security Policy, Webrollen, Tabellenberechtigungen und CSRF Schutz ergänzen diese Grenze. Sie ersetzen sie nicht.
Der Abschnitt zur serverseitigen Validierung der Power Pages Suchfilter zeigt, wie Server Logic veränderte Browserwerte entfernt oder ablehnt, bevor sie den Backend Request beeinflussen.
Power Pages JSON-Konfiguration: Erfahrungen aus dem Enterprise-Projekt
Ich habe dieses Muster für eine Enterprise-Suche in Power Pages umgesetzt.
Ein zentrales Web Template liefert die editierbare Clusterzuordnung. Landingpage JavaScript und React lesen dieselbe Browserquelle. Beide prüfen sie unabhängig. Server Logic akzeptiert nur bekannte IDs aus einer eigenen Allowlist.
Die öffentliche ECE Fallstudie zur Power Pages Enterprise-Suche zeigt den Projektkontext. Der Artikel veröffentlicht keinen vollständigen Kundenkatalog, keine internen IDs, keine Sitemarker und keine Dataverse Schemanamen.
Der Nutzen liegt in einer klaren Änderungsgrenze. Redaktionelle Gruppierung kann ohne zweiten Bundle-Build angepasst werden. Neue Identitäten und neue Backend-Verträge bleiben kontrollierte Entwicklungsarbeit.
Du planst ein Power Pages Widget mit editierbarer Konfiguration und mehreren Browser-Consumer? Im Bereich Power Pages Pro-Code zeige ich, wie ich Laufzeitverträge, Sicherheitsgrenzen und Änderungsverfahren vor der Entwicklung kläre.