Erfassen Sie Ihre Fortschritte
Trailhead-Startseite
Trailhead-Startseite

Verbinden von Salesforce mit serverseitigen Controllern

Lernziele

Nachdem Sie diese Lektion abgeschlossen haben, sind Sie in der Lage, die folgenden Aufgaben auszuführen:
  • Erstellen von Apex-Methoden, die im Code von Aura-Komponenten remote aufgerufen werden können
  • Richten von Aufrufen aus Aura-Komponenten an Remote-Methoden
  • Asynchrones Verarbeiten von Serverantworten mithilfe von Rückruffunktionen
  • Besondere Herausforderung: Erläutern des Unterschieds zwischen "c.", "C:" und "c.".

Konzepte mit serverseitigen Controllern

Bislang war alles, was wir gemacht, komplett clientseitig. Wir speichern unsere Spesen noch nicht zurück in Salesforce. Erstellen Sie einige Spesen und klicken Sie auf die Schaltfläche zum Neuladen. Was passiert? Nun ja, alle Spesen verschwinden. Hurra, geschenktes Geld!

Außer, dass die Buchhaltung gerade angerufen hat, die in diesen Dingen ziemlich humorlos ist. Und wollen nicht eigentlich wir diese Spesen erstattet bekommen, die wir andernfalls aus unserer Tasche zahlen müssten...? Na toll! Das Speichern unserer Daten in Salesforce ist mit Sicherheit ein P0-Fehler!

Spaß beiseite. Es ist endlich Zeit, unserer Anwendung serverseitige Controller hinzuzufügen. Wir haben das bislang nicht gemacht, weil wir uns mit den Grundlagen beschäftigt haben. Nun da Sie bereit dafür sind, lassen Sie uns loslegen!

Dazu bedienen wir uns Abbildungen. Lassen Sie uns sicherstellen, dass wir unser Ziel kennen und unser Tank voll ist, ehe wir losfahren.

Lassen Sie uns zunächst noch einmal einen Blick auf das Diagramm im ersten Modul werfen, das eine (sehr) allgemeine Darstellung der Architektur von Lightning Components-Anwendungen zeigt.

Eine sehr allgemeine Architektur von Lightning Components: Client (Ansicht und Controller), Server (Apex-Controller und Datenbank)

Bislang hat sich alles auf der Clientseite dieser Abbildung abgespielt. (Und beachten Sie die Vereinfachung durch Zusammenführen von Controllern und Hilfsfunktionen hier.) Wenngleich wir einen benutzerdefinierten Objekttyp, Expense__c, referenziert haben, der auf Serverseite definiert ist, sind wir nicht direkt mit dem Server in Berührung gekommen.

Erinnern Sie sich noch, wie wir über das Verschalten bzw. Verknüpfen verschiedener Elemente gesprochen haben, um einen vollständigen Schaltkreis zu erstellen? Das Spesenabrechnungsformular, das wir in der letzten Einheit erstellt haben, kann ungefähr so aussehen.

Clientseite des Flusses

Der Kreis startet mit der Schaltfläche Create, die mit dem Aktionshandler clickCreate (1) verknüpft ist. Wenn der Aktionshandler ausgeführt wird, ruft er Werte aus den Formularfeldern (2) ab und fügt anschließend eine neue Aufwendung dem Array expenses (3) hinzu. Wenn das Array über set aktualisiert wird, wird automatisch das erneute Rendern der Spesenliste (4) ausgelöst, wodurch sich der Kreis schließt. Einfach, nicht wahr?

Wenn wir den serverseitigen Zugriff hinzufügen, wird das Diagramm ein wenig komplizierter. Mehr Pfeile, mehr Farben, mehr Zahlen! (Wir warten für den Moment mit einer Erklärung all dessen.)

Vollständiger Fluss: Client- und Serverseite

Was auffällt ist, dass dieser Kreislauf nicht mehr denselben gleichmäßigen, synchronen Steuerungsfluss aufweist. Serveraufrufe sind aufwendig und können gewisse Zeit dauern. Im Normalfall Millisekunden, bei hoher Netzwerkauslastung auch schon einmal mehrere Sekunden. Sie wollen vermeiden, dass Anwendungen beim Warten auf Serverantworten ausgebremst werden.

Die Lösung, um während der Wartezeiten reaktionsfähig zu bleiben, ist die asynchrone Verarbeitung von Serverantworten. Dies bedeutet, dass wenn Sie auf die Schaltfläche "Create Expense" klicken, Ihr clientseitiger Controller eine Serveranforderung auslöst und dann die Verarbeitung fortsetzt. Der Controller wartet nicht nur die Serverantwort ab, sondern "vergisst", diese Anforderung jemals gestellt zu haben!

Wenn anschließend die Antwort vom Server zurückkommt, wird Code, der mit der Anforderung, einer sog. Rückruffunktion, gepackt wurde, zum Verarbeiten der Antwort ausgeführt, einschließlich der Aktualisierung clientseitiger Daten und der Benutzeroberfläche.

Wenn Sie ein erfahrener JavaScript-Programmierer sind, sind asynchrone Ausführung und Rückruffunktionen für Sie ganz alltäglich. Falls nicht, lernen Sie jetzt etwas Neues kennen, das ganz anders ist. Es ist aber auch echt cool.

Abfragen von Daten in Salesforce

Wir beginnen mit dem Lesen von Daten aus Salesforce, nämlich mit dem Laden der Liste angefallener Spesen, sobald die Spesenabrechnungsanwendung gestartet wird.

Hinweis

Hinweis

(Wenn Sie noch keine echten Spesendatensätze in Salesforce erstellt haben, wäre jetzt ein guter Zeitpunkt.) Andernfalls verbringen Sie nach dem Implementieren von dem, was folgt, Zeit mit dem Debuggen, warum nichts geladen wird, wenn es tatsächlich nichts zu laden gibt. Ihr bescheidener Autor tappt auch immer wieder selbst in diese Falle.

Der erste Schritt ist die Erstellung eines Apex-Controllers. Apex-Steuerfelder enthalten Remotemethoden, die Ihre Lightning-Komponenten aufrufen können. In diesem Fall zum Abfragen und Empfangen von Spesendaten aus Salesforce.

Sehen wir uns einmal eine vereinfachte Version des Codes an. Erstellen Sie in der Entwicklerkonsole eine neue Apex-Klasse namens "ExpensesController" und fügen Sie den folgenden Code ein.

public with sharing class ExpensesController {
    // STERN LECTURE ABOUT WHAT'S MISSING HERE COMING SOON
    @AuraEnabled
    public static List<Expense__c> getExpenses() {
        return [SELECT Id, Name, Amount__c, Client__c, Date__c,
                       Reimbursed__c, CreatedDate
                FROM Expense__c];
    }
}

Mit Apex-Controllern werden wir uns im nächsten Abschnitt eingehender beschäftigen, doch für jetzt ist dies eine wirklich sehr geradlinige Apex-Methode. Sie führt eine SOQL-Abfrage aus und gibt die Ergebnisse zurück. Es sind nur zwei bestimmte Dinge, die diese Methode Ihrem Lightning Components-Code zur Verfügung stellen.

  • Die Anmerkung @AuraEnabled vor der Deklaration der Methode. "Aura" ist der Name des Frameworks im Kern von Lightning Components. Sie haben das Wort bereits im Namespace einiger wichtiger Tags <aura:component> gesehen. Nun wissen Sie, woher es stammt.
  • Das Schlüsselwort static. Alle @AuraEnabled-Controllermethoden müssen statische Methoden mit dem Gültigkeitsbereich public oder global sein.

Wenn Sie diese Anforderungen an Remote-Methoden für die JavaScript-Remoting-Funktion von Visualforce erinnern, ist das kein Zufall. Die Anforderungen sind identisch, weil sich die Architektur in wichtigen Punkten sehr ähnlich ist.

Ein anderer bemerkenswerter Aspekt ist, dass die Methode nichts Besonderes macht, um die Daten für Lightning Components zu packen. Sie gibt bloß die SOQL-Abfrageergebnisse direkt zurück. Das Lightning Components-Framework übernimmt die in den meisten Situationen anfallenden Marshalling-/Unmarshalling-Aufgaben. Sehr schön.

Laden von Daten aus Salesforce

Der nächste Schritt ist das Verknüpfen der Komponente expenses mit dem serverseitigen Apex-Controller. Das ist eine der einfachsten Übungen. Ändern Sie das öffnende <aura:component>-Tag der Komponente "expenses" so, dass es auf den Apex-Controller zeigt, etwa so.

<aura:component controller="ExpensesController">

Der neue Teil ist fett hervorgehoben und ja, es ist wirklich so einfach.

Doch durch Zeigen auf den Apex-Controller werden nicht tatsächlich Daten geladen und es wird auch nicht die Remote-Methode aufgerufen. Wie bei der automatischen Verknüpfung zwischen der Komponente und dem (clientseitigen) Controller ermöglicht dieser Zeigevorgang einfach, dass sich diese beiden Elemente gegenseitig "kennen". Dieses "Kennen" nimmt sogar dieselbe Form an (anderer Wertanbieter), was wir in Kürze erläutern werden. Doch die automatische Verknüpfung geht nur bis hier. Es bleibt unsere Aufgabe, den Kreislauf zu schließen.

In diesem Fall bedeutet das Schließen des Kreislaufs Folgendes.

  1. Wenn die Komponente "expenses" geladen wird,
  2. Salesforce auf vorhandene Spesendatensätze abfragen und
  3. diese Datensätze dem Attribut der Komponente expenses hinzufügen.

Wir gehen diese Schritte einzeln durch. Der erste Schritt, das Auslösen von Verhalten beim ersten Laden der Komponente expenses, erfordert von uns das Schreiben eines init -Handlers. Dies ist eine Kurzbezeichnung für einen Aktionshandler, der mit dem init-Ereignis einer Komponente verknüpft ist. Dieses tritt auf, wenn die Komponente erstmals erstellt wird.

Die Verknüpfung, die Sie hierfür benötigen, ist eine einzelne Markup-Zeile. Fügen Sie Folgendes der Komponente expenses unmittelbar unter den Definitionen der Attribute der Komponente hinzu.

<aura:handler name="init" action="{!c.doInit}" value="{!this}"/>

Das Tag <aura:handler> bestimmt, wie eine Komponente ein bestimmtes Ereignis verarbeiten kann. In diesem Fall sagen wir, dass wir das init-Ereignis verarbeiten, und zwar mit dem Aktionshandler doInit in unserem Controller. (Durch die Festlegung von value="{!this}" erfolgt die Markierung als "Wertereignis". Was dies bedeutet, ist für den Moment zu komplex. Merken Sie sich einfach, dass Sie dieses Attribut-Wert-Paar stets einem init-Ereignis hinzufügen müssen.)

Aufrufen serverseitiger Controllermethoden

Ein Schritt nach unten, noch zwei kommen. Die beiden restlichen Schritte erfolgen im Aktionshandler doInit, weshalb wir einen Blick darauf werfen. Fügen Sie dem Controller der Komponente expenses den folgenden Code hinzu.

    // Load expenses from Salesforce
    doInit: function(component, event, helper) {
        // Create the action
        let action = component.get("c.getExpenses");
        // Add callback behavior for when response is received
        action.setCallback(this, function(response) {
            let state = response.getState();
            if (state === "SUCCESS") {
                component.set("v.expenses", response.getReturnValue());
            }
            else {
                console.log("Failed with state: " + state);
            }
        });
        // Send action off to be executed
        $A.enqueueAction(action);
    },

Bevor Sie sich bei all den neuen Dingen hier verloren vorkommen, ist zu sagen, dass dies bloß ein weiterer Aktionshandler ist. Er ist identisch formatiert und auch die Funktionssignatur ist gleich. Wir bewegen uns also auf vertrautem Grund.

Abgesehen davon ist jede Codezeile nach der Funktionssignatur neu. Wir sehen uns alle gleich an, doch hier zunächst ein Überblick, was dieser Code macht:

  1. Erstellen eines Remote-Methodenaufrufs
  2. Einrichten, was passieren soll, wenn die Rückgabe des Remote-Methodenaufrufs erfolgt
  3. Einrichten einer Warteschlange für den Remote-Methodenaufruf

Das hört sich ziemlich einfach an, oder? Vielleicht sind die Struktur oder Besonderheiten des Codes neu, doch die Grundanforderungen, was passieren soll, sind wiederum vertraut.

Hört sich das an, als wollten wir Ihnen Mut machen? Als würden wir versuchen, Sie auf etwas wirklich Schwieriges vorzubereiten? Wir müssen nun tatsächlich etwas nicht ganz Unkompliziertes ansprechen. Das Problem befindet sich in der ersten Codezeile der Funktion.

        let action = component.get("c.getExpenses");

Diese Codezeile erstellt unseren Remote-Methodenaufruf bzw. unsere Remote-Aktion. Und zunächst sieht component.get() ja auch vertraut aus. Wir haben das bis zu diesem Punkt schon oft gemacht.

Außer…nun ja, vorher war es "v.etwas", das wir abgerufen haben, wobei v der Wertanbieter für "view" war. Hier haben wir "c", und ja, c ist ein weiterer Wertanbieter. Und den Wertanbieter c haben wir vorher auch schon gesehen, z. B. in Ausdrücken wie press="{!c.clickCreate}" und action="{!c.doInit}".

Diese Ausdrücke kamen im Markup der Komponente vor, und zwar in "view". Hier im Controller steht der Wertanbieter c für etwas anderes. Er steht für den Apex-Remote-Controller.

"Moment mal. Wollen Sie mir erzählen, dass es in Aura-Komponenten den clientseitigen Controller c, den Standard-Namespace c und den serverseitigen Controller c gibt?"

Nun, in einem Wort, ja. Tief durchatmen.

Wir wollen ehrlich zu Ihnen sein. Wenn wir noch einmal von vorn beginnen könnten, würden wir vielleicht andere Entscheidungen treffen. Wenngleich die von uns getroffenen Entscheidungen keine Zufälle waren, können drei "Cs" potenziell und definitiv für Verwirrung sorgen. Das trifft auch auf uns zu!

Doch wie sagt man im Rheinland: Et es wie et es. Gefahr erkannt, Gefahr gebannt. Jetzt wissen Sie Bescheid.

Bezeichner
Kontext
Bedeutung
c.
Markup von Komponenten
Clientseitiger Controller
c.
Controllercode
Serverseitiges Steuerfeld
c:
Markup
Standard-Namespace

Nun zurück zu unserem Code. Bevor wir abgelenkt wurden, haben wir uns diese Zeile angesehen.

        let action = component.get("c.getExpenses");

Während uns component.get("v.etwas") in vorherigem Code einen Verweis auf eine untergeordnete Komponente in der Ansicht (Markup der Komponente) zurückgegeben hat, gibt component.get("c.irgendwas") einen Verweis auf eine Aktion zurück, die im Controller verfügbar ist. In diesem Fall wird ein Remotemethodenaufruf an unser Apex-Steuerfeld zurückgegeben. So erstellen Sie einen Aufruf einer @AuraEnabled-Methode.

Die nächste "Zeile", action.setCallback(…), ist ein Codeblock, der ausgeführt wird, wenn der Remote-Methodenaufruf zurückgegeben wird. Da dies "später" erfolgt, schieben wir dies für den Moment beiseite.

Die nächste Zeile, die tatsächlich ausgeführt wird, ist diese.

        $A.enqueueAction(action);

Wir haben $A schon vorher kurz gesehen, ohne es zu erläutern. Es handelt sich um eine globale Variable im Framework, die zahlreiche wichtige Funktionen und Dienste bereitstellt. $A.enqueueAction(action) fügt den Serveraufruf, den wir zuvor konfiguriert haben, der Anforderungswarteschlange des Aura-Komponenten-Frameworks hinzu. Diese Aktion wird zusammen mit anderen ausstehenden Serveranforderungen im nächsten Anforderungszyklus zum Server gesendet.

Das hört sich etwas vage an. Die vollständigen Details sind interessant und für die erweiterte Nutzung von Aura-Komponenten wichtig. Doch für den Moment müssen Sie über $A.enqueueAction(action) nur Folgendes wissen.

  • Das Element dient dazu, die Serveranforderung in eine Warteschlange zu stellen.
  • Was Ihre Controlleraktion betrifft, war es das schon.
  • Es ist nicht garantiert, wann, oder ob, Sie eine Rückmeldung erhalten.

An dieser Stelle kommt der Codeblock ins Spiel, den wir beiseitegeschoben haben. Doch bevor davon die Rede ist, noch ein wenig Popkultur.

Serveraufrufe, asynchrone Ausführung und Rückruffunktionen

Die 2011 von Carly Rae Jepsen veröffentlichte Single "Call Me Maybe" war bei Publikum und Kritikern ein großer Erfolg und erreichte in einem Dutzend Ländern die Nr. 1 der Hitparade. Bislang wurde die Single 18 Mio. Mal verkauft und ist offenkundig eine der meistverkauften "digitalen" Singles aller Zeiten. Am meisten bleibt aus dem Refrain die Zeile "Here’s my number. So call me maybe" im Ohr. Das ist nicht nur fröhlich und unwiderstehlich eingängig, sondern auch eine Metapher dafür, wie Aura-Komponenten Serveraufrufe verarbeiten.

Hören Sie weiter zu. Lassen Sie uns einen Blick auf den Aktionshandler im Pseudo-Code werfen.

    doInit: function(component, event, helper) {
        // Load expenses from Salesforce
        let action = component.get("c.getExpenses");
        action.setCallback(
            // Here’s my number,
            // Call me maybe
        );
        $A.enqueueAction(action);
    },

Hmmm. Vielleicht sollten wir die Parameter für action.setCallback() detaillierter erläutern. Im tatsächlichen Aktionshandlercode erfolgt der Aufruf so.

        action.setCallback(this, function(response) { ... });

this ist der Bereich, in dem der Rückruf ausgeführt wird. Hier ist this die Aktionshandlerfunktion selbst. Stellen Sie sich dies als eine Adresse oder...vielleicht eine Nummer vor (maybe a "number"). Die Funktion ist das, was aufgerufen (called) wird, wenn die Antwort des Servers zurückgegeben wird. Also:

        action.setCallback(scope, callbackFunction);

Here’s my number. Call me maybe.

Die allgemeine Aufgabe ist das Erstellen der Anforderung, das Packen des Codes für die bei Erfüllung der Anforderung zu erfolgende Aktion und dessen Übermittlung zur Ausführung. An dieser Stelle wird die Ausführung des Aktionshandlers selbst beendet.

Hier ist eine andere Möglichkeit, den Durchblick zu bekommen. Sie machen Ihr Kind für die Schule fertig und übergeben ihm eine Liste der Aufgaben, die es erledigen soll, nachdem es von der Schule nach Hause gekommen ist. Sie bringen es zur Schule und gehen dann zur Arbeit. Während Sie bei der Arbeit sind, erledigen Sie Ihre Aufgaben in der sicheren Annahme, dass Ihr braves Kind die von Ihnen aufgetragenen Aufgaben erledigt, sobald es aus der Schule zurück ist. Sie erledigen diese Aufgaben nicht selbst und wissen nicht genau, wann sie erledigt werden. Doch es passiert.

Hier noch eine letzte Möglichkeit zur Veranschaulichung, wiederum in Pseudo-Code. Diese Version extrahiert die Rückruffunktion, um eine linearere Version des Aktionshandlers zu zeigen.

    // Not real code! Do not cut-and-paste!
    doInit: function(component, event, helper) {
        // Create server request
        let action = component.get("c.getExpenses");
        // Send server request
        $A.enqueueAction(action);
        // ... time passes ...
        // ...
        // ... Jeopardy theme plays ...
        // ...
        // ... at some point in the indeterminate future ...
        // Handle server response
        let state = action.response.getState();
        if (state === "SUCCESS") {
            component.set("v.expenses", action.response.getReturnValue());
        }
    },

Wir wiederholen es nochmals. Asynchrone Ausführung und Rückruffunktionen sind für JavaScript-Programmierer ein absolutes Muss. Doch wenn Sie einen anderen Hintergrund haben, ist Ihnen das vielleicht noch nicht vertraut. Hoffentlich haben wir es an dieser Stelle klar dargelegt, da es für die Entwicklung von Anwendungen mit Lightning Components grundlegend ist.

Verarbeiten der Serverantwort

Da wir nun die Struktur zum Erstellen einer Serveranforderung kennen, lassen Sie uns einen Blick auf die Details werfen, wie unsere Rückruffunktion die Antwort tatsächlich verarbeitet. Hier ist nur die Rückruffunktion.

    function(response) {
        let state = response.getState();
        if (state === "SUCCESS") {
            component.set("v.expenses", response.getReturnValue());
        }
    }

Rückruffunktionen verwenden nur den einen Parameter response, bei dem es sich um ein undurchsichtiges Objekt handelt, das die zurückgegebenen Daten, sofern vorhanden, und verschiedene Details zum Status der Anforderung bereitstellt.

Bei dieser spezifischen Rückruffunktion machen wir Folgendes.

  1. Abrufen des Status der Antwort.
  2. Wenn der Status SUCCESS lautet, d. h. unsere Anforderung wie geplant erfüllt wurde, dann:
  3. Legen Sie das Attribut expenses der Komponente auf den Wert der Antwortdaten fest.

Sie haben wahrscheinlich Fragen wie z. B.:

  • Was passiert, wenn der Antwortstatus nicht SUCCESS ist?
  • Was passiert, wenn die Antwort nie kommt? (Call me maybe.)
  • Wie können wir dem Attribut unserer Komponente nur die Antwortdaten zuweisen?

Die Antwort auf die ersten beiden Fragen ist, dass wir diese Möglichkeiten in diesem Modul leider nicht behandeln werden. Es gibt bestimmte Dinge, die Sie wissen und in Ihren realen Anwendungen berücksichtigen müssen, doch dafür fehlt uns der Platz.

Die letzte Frage hat die hier die größte Relevanz, ist aber auch am einfachsten zu beantworten. Wir haben einen Datentyp für das Attribut "expenses" definiert.

<aura:attribute name="expenses" type="Expense__c[]"/>

Und unsere serverseitige Controlleraktion hat eine Methodensignatur, die deren Rückgabedatentyp definiert.

public static List<Expense__c> getExpenses() { ... }

Die Typen stimmen überein, sodass wir einfach eine der anderen zuweisen können. Aura-Komponenten kümmern sich um alle Details. Sie können auch Ihre eigene Verarbeitung der Ergebnisse vornehmen und diese innerhalb Ihrer Anwendung in andere Daten umwandeln. Doch wenn Sie Ihre serverseitigen Aktionen entsprechend entwerfen, ist das nicht unbedingt erforderlich.

Okay, das waren viele verschiedene Möglichkeiten, sich ein Dutzend Codezeilen anzusehen. Hier nun die Frage: Haben Sie Ihre Version unserer Anwendung damit schon ausprobiert? Denn der Teil des Ladens von Spesen aus Salesforce ist hiermit abgeschlossen. Laden Sie die Anwendung neu und prüfen Sie, ob die Spesen, die Sie in Salesforce eingegeben haben, angezeigt werden!

Apex-Steuerfelder für Aura-Komponenten

Bevor wir uns dem nächsten Schritt bei der Entwicklung der Anwendung widmen, wollen wir uns näher mit dem Apex-Steuerfeld beschäftigen. Hier sehen wir die nächste Version, die wir benötigen, um das Erstellen neuer Datensätze zu verarbeiten und um das Kontrollkästchen "Reimbursed?" (Erstattet) für vorhandene Datensätze zu aktualisieren.

public with sharing class ExpensesController {
    @AuraEnabled
    public static List<Expense__c> getExpenses() {
        // Perform isAccessible() checking first, then
        return [SELECT Id, Name, Amount__c, Client__c, Date__c,
                       Reimbursed__c, CreatedDate
                FROM Expense__c];
    }
    @AuraEnabled
    public static Expense__c saveExpense(Expense__c expense) {
        // Perform isUpdateable() checking first, then
        upsert expense;
        return expense;
    }
}

Die frühere Version versprach eine ernste Erläuterung, und die kommt jetzt. Doch lassen Sie uns zunächst den Schwerpunkt auf die Details dieser Minimalversion legen.

Zuerst haben wir mit saveExpense() nur eine neue @AuraEnabled-Methode hinzugefügt. Diese verwendet ein Spesenobjekt (Expense__c), das eingefügt bzw. aktualisiert wird. Dadurch können wir es sowohl zum Erstellen neuer Datensätze als auch zum Aktualisieren vorhandener Datensätze nutzen.

Beachten Sie als Nächstes, dass wir die Klasse mit den Stichworten with sharing erstellt haben. Dadurch werden die Freigaberegeln Ihrer Organisation automatisch auf die Datensätze angewendet, die über diese Methoden verfügbar sind. Benutzern werden beispielsweise nur ihre eigenen Spesendatensätze angezeigt. Salesforce verarbeitet alle diese komplizierten SOQL-Regeln für Sie automatisch im Hintergrund.

Das Verwenden der Schlüsselworte with sharing ist eine der wesentlichen Sicherheitsmaßnahmen, die Sie beim Schreiben von serverseitigem Controllercode berücksichtigen müssen. Es handelt sich jedoch um eine Maßnahme, die erforderlich ist, aber nicht ausreicht. Sehen Sie die Kommentare zum Ausführen der Prüfungen isAccessible() und isUpdateable()? Mit with sharing gelangen Sie nur bis dahin. Die Sicherheit auf Objekt- und Feldebene (Field-Level Security, kurz FLS) müssen Sie selbst implementieren.

Hier ist z. B. eine Version unserer getExpenses()-Methode mit einer minimalen Sicherheitsimplementierung.

    @AuraEnabled
    public static List<Expense__c> getExpenses() {
        // Check to make sure all fields are accessible to this user
        String[] fieldsToCheck = new String[] {
            'Id', 'Name', 'Amount__c', 'Client__c', 'Date__c',
            'Reimbursed__c', 'CreatedDate'
        };
        Map<String,Schema.SObjectField> fieldDescribeTokens =
            Schema.SObjectType.Expense__c.fields.getMap();
        for(String field : fieldsToCheck) {
            if( ! fieldDescribeTokens.get(field).getDescribe().isAccessible()) {
                throw new System.NoAccessException();
            }
        }
        // OK, they're cool, let 'em through
        return [SELECT Id, Name, Amount__c, Client__c, Date__c,
                       Reimbursed__c, CreatedDate
                FROM Expense__c];
    }

Das ist eine recht umfassende Erweiterung unserer anfänglichen einzelnen Codezeile und nur gerade so ausreichend. Darüber hinaus sind Aufrufe von describe aufwendig. Wenn Ihre Anwendung diese Methode häufig aufruft, müssen Sie eine Möglichkeit der Optimierung oder benutzerbezogenen Speicherung Ihrer Zugriffsprüfungen im Cache finden.

Wie bei SLDS haben wir hier einfach nicht den Raum, Ihnen alle Details einer sicheren Apex-Programmierung beizubringen. Im Gegensatz zu SLDS ist das Übernehmen der Verantwortung für die Sicherheit des Codes, den Sie schreiben, nicht optional. Wenn Sie die Artikel zu Praktiken für eine sichere Programmierung unter "Ressourcen" noch nicht gelesen haben, fügen Sie sie Ihrer Lektüreliste hinzu.

Das war's, </ernste_Erläuterung>.

Speichern von Daten in Salesforce

Ehe wir das Formular "Add Expense" tatsächlich implementieren (ohne Mogelei), wollen wir uns zunächst ansehen, wie sich das Erstellen eines neuen Datensatzes vom Lesen vorhandener Datensätze unterscheidet. Mithilfe von doInit() haben wir einfach einige Daten eingelesen und dann die Benutzeroberfläche der Anwendung aktualisiert. Das war nicht so kompliziert, obwohl wir Carly Rae in die Erläuterung einbeziehen mussten.

Das Erstellen eines neuen Datensatzes ist komplexer. Wir lesen Werte aus dem Formular, erstellen lokal einen neuen Spesendatensatz und senden diesen Datensatz zum Speichern auf dem Server. Nachdem der Server uns die Speicherung bestätigt hat, aktualisieren wir die Benutzeroberfläche mithilfe des Datensatzes, der vom Server zurückgegeben wurde.

Hört sich das so an, als wäre das echt kompliziert? Sodass wir vielleicht die Rolling Stones und ein ganzes Album von Songs brauchen, um die nächste Erläuterung zu unterstützen?

Lassen Sie uns einen Blick auf einen Teil des Codes werfen. Danach können Sie selbst entscheiden.

Vergewissern Sie sich zunächst, dass Sie die aktualisierte Fassung des Apex-Controllers gespeichert haben, und zwar die vorherige, die die Methode saveExpense() enthält.

Erinnern Sie sich noch, wie wir Ihnen gezeigt haben, wie die Formularübermittlung verarbeitet wird? Wenn mindestens eines der Felder ungültig ist, wird eine Fehlermeldung angezeigt und das Formular wird nicht übermittelt. Die Fehlermeldung wird entfernt, wenn alle Felder gültig sind.

Da wir alle Details der Erstellung einer neuen Aufwendung in die Hilfsfunktion createExpense() verlagert haben, müssen wir keine weiteren Änderungen am Controller vornehmen. So weit, so einfach?

Alles, was wir tun müssen, ist das Ändern der createExpense()-Funktion in der Hilfsfunktion dergestalt, dass alle komplizierten Aufgaben, die wir zuvor erwähnt haben, erledigt werden. Hier ist dieser Code.

    createExpense: function(component, expense) {
        let action = component.get("c.saveExpense");
        action.setParams({
            "expense": expense
        });
        action.setCallback(this, function(response){
            let state = response.getState();
            if (state === "SUCCESS") {
                let expenses = component.get("v.expenses");
                expenses.push(response.getReturnValue());
                component.set("v.expenses", expenses);
            }
        });
        $A.enqueueAction(action);
    },

Ist das so kompliziert, wie Sie es erwartet haben? Mit so vielen Zeilen? Hoffentlich nicht!

In Wahrheit gibt es nur eine neue Sache in diesem Aktionshandler, die auch noch einfach zu verstehen ist. Lassen Sie uns den Code durchgehen.

Wir beginnen mit dem Erstellen der Aktion, d. h. mit component.get("c.saveExpense") zum Abrufen der neuen Apex-Controllermethode. Sehr vertraut.

Als Nächstes fügen wir die Datennutzlast an die Aktion an. Das ist neu. Wir müssen die Daten für die neue Aufwendung zum Server senden. Doch sehen Sie sich an, wie einfach das ist! Sie verwenden bloß action.setParams() und geben ein Objekt im JSON-Stil mit Paaren aus Parametername/Parameterwert an. Wirklich wichtig ist, dass Ihr Parametername dem Parameternamen entsprechen muss, der in der Deklaration Ihrer Apex-Methode verwendet wird.

Als Nächstes legen wir den Rückruf für die Anforderung fest. Dies passiert wiederum, wenn der Server eine Antwort zurückgibt. Wenn Sie diese Rückruffunktion mit unserer ursprünglichen createExpense-Hilfsfunktion vergleichen, ist diese praktisch identisch (allerdings ohne THIS IS A DISGUSTING, TEMPORARY HACK).

Wie bei der Vorversion rufen wir mit get() das expenses-Attribut ab, übertragen mit push() einen Wert in dieses und legen es dann mit set() fest. Der einzige wirkliche Unterschied ist, dass wir anstatt unsere lokale Version der neuen Aufwendung per push() in das Array zu übertragen, die Serverantwort per push() übertragen!

Warum funktioniert das? Da die serverseitige Methode den (in diesem Fall neuen) Datensatz aktualisiert/einfügt, wobei er mit einer ID versehen wird, und dann den resultierenden Datensatz zurückgibt. Wiederum entsprechen sich die server- und clientseitigen Datentypen, sodass wir keine Zusatzarbeit haben.

Und das war's dann schon. Es geht auch ohne Rolling Stones!

Zu beachtende Aspekte

Wenngleich wir alle wesentlichen Punkte zum Verbinden Ihrer Aura-Komponenten mit serverseitigem Apex-Code behandelt haben, gibt es verschiedene Aspekte, die es hervorzuheben lohnt, ehe Sie sich in den (na, Sie wissen schon) beißen.

Der erste Aspekt ist Groß-/Kleinschreibung, die in Apex und Salesforce im Allgemeinen nicht beachtet, in JavaScript allerdings beachtet werden muss. "Name" und "name" sind also in Apex dasselbe, in JavaScript hingegen nicht.

Dies kann und wird zu absolut zum Wahnsinn treibenden Fehlern führen, die für Ihre Augen völlig unsichtbar sind, selbst wenn sie sich direkt vor Ihren befinden. Insbesondere wenn Sie eine Weile mit Nicht-Lightning Components-Code gearbeitet haben, denken Sie vielleicht überhaupt nicht mehr über die Schreibung von Objekt- und Feldnamen, Methoden usw. nach.

Wir empfehlen Ihnen eine bewährte Vorgehensweise: Verwenden Sie stets den exakten API-Namen aller Objekte, Felder, Typen, Klassen, Methoden, Entitäten, Elemente, Elefanten oder was Sie sonst noch haben. Immer und überall, und zwar auch, wenn es egal ist. Auf diese Weise haben Sie keine Probleme. Oder zumindest nicht dieses Problem.

Der andere Aspekt, auf den wir Ihre Aufmerksamkeit lenken möchten, ist das Wesen von "required". Wir können nicht umhin, ein berühmtes Zitat zu wiederholen: "Du benutzt dieses Wort ständig. Ich glaube, es bedeutet nicht das, was du glaubst, das es bedeutet." (Aus dem Film "Die Braut des Prinzen")

Im Code, den wir bislang geschrieben haben, haben wir mindestens zwei verschiedene Arten von "required" kennengelernt. Im Markup für das Formular "Add Expense" wird das Wort auf zwei Arten verwendet, z. B. im Feld "Expense name".

<lightning:input aura:id="expenseform"
                 label="Expense Name"
                 name="expensename"
                 value="{!v.newExpense.Name}"
                 required="true"/> 

Für das Tag <lightning:input> ist dessen Attribut required auf true festgelegt. Diese beiden Beispiele veranschaulichen nur eine Bedeutung von "required", und zwar "Die Benutzeroberfläche auf dieses Element festlegen, um anzugeben, dass das Feld erforderlich ist". In anderen Worten: dies ist nur Kosmetik. Es gibt hier keinen Schutz der Qualität Ihrer Daten.

Eine weitere Bedeutung des Worts "required" wird in der Überprüfungslogik veranschaulicht, die wir für dasselbe Feld geschrieben haben.

let validExpense = component.find('expenseform').reduce(function (validSoFar, inputCmp) {
    // Displays error messages for invalid fields
    inputCmp.showHelpMessageIfInvalid();
    return validSoFar && inputCmp.get('v.validity').valid;
}, true);

Das Wort "required" ist nirgendwo zu sehen, doch das ist genau, was die Überprüfungslogik erzwingt. Sie müssen für das Feld "Expense name" einen Wert festlegen.

So weit, so besonders gut. Ihr Spesenformular übermittelt keine neue Aufwendung mit einem leeren Namen. Es sei denn, es liegt ein Fehler vor. Oder ein anderes Widget nutzt denselben serverseitigen Controller, ohne bei der Formularüberprüfung so sorgfältig zu sein. Es gibt noch viele weitere Beispiele. Dies bietet also einen bestimmten Grad an Schutz Ihrer Datenqualität, ohne allerdings perfekt zu sein.

Wie erzwingen Sie (und wir meinen erzwingen) eine Datenintegritätsregel für "Expense name" in diesem Beispiel? Das erfolgt auf Serverseite. Und zwar nicht irgendwo auf Serverseite. Sie fügen die Regel der Felddefinition hinzu oder codieren sie in einem Trigger. Oder wenn Sie besonders sorgfältig sind, was alle klar denkenden Entwickler sind, machen Sie beides.

Für echte Datenintegrität gilt: Wenn "required" required bedeutet, erzwingen Sie es auf der niedrigstmöglichen Ebene.