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.
Probleme:
- • Code im Browser sichtbar (F12)
- • 100 API Calls = 100 HTTP Requests
- • Business Logic exposed
- • Kein Batch-Support
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
- Power Pages Studio öffnen
- Setup Workspace → Server Logic (Preview)
- "New Server Logic" klicken
- Name eingeben (z.B. "BulkContactImport")
- Web Role zuweisen
Schritt 2: Code schreiben
- "Edit Code" klicken
- "Open Visual Studio Code" wählen
- Code-Template einfügen (function get/post/put/del)
- Speichern (Ctrl+S)
Schritt 3: Table Permissions
- Security Workspace → Table Permissions
- Neue Permission für betroffene Tables
- Privileges (Create, Read, Update, Delete) setzen
- 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