Zum Hauptinhalt springen
⚠️ PREVIEW

Power Pages Server Logic:
Server-Side JavaScript für sichere Business Logic

3 Code-Templates: Bulk Operations, versteckte Business Logic & externe APIs

Von Tino Rabe, Microsoft Power Pages MVP • 31. Oktober 2025 • 16 min Lesezeit

Wichtig: Preview Feature

Server Logic ist aktuell ein Preview Feature in Power Pages. Es ist nicht für Production-Umgebungen geeignet und unterliegt den Supplemental Terms of Use.

Einleitung

Microsoft hat mit Power Pages Server Logic ein neues Feature veröffentlicht, das Server-Side JavaScript Execution direkt in Power Pages ermöglicht. Im Gegensatz zur Client-Side Web API läuft Server Logic Code auf dem Power Pages Server und ist vollständig vor dem Browser versteckt.

❌ Client-Side Web API

Probleme:

  • • Code im Browser sichtbar (F12)
  • • 100 API Calls = 100 HTTP Requests
  • • Business Logic exposed
  • • Kein Batch-Support
✅ Server Logic

Lösung:

  • • Code auf Server (versteckt)
  • • 1 API Call, Loop auf Server
  • • Business Logic geschützt
  • • Pseudo-Batch via Loops

Was ist Power Pages Server Logic?

Server Logic ermöglicht die Ausführung von ECMAScript 2023 JavaScript auf dem Power Pages Server. Der Code wird in Dataverse gespeichert und über spezielle API-Endpoints aufgerufen:

POST https://yoursite.powerappsportals.com/_api/serverlogics/YourFunctionName
Content-Type: application/json
__RequestVerificationToken: {{CSRF_TOKEN}}

{ "data": "your payload" }

Wichtigste Unterschiede

Aspekt Client-Side Web API Server Logic
Ausführung Im Browser (JavaScript) Auf Server (ECMAScript 2023)
Code-Sichtbarkeit ❌ Via DevTools sichtbar ✅ Versteckt auf Server
Business Logic ❌ Exposed (F12) ✅ Geschützt
API Keys ❌ Im Browser code ✅ Via Server.SiteSetting
Bulk Operations ❌ 100 HTTP Requests ✅ 1 Request, Loop auf Server
External APIs ❌ CORS Issues ✅ Server.Connector.HttpClient

Use Case 1: Bulk Contact Import

Problem: Import von 100+ Kontakten erfordert 100 einzelne API-Calls vom Browser → langsam, fehleranfällig, keine Transaktionen.

Lösung: Ein einzelner API-Call an Server Logic, Loop läuft auf dem Server nahe an Dataverse.

💼 Bulk Import Template - Client + Server Code

🌐 Client-Side (Browser)

Ein einziger API-Call mit allen Kontakten:

// CSV-Daten vom Upload
const csvData = [
    { firstname: "John", lastname: "Doe", emailaddress1: "john@example.com" },
    { firstname: "Jane", lastname: "Smith", emailaddress1: "jane@example.com" },
    // ... 98 weitere Kontakte
];

fetch('/_api/serverlogics/BulkContactImport', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        '__RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
    },
    body: JSON.stringify({ contacts: csvData })
})
.then(response => response.json())
.then(data => {
    if (data.success) {
        console.log(`✅ Import komplett!`);
        console.log(`Created: ${data.results.successCount}`);
        console.log(`Failed: ${data.results.failedCount}`);
    }
})
.catch(error => console.error('Error:', error));

🖥️ Server-Side (Power Pages Server Logic)

Loop läuft auf dem Server, Code versteckt:

function post() {
    try {
        // Parse Request Body
        const payload = JSON.parse(Server.Context.Body);
        const contacts = payload.contacts;

        // Validierung
        if (!contacts || !Array.isArray(contacts)) {
            return JSON.stringify({
                success: false,
                error: "Invalid payload"
            });
        }

        // Results initialisieren
        const results = {
            successCount: 0,
            failedCount: 0,
            errors: [],
            createdIds: []
        };

        Server.Logger.Log(`Starting bulk import of ${contacts.length} contacts`);

        // SERVER-SIDE LOOP (versteckt vor Browser)
        for (let i = 0; i < contacts.length; i++) {
            const contact = contacts[i];

            try {
                // Dataverse CreateRecord
                const createResponse = Server.Connector.Dataverse.CreateRecord(
                    "contacts",
                    JSON.stringify({
                        firstname: contact.firstname || "",
                        lastname: contact.lastname,
                        emailaddress1: contact.emailaddress1,
                        telephone1: contact.telephone1 || ""
                    })
                );

                const responseData = JSON.parse(createResponse.Body);
                results.createdIds.push(responseData.contactid);
                results.successCount++;

                Server.Logger.Log(`Created contact ${i + 1}/${contacts.length}`);

            } catch (err) {
                results.failedCount++;
                results.errors.push({
                    index: i,
                    contact: contact.lastname,
                    error: err.message
                });
                Server.Logger.Error(`Failed contact ${i}: ${err.message}`);
            }
        }

        Server.Logger.Log(`Bulk import completed. Success: ${results.successCount}, Failed: ${results.failedCount}`);

        return JSON.stringify({
            success: true,
            results: results
        });

    } catch (err) {
        Server.Logger.Error(`Bulk import failed: ${err.message}`);
        return JSON.stringify({
            success: false,
            error: err.message
        });
    }
}

Vorteile dieser Lösung

  • 1 HTTP Request statt 100 (Browser → Server)
  • Server-Side Loop nahe an Dataverse = deutlich schneller
  • Business Logic versteckt (nicht via F12 sichtbar)
  • Error Handling pro Record mit Server.Logger
  • Detailliertes Result-Object mit IDs und Fehlern

Use Case 2: Hidden Business Logic (Commission Calculator)

Problem: Provisions-Berechnungen im Client-Side Code sind via DevTools (F12) sichtbar → Wettbewerber können Algorithmen kopieren.

Lösung: Berechnung läuft auf dem Server, Client erhält nur das Endergebnis.

🔒 Hidden Business Logic Template

🌐 Client-Side (Browser)

Einfacher API-Call, keine Business Logic sichtbar:

// Client erhält nur Endergebnis
async function getCommission(dealId) {
    const response = await fetch(
        `/_api/serverlogics/CommissionCalculator?dealId=${dealId}`,
        {
            headers: {
                '__RequestVerificationToken': getCSRFToken()
            }
        }
    );

    const result = await response.json();

    // Nur finale Zahl, kein Algorithmus sichtbar
    return result.commission; // z.B. 12500
}

🖥️ Server-Side (Power Pages Server Logic)

Proprietärer Algorithmus komplett versteckt:

function get() {
    try {
        // Query Parameter lesen
        const dealId = Server.Context.QueryParameters['dealId'];

        if (!dealId) {
            return JSON.stringify({
                success: false,
                error: "Missing dealId"
            });
        }

        // Deal-Daten aus Dataverse holen
        const dealResponse = Server.Connector.Dataverse.RetrieveRecord(
            "opportunities",
            dealId,
            "$select=estimatedvalue,customerid,statecode"
        );

        const deal = JSON.parse(dealResponse.Body);

        // Customer-Daten holen (für Tier-Prüfung)
        const customerResponse = Server.Connector.Dataverse.RetrieveRecord(
            "accounts",
            deal._customerid_value,
            "$select=accountid,customertypecode,address1_country"
        );

        const customer = JSON.parse(customerResponse.Body);

        // PROPRIETÄRER ALGORITHMUS (versteckt vor Browser)
        let commission = calculateCommissionInternal(
            deal.estimatedvalue,
            customer.customertypecode,
            customer.address1_country
        );

        Server.Logger.Log(`Commission calculated for deal ${dealId}: ${commission}`);

        return JSON.stringify({
            success: true,
            commission: commission,
            dealId: dealId
        });

    } catch (err) {
        Server.Logger.Error(`Commission calculation failed: ${err.message}`);
        return JSON.stringify({
            success: false,
            error: "Calculation failed"
        });
    }
}

// INTERNE FUNKTION (nicht vom Browser aufrufbar)
function calculateCommissionInternal(value, tierCode, country) {
    let rate = 0.10; // Base: 10%

    // Tier-basierte Anpassung (proprietär)
    if (tierCode === 3) rate = 0.15;      // Platinum: 15%
    else if (tierCode === 2) rate = 0.12; // Gold: 12%

    // Regions-Multiplikator (proprietär)
    if (country === "DE" || country === "AT" || country === "CH") {
        rate *= 1.2; // DACH: +20%
    }

    // Value-basierte Bonifikation (proprietär)
    if (value > 100000) {
        rate += 0.05; // +5% bei >100k
    }

    return Math.round(value * rate);
}

Vorteile dieser Lösung

  • Proprietärer Algorithmus komplett versteckt
  • Client erhält nur Endergebnis (keine Rückschlüsse möglich)
  • Wettbewerber können Formel nicht reverse-engineeren
  • Algorithmus-Updates ohne Client-Deployment
  • Zusätzliche Validierung auf Server möglich

Use Case 3: External API Integration

Problem: Externe REST APIs (Payment Providers, SMS Services, Weather APIs, etc.) benötigen API-Keys. Im Client-Side Code wären diese via F12 sichtbar → Sicherheitsrisiko.

Lösung: API-Keys in Site Settings speichern, Aufrufe über Server Logic → Keys nie im Browser.

🔗 External API Template (Generic REST API)

🌐 Client-Side (Browser)

Keine API-Keys im Browser, generischer API-Call:

// Client kennt keine API-Keys
async function callExternalService(actionType, payload) {
    const response = await fetch(
        '/_api/serverlogics/ExternalAPIProxy',
        {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                '__RequestVerificationToken': getCSRFToken()
            },
            body: JSON.stringify({
                action: actionType,
                data: payload
            })
        }
    );

    const result = await response.json();

    if (result.success) {
        console.log("API Call successful:", result.data);
        return result.data;
    } else {
        console.error("API Call failed:", result.error);
        throw new Error(result.error);
    }
}

🖥️ Server-Side (Power Pages Server Logic)

API-Keys sicher aus Site Settings, generischer REST API Call:

function post() {
    try {
        const payload = JSON.parse(Server.Context.Body);
        const action = payload.action;

        // API-Keys aus verschlüsselten Site Settings (NIE im Browser)
        const apiKey = Server.SiteSetting.Get("ExternalAPI_ApiKey");
        const apiBaseUrl = Server.SiteSetting.Get("ExternalAPI_BaseUrl");

        if (!apiKey || !apiBaseUrl) {
            throw new Error("API credentials not configured");
        }

        Server.Logger.Log(`External API call: ${action}`);

        // Beispiel: Payment API, SMS Service, Weather API, etc.
        let apiEndpoint = "";
        let requestBody = {};

        // Action-basiertes Routing
        if (action === "sendSMS") {
            apiEndpoint = `${apiBaseUrl}/messages`;
            requestBody = {
                to: payload.data.phoneNumber,
                text: payload.data.message,
                from: "YourServiceName"
            };
        } else if (action === "verifyPayment") {
            apiEndpoint = `${apiBaseUrl}/payments/verify`;
            requestBody = {
                transactionId: payload.data.transactionId,
                amount: payload.data.amount
            };
        } else if (action === "getWeather") {
            apiEndpoint = `${apiBaseUrl}/weather?city=${payload.data.city}`;
            // GET Request ohne Body
        } else {
            throw new Error(`Unknown action: ${action}`);
        }

        // EXTERNAL API CALL (via Server.Connector.HttpClient)
        let response;

        if (action === "getWeather") {
            // GET Request
            response = Server.Connector.HttpClient.GetAsync(
                apiEndpoint,
                JSON.stringify({
                    "Authorization": `Bearer ${apiKey}`,
                    "Accept": "application/json"
                })
            );
        } else {
            // POST Request
            response = Server.Connector.HttpClient.PostAsync(
                apiEndpoint,
                JSON.stringify(requestBody),
                JSON.stringify({
                    "Authorization": `Bearer ${apiKey}`,
                    "Content-Type": "application/json"
                }),
                "application/json"
            );
        }

        if (!response.IsSuccessStatusCode) {
            throw new Error(`API error: ${response.ReasonPhrase}`);
        }

        const apiResult = JSON.parse(response.Body);

        Server.Logger.Log(`External API success: ${action}`);

        return JSON.stringify({
            success: true,
            data: apiResult,
            action: action
        });

    } catch (err) {
        Server.Logger.Error(`External API failed: ${err.message}`);
        return JSON.stringify({
            success: false,
            error: err.message
        });
    }
}

⚙️ Erforderliche Site Settings

ServerLogic/AllowedDomains = api.yourservice.com,api.alternative.com
ExternalAPI_ApiKey = [Your API Key]
ExternalAPI_BaseUrl = https://api.yourservice.com/v1

Beispiele für External APIs: Stripe/PayPal (Payment), Twilio/MessageBird (SMS), OpenWeather/WeatherAPI, SendGrid/Mailgun (E-Mail), Google Maps/HERE (Geocoding)

Vorteile dieser Lösung

  • API-Keys nie im Browser (via Server.SiteSetting)
  • Keine CORS-Probleme (Server → API direkt)
  • Synchrone Verarbeitung (kein Power Automate Delay)
  • Error Handling mit Server.Logger
  • Zentralisierte Credential-Verwaltung

Server Objects API Referenz

Server Logic stellt mehrere Built-In Objects zur Verfügung:

Server.Connector.Dataverse

CRUD Operationen auf Dataverse Tables

// Create
Server.Connector.Dataverse.CreateRecord(
    "contacts",
    JSON.stringify({...})
);

// Read
Server.Connector.Dataverse.RetrieveRecord(
    "contacts",
    contactId,
    "$select=firstname,lastname"
);

// Update
Server.Connector.Dataverse.UpdateRecord(
    "contacts",
    contactId,
    JSON.stringify({...})
);

// Delete
Server.Connector.Dataverse.DeleteRecord(
    "contacts",
    contactId
);

Server.Connector.HttpClient

HTTP Requests zu externen APIs

// GET
Server.Connector.HttpClient.GetAsync(
    url,
    headers
);

// POST
Server.Connector.HttpClient.PostAsync(
    url,
    body,
    headers,
    contentType
);

// PUT
Server.Connector.HttpClient.PutAsync(
    url,
    body,
    headers,
    contentType
);

// DELETE
Server.Connector.HttpClient.DeleteAsync(
    url,
    headers
);

Server.Context

Request Metadata & User Info

// Request Body
const payload = JSON.parse(
    Server.Context.Body
);

// Query Parameters
const id = Server.Context.QueryParameters['id'];

// HTTP Method
const method = Server.Context.HttpMethod;

// Request Headers
const auth = Server.Context.Headers['Authorization'];

Server.Logger

Diagnostic Logging (DevTools Extension)

// Info
Server.Logger.Log("Info message");

// Warning
Server.Logger.Warn("Warning message");

// Error
Server.Logger.Error("Error message");

// Logs sichtbar in DevTools Extension

Server.SiteSetting

Verschlüsselte Site Settings lesen

// API Keys sicher speichern
const apiKey = Server.SiteSetting.Get(
    "ExternalAPI_ApiKey"
);

const timeout = Server.SiteSetting.Get(
    "ServerLogic/TimeoutInSeconds"
);

Server.User

Aktueller User (authenticated)

// User Info
if (Server.User) {
    const fullname = Server.User.fullname;
    const email = Server.User.emailaddress1;
    const userId = Server.User.contactid;
} else {
    // Anonymous User
}

Setup Guide

Voraussetzungen

  • 1.
    Enhanced Data Model
    Server Logic funktioniert nur mit Enhanced Data Model. Migration erforderlich falls Standard Model.
  • 2.
    Power Pages Studio
    Zugriff auf "Setup" Workspace → "Server Logic (Preview)"
  • 3.
    VS Code mit Power Platform Tools
    Für Code-Authoring mit IntelliSense

Schritt-für-Schritt Setup

Schritt 1: Server Logic erstellen

  1. Power Pages Studio öffnen
  2. Setup Workspace → Server Logic (Preview)
  3. "New Server Logic" klicken
  4. Name eingeben (z.B. "BulkContactImport")
  5. Web Role zuweisen

Schritt 2: Code schreiben

  1. "Edit Code" klicken
  2. "Open Visual Studio Code" wählen
  3. Code-Template einfügen (function get/post/put/del)
  4. Speichern (Ctrl+S)

Schritt 3: Table Permissions

  1. Security Workspace → Table Permissions
  2. Neue Permission für betroffene Tables
  3. Privileges (Create, Read, Update, Delete) setzen
  4. Web Role zuweisen

Schritt 4: Site Settings (optional)

ServerLogic/Enabled = true
ServerLogic/TimeoutInSeconds = 120
ServerLogic/AllowedDomains = api.example.com
ServerLogic/MaxMemoryUsageInBytes = 10485760

Einschränkungen & Limitierungen

Preview Status

  • Nicht Production-Ready: Preview Features ändern sich, Breaking Changes möglich
  • Kein SLA: Keine Service Level Agreements, keine Production Support
  • Enhanced Data Model Required: Funktioniert nur mit EDM, nicht mit Standard Model

Technische Limitierungen

❌ Nicht verfügbar

  • • Browser APIs (fetch, XMLHttpRequest)
  • • Node.js APIs (Buffer, fs, process)
  • • DOM Manipulation
  • • window, document Objects
  • • npm Packages

✅ Verfügbar

  • • ECMAScript 2023 Standard
  • • Server.Connector.* APIs
  • • Server.Context, Server.User
  • • Server.Logger für Diagnostics
  • • JSON, Array, String Methods

⏱️ Performance Limits

  • Timeout: 120 Sekunden (Standard), max 240 Sekunden
  • Memory: 10 MB pro Function
  • Content-Types: application/json, text/html, application/x-www-form-urlencoded
  • Allowed Domains: Muss explizit in Site Settings definiert werden

Fragen zu Power Pages?

In einem kostenlosen 30-Minuten-Gespräch analysieren wir Ihre Anforderungen und zeigen Ihnen die Möglichkeiten der Power Platform für Ihr Projekt.

Jetzt Erstgespräch buchen
Tino Rabe

Tino Rabe

Microsoft Power Pages MVP

Ich unterstütze mittelständische Unternehmen dabei, sichere und DSGVO-konforme Kundenportale mit Microsoft Power Pages aufzubauen. Mein Fokus: Schnelle Implementierung, messbare ROI, keine Vendor-Lock-ins.