Power Pages React und Dataverse typisiert verbinden
Typisierte Power Pages Schnittstellen verbinden React und Dataverse über Controller, Service, Repository, Transport und geprüfte Laufzeitparser.
Inhalt
- Welche Transport-Schnittstellen braucht React in Power Pages?
- Warum TypeScript Netzwerkdaten in Power Pages nicht validiert
- Warum brauchen drei Power Pages Operationen getrennte Request-Verträge?
- Welche Aufgaben haben Controller, Service und Repository in React?
- Wie kapselt ein Power Pages Transport die Laufzeit?
- Welche JSON-Verträge besitzt eine Power Pages Antwort?
- Wie verhindert ein Operationsparser vertauschte Power Pages Antworten?
- Wie tief müssen Power Pages Ergebnisse geprüft werden?
- Wie bleiben Parserfehler in React und Power Pages sichtbar?
- Wie werden validierte Suchergebnisse zum React Consumer-Vertrag?
- Wann rechtfertigen typisierte React und Dataverse Schnittstellen den Aufwand?
- Typisierte React und Dataverse Schnittstellen im Enterprise-Projekt
Welche Transport-Schnittstellen braucht React in Power Pages?
Der technische Vertrag zwischen meiner React Suche und Power Pages beginnt mit zwei Methoden:
export interface ISearchTransport {
submitSearchFilters(request: SearchServerLogicRequest): Promise<SearchApiData<SearchResponse>>;
fetchSuggestions(request: SuggestServerLogicRequest): Promise<readonly SearchSuggestion[]>;
}Dieses Interface zeigt die zwei Operationen, die das bestehende React Suchwidget selbst konsumiert.
Der gemeinsame HTTP-Endpunkt besitzt zusätzlich getPortalCatalogDetails. Dieser dritte Vertrag gehört zu Detail- und Favoritenansichten. Er wird typisiert, ohne eine ungenutzte Methode in den Search Service einzubauen.
Das Interface kennt keine jQuery Callbacks. Es kennt keinen Server Logic Endpunkt. Es kennt keine Dataverse Connector Details.
Die Anwendungsschichten sehen nur einen Request und ein Promise mit einem definierten Ergebnis.
Die öffentlichen Beispiele verwenden dafür ein kleines generalisiertes Domänenmodell:
type SupportedLcid = 1031 | 1033;
type SearchSuggestionKind = 'city' | 'postalCode' | 'region' | 'center';
interface FilterPayload {
readonly lcid: SupportedLcid;
readonly searchText?: string;
}
interface SearchResult {
readonly id: string;
readonly name: string;
}
interface SearchResponse {
readonly lcid: SupportedLcid;
readonly objects: readonly SearchResult[];
}
interface SearchApiData<TContent> {
readonly IsSucceeded: boolean;
readonly Content: TContent;
}
interface SearchSuggestion {
readonly kind: SearchSuggestionKind;
readonly label: string;
readonly value: string;
readonly isDirectMatch: boolean;
}Diese kleine Grenze ist der Ausgangspunkt meiner Power Pages React Dataverse Server Logic Architektur. Die Übersicht der Enterprise-Suche ordnet sie in den vollständigen Datenfluss ein.
Warum TypeScript Netzwerkdaten in Power Pages nicht validiert
TypeScript kann sicherstellen, dass mein Service nur einen SearchServerLogicRequest an den Transport übergibt. Es kann verhindern, dass ein Consumer versehentlich ein veränderbares Array erwartet.
TypeScript kann nicht prüfen, was der Server tatsächlich zurücksendet.
Interfaces und Typen werden beim Build entfernt. Ein Ausdruck wie dieser erzeugt deshalb keine Laufzeitprüfung:
const response = JSON.parse(rawValue) as SearchResponse;Der Cast ändert nur die Sicht des Compilers. Falsches JSON bleibt falsches JSON.
Die TypeScript Dokumentation zur Typverengung zeigt, wie Laufzeitprüfungen aus unknown einen belastbaren Typ ableiten.
Ich lasse Netzwerkdaten deshalb so lange unknown. Erst ein Parser prüft die erwartete Form und gibt anschließend einen Domänentyp zurück.
Auch readonly besitzt eine klare Grenze. Es verhindert unerwünschte Zuweisungen im TypeScript Code. Es friert ein Objekt zur Laufzeit nicht automatisch ein.
Aspekt | TypeScript Typ | Laufzeitparser |
|---|---|---|
Zeitpunkt | Build und Entwicklung | Nach Empfang echter Netzwerkdaten |
Eingabe | Bekannte Werte im eigenen Code |
|
Prüfung | Aufrufe, Felder und Rückgabetypen | Objektform, Discriminator und benötigte Werte |
Grenze | Nach Build nicht mehr vorhanden | Prüft nur ausdrücklich implementierte Regeln |
Fehlerwirkung | Compiler meldet widersprüchlichen Code | Parser lehnt ungültige Daten sichtbar ab |
Warum brauchen drei Power Pages Operationen getrennte Request-Verträge?
Die Suche und Auto-Suggest nutzen denselben Server Logic Endpunkt. Sie besitzen trotzdem unterschiedliche Verträge.
export type SearchServerLogicRequest = {
readonly operation: 'search';
} & FilterPayload;
export interface SuggestServerLogicRequest {
readonly operation: 'suggest';
readonly lcid: SupportedLcid;
readonly searchText: string;
}
export interface PortalCatalogDetailsServerLogicRequest {
readonly operation: 'getPortalCatalogDetails';
readonly entityType: 'center' | 'area';
readonly ids: readonly string[];
readonly languageCode: 'de' | 'de-DE' | 'en' | 'en-US' | 'en-GB';
}
export type ServerLogicRequest =
| SearchServerLogicRequest
| SuggestServerLogicRequest
| PortalCatalogDetailsServerLogicRequest;Das Feld operation ist der Discriminator.
Eine vollständige Suche kann alle unterstützten Filter tragen. Ein Suggest Request enthält nur Sprache und Suchtext. Ein Detail-Batch enthält einen Entitätstyp, eine Sprache und bekannte IDs.
Ich nutze keinen großen Request-Typ mit vielen optionalen Feldern. Ein solcher Typ würde Kombinationen zulassen, die fachlich keinen Sinn ergeben.
Der Service erzeugt die Operation ausdrücklich:
class SearchService {
constructor(private readonly repository: SearchRepository) {}
submitSearchFilters(payload: FilterPayload) {
return this.repository.submitSearchFilters({
operation: 'search',
...payload,
});
}
fetchSuggestions(searchText: string, lcid: SupportedLcid) {
return this.repository.fetchSuggestions({
operation: 'suggest',
lcid,
searchText,
});
}
}Der Search Service ergänzt weiterhin nur search und suggest, weil das vorhandene Widget keine Favoritenseite rendert. Der vollständige Endpoint-Typ enthält trotzdem den dritten Vertrag für einen separaten Browser-Consumer.
Der Deep Dive Power Pages Favoriten mit Batch-GUIDs und Server Logic zeigt diesen Vertrag.
Dadurch kann der Aufrufer die Operation nicht vergessen. Der Rückgabetyp bleibt an die gewählte Methode gekoppelt.
Aspekt | Vollständige Suche | Auto-Suggest | Detail-Batch |
|---|---|---|---|
Discriminator | operation: ‘search’ | operation: ‘suggest’ | operation: ‘getPortalCatalogDetails’ |
Pflichtwerte | lcid |
|
|
Optionale Werte | Suchtext und unterstützte Filter | Keine zusätzlichen Suchfilter | Keine Suchfilter |
Rückgabe | SearchApiData<SearchResponse> | readonly SearchSuggestion[] | PortalCatalogDetailsData |
Fachlicher Zweck | Ergebnisliste aus allen akzeptierten Filtern | Kleine Vorschlagsliste für Suchtexteingabe | Bekannte Center oder Flächen gesammelt laden |
Der Deep Dive Power Pages Auto-Suggest mit React: Debounce und Requests verfolgt diesen kleineren Vertrag vom Eingabefeld bis zur gruppierten und geprüften Vorschlagsliste.
Welche Aufgaben haben Controller, Service und Repository in React?
Ich trenne die Schichten nicht wegen ihrer Namen. Jede Schicht muss eine konkrete Entscheidung besitzen.
Der Controller prüft UI Eingaben. Er trimmt den Suchtext, liefert Validierungsfehler als discriminated union und bereitet einen serialisierten Suchvertrag vor.
type SearchPreparationResult =
| {
readonly isValid: false;
readonly validationErrors: readonly string[];
}
| {
readonly isValid: true;
readonly prepared: {
readonly payload: FilterPayload;
readonly queryString: string;
};
};Der hier auf payload und queryString reduzierte Vertrag liegt im vollständigen Widget unter serializedSearch. Wie dieser Vertrag mit Landing Navigation, pushState, replaceState und popstate verwendet wird, zeigt der nächste Deep Dive über Power Pages React URL Parameter und Browserhistorie.
Der Service ergänzt die Backend Operation und besitzt die fachnahen Methoden für Suche und Vorschläge.
Das Repository reicht den fertigen Request an ISearchTransport weiter. In diesem Projekt ist es bewusst dünn. Es bildet die Datenquellengrenze für den echten Power Pages Transport und einen lokalen Mock.
Die Verdrahtung bleibt dadurch klein:
function buildController(transport: ISearchTransport): SearchController {
const repository = new SearchRepository(transport);
const service = new SearchService(repository);
return new SearchController(service);
}Ein dünnes Repository ist nicht automatisch sinnvoll. Wenn eine Anwendung nur eine Datenquelle besitzt, keinen Mock benötigt und keine eigene Datenzugriffsgrenze braucht, kann diese Schicht unnötig sein.
Hier unterstützt sie zwei Laufzeitumgebungen mit demselben Controller. Das zentrale Host Muster für das React Widget hält im Gegenzug die Seitentemplates frei von diesen Anwendungsschichten.
Wie kapselt ein Power Pages Transport die Laufzeit?
Nur PowerPagesSearchTransport kennt window.webapi, safeAjax und den Server Logic Pfad.
export class PowerPagesSearchTransport implements ISearchTransport {
async submitSearchFilters(request: SearchServerLogicRequest): Promise<SearchApiData<SearchResponse>> {
const rawData = await postServerLogicRequest(request);
return parseSearchResponse(rawData);
}
async fetchSuggestions(request: SuggestServerLogicRequest): Promise<readonly SearchSuggestion[]> {
const rawData = await postServerLogicRequest(request);
return parseSuggestionResponse(rawData);
}
}Repository, Service und Controller müssen dadurch keine Callback Struktur kennen. Sie arbeiten nur mit Promises.
Der Adapter übernimmt zwei Aufgaben:
- Er übersetzt den Promise Vertrag in den Power Pages AJAX Aufruf.
- Er übergibt die empfangenen Strings an den passenden Laufzeitparser.
CSRF Schutz, serverseitige Autorisierung und OData Aufbau bleiben eigene Verantwortlichkeiten. Der vorherige Beitrag zur Power Pages Server Logic Sicherheitsgrenze dokumentiert diese Prüfungen.
Welche JSON-Verträge besitzt eine Power Pages Antwort?
Die Antwort ist nicht nach einem einzigen JSON.parse abgeschlossen.
Die Microsoft Dokumentation zum Aufruf von Server Logic zeigt den äußeren Power Pages Envelope mit Request ID, Erfolgsstatus, Daten und Server Logic Namen.
Der im Projekt verwendete Wrapper stellt diese Informationen im Callback bereit. Das Feld data enthält den serialisierten Rückgabewert meiner Server Logic.
Dieser innere String besitzt einen eigenen Operationsvertrag:
{
"operation": "search",
"data": {
"IsSucceeded": true,
"Content": "{\"lcid\":1031,\"objects\":[]}"
},
"serverLogicName": "catalog-search"
}Das Feld Content ist erneut JSON als String.
Das Beispiel zeigt die Search Operation. Dort enthält data den Custom API Umschlag und Content erneut serialisiertes JSON.
Suggest und Detail-Batch besitzen eine andere dritte Prüfebene. Suggest liefert direkt suggestions. Der Detail-Batch liefert direkt entityType, results, notFoundIds und invalidIds.
Der gemeinsame Operationsparser bestätigt nur Umschlag, Discriminator und Datenobjekt. Danach übernimmt ein operationsspezifischer Parser.
Ich behandle deshalb drei Grenzen getrennt:
Prüfebene | Bestätigt | Typische Prüfung |
|---|---|---|
Power Pages Envelope | HTTP und Server Logic Aufruf waren erfolgreich | Erfolgsstatus prüfen und |
Operationsvertrag | Antwort gehört zu | Discriminator und vorhandenes Datenobjekt |
Operationsdaten | Search Umschlag, Suggestion-Liste oder Detail-Batch | Operationsspezifische Felder und Ergebnislisten |
Keine dieser Grenzen darf durch einen TypeScript Cast übersprungen werden.
Wie verhindert ein Operationsparser vertauschte Power Pages Antworten?
Der gemeinsame Parser erhält den erwarteten Discriminator als Argument:
type ServerLogicOperation = 'search' | 'suggest' | 'getPortalCatalogDetails';
function parseJson(value: string, label: string): unknown {
try {
return JSON.parse(value) as unknown;
} catch {
throw new Error(`${label} is not valid JSON.`);
}
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function parseOperationResult(
value: string,
expectedOperation: ServerLogicOperation
): {
readonly data: Record<string, unknown>;
readonly serverLogicName: string;
} {
const parsed: unknown = parseJson(value, 'Server Logic data');
if (!isRecord(parsed)) {
throw new Error('Server Logic data must be an object.');
}
if (parsed['operation'] !== expectedOperation) {
throw new Error(`Server Logic operation must be "${expectedOperation}".`);
}
if (!isRecord(parsed['data'])) {
throw new Error('Server Logic data does not contain a data object.');
}
return {
data: parsed['data'],
serverLogicName: typeof parsed['serverLogicName'] === 'string' ? parsed['serverLogicName'] : 'catalog-search',
};
}Ein Suggest Ergebnis kann damit nicht versehentlich als vollständige Suchantwort verarbeitet werden. Dasselbe gilt für einen Detail-Batch. Der Discriminator muss zum aufrufenden Parser passen.
Ungültiges JSON, ein fehlendes Datenobjekt und eine falsche Operation erzeugen jeweils einen ausdrücklichen Fehler. Der Parser erfindet keinen Ersatzwert.
Wie tief müssen Power Pages Ergebnisse geprüft werden?
Der Suchparser prüft zuerst den Custom API Envelope.
IsSucceeded muss ein Boolean sein. Content muss ein String sein. Ein optionaler OData Kontext muss ebenfalls eine Zeichenkette sein.
Danach wird Content erneut geparst. Der Suchparser prüft die benötigten Top-Level Felder und ob die erwarteten Ergebnislisten Arrays sind.
Die Elemente dieser Listen werden dadurch nicht vollständig validiert.
Diese Unterscheidung ist wichtig. Ein TypeScript Array Cast nach Array.isArray validiert nicht automatisch die Elemente. Wenn ein Consumer ein einzelnes Ergebnisfeld als geschäftskritisch verwendet, braucht dieses Feld eine zusätzliche Laufzeitprüfung oder ein geeignetes Schema.
Die Detail-DTOs besitzen ebenfalls TypeScript Verträge. Ein Browser-Consumer behandelt empfangene Center- oder Area-Elemente zunächst als unknown. Die Laufzeitprüfung muss die Felder abdecken, auf die der Consumer zugreift. Das gilt unabhängig davon, ob ein Einzelabruf oder ein Favoriten-Batch die Daten liefert.
Der Suggestion Vertrag ist kleiner. Dort prüft der Parser jeden Eintrag:
function isSuggestionKind(value: unknown): value is SearchSuggestionKind {
return value === 'city' || value === 'postalCode' || value === 'region' || value === 'center';
}
function parseSuggestion(value: unknown, index: number): SearchSuggestion {
if (!isRecord(value)) {
throw new Error(`Suggestion at index ${String(index)} must be an object.`);
}
if (!isSuggestionKind(value['kind'])) {
throw new Error(`Suggestion at index ${String(index)} has an invalid kind.`);
}
if (typeof value['label'] !== 'string' || value['label'].trim() === '') {
throw new Error(`Suggestion at index ${String(index)} needs a label.`);
}
if (typeof value['value'] !== 'string' || value['value'].trim() === '') {
throw new Error(`Suggestion at index ${String(index)} needs a value.`);
}
if (typeof value['isDirectMatch'] !== 'boolean') {
throw new Error(`Suggestion at index ${String(index)} needs isDirectMatch.`);
}
return {
kind: value['kind'],
label: value['label'],
value: value['value'],
isDirectMatch: value['isDirectMatch'],
};
}Die Prüftiefe folgt dem Vertrag. Sie wird nicht durch einen allgemeinen Anspruch auf vollständige Typisierung ersetzt.
Wie bleiben Parserfehler in React und Power Pages sichtbar?
Ein Parserfehler wird vom Transport nicht in ein leeres Ergebnis umgewandelt.
Das abgelehnte Promise läuft durch Repository und Service zurück zum Widget. Dort erhält die Oberfläche einen technischen Fehlerzustand.
Eine erfolgreiche leere Suche bleibt davon getrennt:
type SearchExecutionState =
| { readonly status: 'success'; readonly response: SearchResponse }
| { readonly status: 'error'; readonly message: string };Dieser Beispieltyp fasst die sichtbare Entscheidung zusammen. Im realen Widget wird der Zustand über Promise Erfolg und Fehlerbehandlung gesteuert.
Die Grenze verhindert einen gefährlichen Eindruck. Keine Treffer bedeuten nicht automatisch, dass Server Logic, Custom API und Parser korrekt gearbeitet haben.
Wie werden validierte Suchergebnisse zum React Consumer-Vertrag?
Nach erfolgreicher Prüfung besitzt das Widget einen typisierten Ergebnisvertrag:
export type SearchResultsEventDetail = SearchApiData<SearchResponse>;
export function dispatchSearchResults(eventName: string, response: SearchResultsEventDetail): void {
window.dispatchEvent(
new CustomEvent(eventName, {
detail: response,
})
);
}Das Widget veröffentlicht nur Antworten, die den vorgelagerten Parser passiert haben.
Ein Vanilla JavaScript Consumer erhält zur Laufzeit dasselbe Objekt wie ein React Consumer. TypeScript unterstützt nur Consumer, die den Typvertrag importieren. Andere Consumer brauchen die dokumentierte Event Form als eigenen Vertrag.
Die Event Übergabe ist deshalb keine neue Validierungsgrenze. Sie verteilt ein bereits akzeptiertes Ergebnis.
Wie der Producer diesen Vertrag zeitlich veröffentlicht und wie unabhängige Consumer ihn abonnieren, zeigt der folgende Beitrag über Power Pages React CustomEvents.
Wann rechtfertigen typisierte React und Dataverse Schnittstellen den Aufwand?
Für einen einzelnen einfachen Web API Request wäre diese Architektur zu groß.
In meiner Suche treffen mehrere Anforderungen zusammen:
- Der gemeinsame POST-Endpunkt besitzt drei getrennte Request- und Result-Verträge.
- Vollständige Suche und Auto-Suggest besitzen unterschiedliche Verträge.
- Landingpage und Ergebnisseite nutzen denselben Controller in verschiedenen Abläufen.
- Lokale Entwicklung und Power Pages verwenden unterschiedliche Transportimplementierungen.
- Die Custom API liefert verschachtelte serialisierte Antworten.
- Mehrere Consumer sollen geprüfte Ergebnisse erhalten.
Unter diesen Bedingungen liefern die Schichten überprüfbare Grenzen. Sie verteilen nicht nur Code auf mehr Dateien.
Der allgemeine Beitrag zur React Frontend Architektur in Power Pages hilft bei der Entscheidung, wann ein isoliertes Widget sinnvoll ist und wann native Power Pages Funktionen ausreichen.
Typisierte React und Dataverse Schnittstellen im Enterprise-Projekt
Ich habe diese Struktur für eine komplexe Enterprise-Suche umgesetzt.
Der sichtbare React Teil kann dadurch mit kleinen Methoden arbeiten. Die Power Pages Laufzeit bleibt im Adapter. Server Logic und Custom API Antworten werden schrittweise geprüft. Entkoppelte Consumer erhalten erst den akzeptierten Ergebnisvertrag.
Die ECE Fallstudie zur Power Pages Enterprise-Suche zeigt den Projektkontext. Die Codeausschnitte in diesem Beitrag sind generalisiert.
Ich verkaufe damit keine allgemeine Repository Vorlage. Ich dokumentiere, wie ich Integrationsgrenzen für einen geschäftskritischen Power Pages Workflow nachvollziehbar mache.
Du planst eine React Anwendung in Power Pages, die Server Logic oder eine Dataverse Custom API nutzt? Im Power Pages Pro-Code Bereich zeige ich, wie ich Verträge, Laufzeitgrenzen und Übergaberisiken vor der Entwicklung kläre.