A Mendix SDK-Grundlagen – Teil 2 | Mendix

Direkt zum Inhalt

A Mendix SDK-Grundlagen – Teil 2

Mendix Das Hauptbild von SDK Primer Pt 2 zeigt einen violetten und korallenfarbenen Hintergrund mit einem dreidimensionalen grauen Feld in der Mitte, das die Mendix Auf der einen Seite befindet sich ein Logo, auf der anderen Seite ein vorgetäuschter Code und auf der dritten Seite ein Diagramm der miteinander verbundenen Elemente.

Dies ist der zweite Blog-Beitrag in einer kurzen Serie, die zeigt, wie die Mendix Plattform-/Modell-SDKs ermöglichen Ihnen den programmgesteuerten Zugriff auf das Modell Ihrer App, ohne Mendix Studio oder Mendix Studio Pro.

Erstellen von Apps und Aufbau des Modells

Im erster SDK-Blogbeitrag Ich habe gezeigt, wie Sie mit einem Typescript-Skript und NodeJS auf ein Mendix App-Modell und exportiere Informationen aus dem Modell in ein JavaScript-Skript. Jetzt erstelle ich ein neues Mendix App mithilfe eines Plattform-/Modell-SDK-Skripts, erstellen Sie ein neues Modul in der App und platzieren Sie einige Elemente in diesem Modul.

A Mendix SDK-Grundlagen – Teil 2_Bild eines Computers mit leerem Bildschirm, der eine leere Tafel zum Starten anzeigt

Eine neue App

Für einen Blog muss der Anwendungsfall ziemlich einfach sein, daher werde ich eine App erstellen, die ein sehr einfaches Szenario zur Auftragseingabe nachahmt. Die App wird nur Folgendes enthalten:

  • Eine Bestellentität mit einer Autonummer für die Bestell-ID, einem Bestellwert, einem Bestellstatus und einem Kundennamen, die auch ein Dokument (beispielsweise eine PDF-Datei) der eingegangenen Bestellung enthalten kann.
  • Eine Auftragszeilenentität mit einer Auftragszeilen-ID, einem Produktnamen und einem Zeilenwert.
  • Eine Zuordnung mit kaskadierter Löschung zwischen den Entitäten „Auftrag“ und „Auftragsposition“.
  • Eine nicht persistente Entität, die zum Suchen von Aufträgen verwendet werden kann, deren Wert zwischen den vom Benutzer eingegebenen Maximal- und Minimalwerten liegt.
  • Eine Aufzählung, die den Bestellstatus darstellt.
  • Sicherheit auf Produktionsebene mit einer Administratorrolle und einer normalen Benutzerrolle konfiguriert. Ich habe die persistenten Entitäten für den Administrator beschreibbar, für normale Benutzer jedoch schreibgeschützt gemacht, um zu veranschaulichen, wie das gemacht wird.

Das Skript erstellt alles von Grund auf und ermöglicht auch die Kontrolle über die Mendix Version und Start-App-Vorlage, die als Grundlage für die erstellte App verwendet werden.

Ich gehe davon aus, dass wir in nachfolgenden Blogbeiträgen auf dieser App aufbauen können.

Also, um zu beginnen: Alle Aktivitäten finden in einer asynchronen Funktion namens main() statt. Dies ermöglicht uns, Await-Modifikatoren zu verwenden, um den Code zu vereinfachen.

Erstellen Sie die App

Dies ist einfach und wir benötigen lediglich einen Namen für die App und eine optionale Zusammenfassung (Beschreibung) und eine Vorlagen-ID. Wenn Sie die Vorlagen-ID nicht angeben, wird die Standardvorlage für die neueste verfügbare Mendix Version verwendet werden.

Ich werde die Vorlage „Blank Web App“ verwenden für Mendix 9.18.1

Die Vorlagen-ID für die Vorlage, die Sie für die App verwenden möchten, finden Sie unter Mendix Marktplatz und Auswahl der Option „Vorlagen“:

A Mendix SDK Primer — Teil 2_Finden Sie die Vorlage, die Sie verwenden möchten, indem Sie in der Mendix Marketplace

Klicken Sie auf die gewünschte Vorlage und dann auf der Seite der Vorlage auf die Registerkarte „Veröffentlichungen“.

A Mendix SDK Primer – Teil 2_Bild der Registerkarte „Releases“ mit der Seite einer Vorlage

Wenn Sie die einzelnen Releases öffnen, finden Sie Details dazu, einschließlich der Framework-Version, die der „Mendix Version“, für die es bestimmt ist. Nicht alle Mendix Versionen werden dargestellt. Wenn Sie eine Version verwenden müssen, die nicht angezeigt wird, wählen Sie die neueste Framework-Version vor der gewünschten Version und aktualisieren Sie die App dann in Studio Pro, nachdem die App erstellt wurde.

A Mendix SDK-Grundlagen – Teil 2_Details zur Veröffentlichung der Marketplace-Vorlage

Kopieren Sie die UUID, die für die Version angegeben ist, die Sie verwenden möchten. Dies ist die Vorlagen-ID, die in die Anweisung „createNewApp“ eingefügt werden kann.

    import { MendixPlatformClient } from "mendixplatformsdk";
    import { projects, security, domainmodels, enumerations, texts } from 'mendixmodelsdk';
    
    main().catch(console.error);
    
    
    async function main()
    {
        const client = new MendixPlatformClient();
    
        // Create the app
        const app = await client.createNewApp("SDKTestApp", {summary: "Test App created using the Mendix SDKs", templateId: "5cdf7b82-e902-4b8d-b0dd-bdd939c9e82f"});
    
        console.log("App created with App ID: " + app.appId);

Als nächstes erstellen wir eine Arbeitskopie für die App und öffnen das Modell, damit wir es aktualisieren können.

        // Open a working copy
        const workingCopy = await app.createTemporaryWorkingCopy("trunk");
        const model = await workingCopy.openModel();

Einrichten des Moduls

Als nächstes erstellen wir das neue App-Modul, geben ihm den gewünschten Namen und initialisieren die Sicherheit für das Modul. In dieser App arbeiten wir mit zwei Rollen, eine für Administratorzugriff und eine für normale Benutzer. Die Art und Weise, wie ich dies eingerichtet habe, ist nicht sehr realistisch, da ich nur versuche zu zeigen, wie Sie die Sicherheit im SDK einrichten – Administratoren haben Schreibzugriff auf Entitäten, während normale Benutzer nur Lesezugriff haben (mit einer Ausnahme).

        // Create a new module
        const module = projects.Module.createIn(model.allProjects()[0]);
        module.name = "SDKModule";
    
        // Set up module security
        const moduleSecurity = security.ModuleSecurity.createIn(module);
        const adminRole = security.ModuleRole.createIn(moduleSecurity);
        adminRole.name = "Admin";
        const userRole = security.ModuleRole.createIn(moduleSecurity);
        userRole.name = "User";

Schließlich müssen wir das Domänenmodell für das Modul erstellen und können dann mit der Befüllung beginnen.

S        // Create the module domain model
        const domainModel = domainmodels.DomainModel.createIn(module);

Erstellen einer Aufzählung

Der „Bestellstatus“ der Bestellentität soll eine Aufzählung sein. Erstellen wir diese also zuerst und weisen ihr vier mögliche Werte zu: New, On Hold, In Bearbeitung und Fertig gestellt. Der Einfachheit halber stellen wir für jeden Wert nur Beschriftungen in US-Englisch bereit.

        // Make the Order Status enumeration with its values
        const statusEnumeration = enumerations.Enumeration.createIn(module);
        statusEnumeration.name = "ENUM_Status";
    
        const statusEnumerationValue1 = enumerations.EnumerationValue.createIn(statusEnumeration);
        statusEnumerationValue1.name = "_New";
        const statusEnumerationCaption1 = texts.Text.createInEnumerationValueUnderCaption(statusEnumerationValue1);
        const statusEnumerationTranslation1 = texts.Translation.create(model);
        statusEnumerationTranslation1.text = "New";
        statusEnumerationTranslation1.languageCode = "en_US";
        statusEnumerationCaption1.translations.push(statusEnumerationTranslation1);
    
        const statusEnumerationValue2 = enumerations.EnumerationValue.createIn(statusEnumeration);
        statusEnumerationValue2.name = "Hold";
        const statusEnumerationCaption2 = texts.Text.createInEnumerationValueUnderCaption(statusEnumerationValue2);
        const statusEnumerationTranslation2 = texts.Translation.create(model);
        statusEnumerationTranslation2.text = "On Hold";
        statusEnumerationTranslation2.languageCode = "en_US";
        statusEnumerationCaption2.translations.push(statusEnumerationTranslation2);
       
        const statusEnumerationValue3 = enumerations.EnumerationValue.createIn(statusEnumeration);
        statusEnumerationValue3.name = "Progress";
        const statusEnumerationCaption3 = texts.Text.createInEnumerationValueUnderCaption(statusEnumerationValue3);
        const statusEnumerationTranslation3 = texts.Translation.create(model);
        statusEnumerationTranslation3.text = "In Progress";
        statusEnumerationTranslation3.languageCode = "en_US";
        statusEnumerationCaption3.translations.push(statusEnumerationTranslation3);
       
        const statusEnumerationValue4 = enumerations.EnumerationValue.createIn(statusEnumeration);
        statusEnumerationValue4.name = "Completed";
        const statusEnumerationCaption4 = texts.Text.createInEnumerationValueUnderCaption(statusEnumerationValue4);
        const statusEnumerationTranslation4 = texts.Translation.create(model);
        statusEnumerationTranslation4.text = "Completed";
        statusEnumerationTranslation4.languageCode = "en_US";
        statusEnumerationCaption4.translations.push(statusEnumerationTranslation4);

OK, das war ziemlich unkompliziert. Machen wir also mit etwas Komplexerem weiter.

Entitäten erstellen

Während wir dies durcharbeiten, sollten Sie eine klare Übereinstimmung zwischen dem Code, den wir hier haben, und der Ausführung derselben Aktionen in Studio Pro erkennen.

Beginnen wir mit der Auftragszeilenentität. Die Entitätserstellung ist einfach. Der Standort bestimmt die Position, die die Entität im Studio Pro-Domänenmodelldiagramm einnimmt: wobei x die horizontale Position in Diagrammpunkten angibt, gemessen von der linken Seite des Diagramms, und y die vertikale Position in Diagrammpunkten angibt, gemessen von der oberen Seite des Diagramms nach unten. Beide spiegeln die obere linke Ecke des Entitätsfelds wider.

        // Create the Order Line Entity and attributes
        const orderLineEntity = domainmodels.Entity.createIn(domainModel);
        orderLineEntity.name = "OrderLine";
        orderLineEntity.location = { x: 100, y: 100};
        orderLineEntity.documentation = "The Order Line entity created using the Model SDK";

Die ersten beiden Attribute sind ebenfalls unkompliziert – eines ist eine Ganzzahl und das andere eine Dezimalzahl.

        const orderLineIdAttributeType = domainmodels.IntegerAttributeType.create(model);
        const orderLineIdAttributeDefault = domainmodels.StoredValue.create(model);
        orderLineIdAttributeDefault.defaultValue = "";
        const orderLineIdAttribute = domainmodels.Attribute.createIn(orderLineEntity);
        orderLineIdAttribute.name = "OrderLineId";
        orderLineIdAttribute.type = orderLineIdAttributeType;
        orderLineIdAttribute.value = orderLineIdAttributeDefault;
        orderLineIdAttribute.documentation = "The Id of this Order Line unique within an order created using the Model SDK";
    
        const orderLineValueAttributeType = domainmodels.DecimalAttributeType.create(model);
        const orderLineValueAttributeDefault = domainmodels.StoredValue.create(model);
        orderLineValueAttributeDefault.defaultValue = "";
        const orderLineValueAttribute = domainmodels.Attribute.createIn(orderLineEntity);
        orderLineValueAttribute.name = "OrderLineValue";
        orderLineValueAttribute.type = orderLineValueAttributeType;
        orderLineValueAttribute.value = orderLineValueAttributeDefault;
        orderLineValueAttribute.documentation = "The Value of this Order Line created using the Model SDK";

Das dritte Attribut weist einige Besonderheiten auf:

  • Das Attribut ist eine Zeichenfolge und hat eine angegebene Länge (300).
  • Das Attribut hat einen Standardwert, der nicht leer ist: „Happy Days“.
  • Auf das Attribut wird eine obligatorische Validierungsregel angewendet. Dafür muss eine Fehlermeldung definiert werden. Auch hier verwenden wir der Einfachheit halber nur amerikanisches Englisch.
        const orderLineProductNameAttributeType = domainmodels.StringAttributeType.create(model);
        orderLineProductNameAttributeType.length = 300;
        const orderLineProductNameAttributeDefault = domainmodels.StoredValue.create(model);
        orderLineProductNameAttributeDefault.defaultValue = "Happy Days";
        const orderLineProductNameAttribute = domainmodels.Attribute.createIn(orderLineEntity);
        orderLineProductNameAttribute.name = "ProductName";
        orderLineProductNameAttribute.type = orderLineProductNameAttributeType;
        orderLineProductNameAttribute.value = orderLineProductNameAttributeDefault;
        orderLineProductNameAttribute.documentation = "The name of the Product the subject of this Order Line created using the Model SDK";
        const orderLineEntityValidationRule1 = domainmodels.ValidationRule.createIn(orderLineEntity);
        orderLineEntityValidationRule1.attribute = orderLineProductNameAttribute;
        domainmodels.RequiredRuleInfo.createIn(orderLineEntityValidationRule1);
        const orderLineEntityValidationRuleText = texts.Text.createInValidationRuleUnderErrorMessage(orderLineEntityValidationRule1);
        const orderLineEntityValidationRuleTranslation = texts.Translation.create(model);
        orderLineEntityValidationRuleTranslation.text = "A Product Name must be entered";
        orderLineEntityValidationRuleTranslation.languageCode = "en_US";
        orderLineEntityValidationRuleText.translations.push(orderLineEntityValidationRuleTranslation);

Als nächstes erstellen wir die Entität order. Diese unterscheidet sich ein wenig von order line, da sie eine Spezialisierung von System.FileDocument ist. Wie ich bereits im SDK-Blog Teil 1, die Verwendung von Systemmodulelementen ist etwas umständlich, da Sie aus Sicherheitsgründen nicht direkt in einem SDK-Skript darauf verweisen können. Wir verwenden daher einen Workaround. Wenn die Generalisierungsentität nicht im Systemmodul wäre, könnten wir direkt darauf verweisen.

        // Create the Order Entity and its attributes
        const orderEntity = domainmodels.Entity.createIn(domainModel);
        orderEntity.name = "Order";
        orderEntity.location = { x: 500, y: 100};
        orderEntity.documentation = "The Order Entity specializing from System.FileDocument created using the Model SDK";
        const orderEntityGeneralization = domainmodels.Generalization.createIn(orderEntity);
        (orderEntityGeneralization as any)["__generalization"].updateWithRawValue("System.FileDocument");

Vorerst machen wir nichts Ausgefallenes mit den Attributen in der Bestellentität, der Code ist also unkompliziert. Beachten Sie jedoch erneut, dass es ein Zeichenfolgenattribut mit einer angegebenen Länge (100) gibt, das AutoNumber-Attribut OrderId einen definierten Standardstartwert (auf „1“ eingestellt) hat und der OrderStatus (der die Aufzählung verwendet, die wir oben erstellt haben) seinen Standardwert („_New“) hat.

        const orderIdAttributeType = domainmodels.AutoNumberAttributeType.create(model);
        const orderIdAttributeDefault = domainmodels.StoredValue.create(model);
        orderIdAttributeDefault.defaultValue = "1";
        const orderIdAttribute = domainmodels.Attribute.createIn(orderEntity);
        orderIdAttribute.name = "OrderId";
        orderIdAttribute.type = orderIdAttributeType;
        orderIdAttribute.value = orderIdAttributeDefault;
        orderIdAttribute.documentation = "The Id of this Order Id unique accross the system created using the Model SDK";
    
        const orderValueAttributeType = domainmodels.DecimalAttributeType.create(model);
        const orderValueAttributeDefault = domainmodels.StoredValue.create(model);
        orderValueAttributeDefault.defaultValue = "";
        const orderValueAttribute = domainmodels.Attribute.createIn(orderEntity);
        orderValueAttribute.name = "OrderValue";
        orderValueAttribute.type = orderValueAttributeType;
        orderValueAttribute.value = orderValueAttributeDefault;
        orderValueAttribute.documentation = "The Value of this Order created using the Model SDK";
    
        const customerNameAttributeType = domainmodels.StringAttributeType.create(model);
        customerNameAttributeType.length = 100;
        const customerNameAttributeDefault = domainmodels.StoredValue.create(model);
        customerNameAttributeDefault.defaultValue = "";
        const customerNameAttribute = domainmodels.Attribute.createIn(orderEntity);
        customerNameAttribute.name = "CustomerName";
        customerNameAttribute.type = customerNameAttributeType;
        customerNameAttribute.value = customerNameAttributeDefault;
        customerNameAttribute.documentation = "The name of the Customer who raised this Order created using the Model SDK";
    
        const orderStatusAttributeType = domainmodels.EnumerationAttributeType.create(model);
        orderStatusAttributeType.enumeration = statusEnumeration;
        const orderStatusAttributeDefault = domainmodels.StoredValue.create(model);
        orderStatusAttributeDefault.defaultValue = "_New";
        const orderStatusAttribute = domainmodels.Attribute.createIn(orderEntity);
        orderStatusAttribute.name = "OrderStatus";
        orderStatusAttribute.type = orderStatusAttributeType;
        orderStatusAttribute.value = orderStatusAttributeDefault;
        orderStatusAttribute.documentation = "The status of this Order created using the Model SDK";

Erstellen Sie die nicht persistente OrderSearch-Entität und ihre Attribute. Die Nichtpersistenz wird mithilfe eines einfachen Flags festgelegt.

        // Create the OrderSearch non-persistent Entity
        const orderSearchEntity = domainmodels.Entity.createIn(domainModel);
        orderSearchEntity.name = "OrderSearch";
        orderSearchEntity.location = { x: 900, y: 100};
        orderSearchEntity.documentation = "A non-persistent entity used for searching Orders created using the Model SDK";
        const orderSearchEntityNoGeneralization = domainmodels.NoGeneralization.createIn(orderSearchEntity);
        orderSearchEntityNoGeneralization.persistable = false;
    
        const orderSearchMinimumValueAttributeType = domainmodels.DecimalAttributeType.create(model);
        const orderSearchMinimumValueDefaultValue = domainmodels.StoredValue.create(model);
        orderSearchMinimumValueDefaultValue.defaultValue = "";
        const orderSearchMiniumuValueAttribute = domainmodels.Attribute.createIn(orderSearchEntity);
        orderSearchMiniumuValueAttribute.name = "MinimumValue";
        orderSearchMiniumuValueAttribute.type = orderSearchMinimumValueAttributeType;
        orderSearchMiniumuValueAttribute.value = orderSearchMinimumValueDefaultValue;
        orderSearchMiniumuValueAttribute.documentation = "The search order value minimum value created using the Model SDK";
    
        const orderSearchMaximumValueAttributeType = domainmodels.DecimalAttributeType.create(model);
        const orderSearchMaximumValueDefaultValue = domainmodels.StoredValue.create(model);
        orderSearchMaximumValueDefaultValue.defaultValue = "";
        const orderSearchMaxiumuValueAttribute = domainmodels.Attribute.createIn(orderSearchEntity);
        orderSearchMaxiumuValueAttribute.name = "MaximumValue";
        orderSearchMaxiumuValueAttribute.type = orderSearchMaximumValueAttributeType;
        orderSearchMaxiumuValueAttribute.value = orderSearchMaximumValueDefaultValue;
        orderSearchMaxiumuValueAttribute.documentation = "The search order value maximum value created using the Model SDK";

Schließlich müssen wir die Verknüpfung zwischen den Entitäten Auftrag und Auftragszeile erstellen. Die Kinderverbindung und übergeordneteVerbindung Einstellungen sind ähnlich denen für die Entität Standort Werte, aber statt absoluter Positionen im Diagramm beziehen sie sich auf Prozentwerte. Die x bezieht sich darauf, wie weit die Entität im Diagramm verläuft, und die y gibt an, wie weit unten die Seite der Entität im Diagramm ist. Mit diesen Angaben können Sie festlegen, dass die Linie, die die Assoziation darstellt, an jedem beliebigen Punkt der Entitätsbox endet.

Wenn die Zuordnung erstellt wurde, legen wir das Löschverhalten so fest, dass zugehörige OrderLine-Objekte gelöscht werden, wenn ein Bestellobjekt gelöscht wird.

        // Create the association between the OrderLine and Order Entities
        const association = domainmodels.Association.createIn(domainModel);
        association.name = "OrderLine_Order"; 
        association.child = orderEntity;
        association.parent = orderLineEntity;
        association.type = domainmodels.AssociationType.Reference;
        association.owner = domainmodels.AssociationOwner.Default;
        association.childConnection = { x: 0, y: 50};
        association.parentConnection = { x: 100, y: 50};
        association.documentation = "Association created using the Model SDK";
        const associationDeleteBehavior = domainmodels.AssociationDeleteBehavior.createIn(association);
        associationDeleteBehavior.childDeleteBehavior = domainmodels.DeletingBehavior.DeleteMeAndReferences;
        association.deleteBehavior = associationDeleteBehavior;

Das Ganze sollte in Form eines Domänenmodelldiagramms entstehen, das wie folgt aussieht:

A Mendix SDK-Grundlagen – Teil 2_Domänenmodelldiagramm

Entitätssicherheitseinstellungen

Jetzt müssen wir eine Liste von Einstellungen ausführen, um die Sicherheit für jede der von uns erstellten Administrator- und Benutzerrollen zu steuern. Jedes Attribut und jede Zuordnung muss separat eingerichtet werden, und wenn die Rollen unterschiedlich sind, muss auch jede Rolle separat eingerichtet werden. Dies ist relativ unkompliziert, wenn auch ziemlich langwierig.

Außerdem müssen wir die Einstellungen für die geerbten Attribute von System.FileDocument mit einem ähnlichen Workaround wie zuvor definieren, da auf das Systemmodul nicht direkt zugegriffen werden kann.

        // Set up the security on the new module's Entities
        // Security settings for Order Line Entity for Admin user
        const orderLineEntityAdminAccessRule = domainmodels.AccessRule.createInEntityUnderAccessRules(orderLineEntity);
        orderLineEntityAdminAccessRule.allowCreate = true;
        orderLineEntityAdminAccessRule.allowDelete = true;
        orderLineEntityAdminAccessRule.defaultMemberAccessRights = domainmodels.MemberAccessRights.ReadWrite;
        orderLineEntityAdminAccessRule.moduleRoles.push(adminRole);
        
        const orderLineEntityAdminAccessOrderLineId = domainmodels.MemberAccess.createIn(orderLineEntityAdminAccessRule);
        orderLineEntityAdminAccessOrderLineId.attribute = orderLineIdAttribute;
        orderLineEntityAdminAccessOrderLineId.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderLineEntityAdminAccessProductName = domainmodels.MemberAccess.createIn(orderLineEntityAdminAccessRule);
        orderLineEntityAdminAccessProductName.attribute = orderLineProductNameAttribute;
        orderLineEntityAdminAccessProductName.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderLineEntityAdminAccessOrderLineValue = domainmodels.MemberAccess.createIn(orderLineEntityAdminAccessRule);
        orderLineEntityAdminAccessOrderLineValue.attribute = orderLineValueAttribute;
        orderLineEntityAdminAccessOrderLineValue.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderLineEntityAdminAccessAssociation = domainmodels.MemberAccess.createIn(orderLineEntityAdminAccessRule);
        orderLineEntityAdminAccessAssociation.association = association;
        orderLineEntityAdminAccessAssociation.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        // Security settings for Order Line Entity for regular user
        const orderLineEntityUserAccessRule = domainmodels.AccessRule.createInEntityUnderAccessRules(orderLineEntity);
        orderLineEntityUserAccessRule.allowCreate = false;
        orderLineEntityUserAccessRule.allowDelete = false;
        orderLineEntityUserAccessRule.defaultMemberAccessRights = domainmodels.MemberAccessRights.ReadOnly;
        orderLineEntityUserAccessRule.moduleRoles.push(userRole);
        
        const orderLineEntityUserAccessOrderLineId = domainmodels.MemberAccess.createIn(orderLineEntityUserAccessRule);
        orderLineEntityUserAccessOrderLineId.attribute = orderLineIdAttribute;
        orderLineEntityUserAccessOrderLineId.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderLineEntityUserAccessProductName = domainmodels.MemberAccess.createIn(orderLineEntityUserAccessRule);
        orderLineEntityUserAccessProductName.attribute = orderLineProductNameAttribute;
        orderLineEntityUserAccessProductName.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderLineEntityUserAccessOrderLineValue = domainmodels.MemberAccess.createIn(orderLineEntityUserAccessRule);
        orderLineEntityUserAccessOrderLineValue.attribute = orderLineValueAttribute;
        orderLineEntityUserAccessOrderLineValue.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderLineEntityUserAccessAssociation = domainmodels.MemberAccess.createIn(orderLineEntityUserAccessRule);
        orderLineEntityUserAccessAssociation.association = association;
        orderLineEntityUserAccessAssociation.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        // Security settings for Order Entity for Admin user
        const orderEntityAdminAccessRule = domainmodels.AccessRule.createInEntityUnderAccessRules(orderEntity);
        orderEntityAdminAccessRule.allowCreate = true;
        orderEntityAdminAccessRule.allowDelete = true;
        orderEntityAdminAccessRule.defaultMemberAccessRights = domainmodels.MemberAccessRights.ReadWrite;
        orderEntityAdminAccessRule.moduleRoles.push(adminRole);
        
        const orderEntityAdminAccessOrderId = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        orderEntityAdminAccessOrderId.attribute = orderIdAttribute;
        orderEntityAdminAccessOrderId.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityAdminAccessOrderValue = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        orderEntityAdminAccessOrderValue.attribute = orderValueAttribute;
        orderEntityAdminAccessOrderValue.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderEntityAdminAccessCustomerName = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        orderEntityAdminAccessCustomerName.attribute = customerNameAttribute;
        orderEntityAdminAccessCustomerName.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderEntityAdminAccessOrderStatus = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        orderEntityAdminAccessOrderStatus.attribute = orderStatusAttribute;
        orderEntityAdminAccessOrderStatus.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderEntityAdminAccessName = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        (orderEntityAdminAccessName as any)["__attribute"].updateWithRawValue("System.FileDocument.Name");
        orderEntityAdminAccessName.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderEntityAdminAccessFileId = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        (orderEntityAdminAccessFileId as any)["__attribute"].updateWithRawValue("System.FileDocument.FileID");
        orderEntityAdminAccessFileId.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityAdminAccessDeleteAfterDownload = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        (orderEntityAdminAccessDeleteAfterDownload as any)["__attribute"].updateWithRawValue("System.FileDocument.DeleteAfterDownload");
        orderEntityAdminAccessDeleteAfterDownload.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderEntityAdminAccessContents = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        (orderEntityAdminAccessContents as any)["__attribute"].updateWithRawValue("System.FileDocument.Contents");
        orderEntityAdminAccessContents.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderEntityAdminAccessHasContents = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        (orderEntityAdminAccessHasContents as any)["__attribute"].updateWithRawValue("System.FileDocument.HasContents");
        orderEntityAdminAccessHasContents.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityAdminAccessSize = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        (orderEntityAdminAccessSize as any)["__attribute"].updateWithRawValue("System.FileDocument.Size");
        orderEntityAdminAccessSize.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        // Security settings for Order Entity for regular user
        const orderEntityUserAccessRule = domainmodels.AccessRule.createInEntityUnderAccessRules(orderEntity);
        orderEntityUserAccessRule.allowCreate = false;
        orderEntityUserAccessRule.allowDelete = false;
        orderEntityUserAccessRule.defaultMemberAccessRights = domainmodels.MemberAccessRights.ReadOnly;
        orderEntityUserAccessRule.moduleRoles.push(userRole);
        
        const orderEntityUserAccessOrderId = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        orderEntityUserAccessOrderId.attribute = orderIdAttribute;
        orderEntityUserAccessOrderId.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccessOrderValue = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        orderEntityUserAccessOrderValue.attribute = orderValueAttribute;
        orderEntityUserAccessOrderValue.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccesCustomerName = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        orderEntityUserAccesCustomerName.attribute = customerNameAttribute;
        orderEntityUserAccesCustomerName.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccesOrderStatus = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        orderEntityUserAccesOrderStatus.attribute = orderStatusAttribute;
        orderEntityUserAccesOrderStatus.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccessName = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        (orderEntityUserAccessName as any)["__attribute"].updateWithRawValue("System.FileDocument.Name");
        orderEntityUserAccessName.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccessFileId = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        (orderEntityUserAccessFileId as any)["__attribute"].updateWithRawValue("System.FileDocument.FileID");
        orderEntityUserAccessFileId.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccessDeleteAfterDownload = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        (orderEntityUserAccessDeleteAfterDownload as any)["__attribute"].updateWithRawValue("System.FileDocument.DeleteAfterDownload");
        orderEntityUserAccessDeleteAfterDownload.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccessContents = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        (orderEntityUserAccessContents as any)["__attribute"].updateWithRawValue("System.FileDocument.Contents");
        orderEntityUserAccessContents.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccessHasContents = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        (orderEntityUserAccessHasContents as any)["__attribute"].updateWithRawValue("System.FileDocument.HasContents");
        orderEntityUserAccessHasContents.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccessSize = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        (orderEntityUserAccessSize as any)["__attribute"].updateWithRawValue("System.FileDocument.Size");
        orderEntityUserAccessSize.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        // Security settings for OrderSearch Entity for both Admin and regular users
        const orderSearchEntityUserAccessRule = domainmodels.AccessRule.createInEntityUnderAccessRules(orderSearchEntity);
        orderSearchEntityUserAccessRule.allowCreate = true;
        orderSearchEntityUserAccessRule.allowDelete = true;
        orderSearchEntityUserAccessRule.defaultMemberAccessRights = domainmodels.MemberAccessRights.ReadWrite;
        orderSearchEntityUserAccessRule.moduleRoles.push(adminRole);
        orderSearchEntityUserAccessRule.moduleRoles.push(userRole);
        
        const orderSearchEntityUserAccessMinimumValue = domainmodels.MemberAccess.createIn(orderSearchEntityUserAccessRule);
        orderSearchEntityUserAccessMinimumValue.attribute = orderSearchMiniumuValueAttribute;
        orderSearchEntityUserAccessMinimumValue.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderSearchEntityUserAccessMaximumValue = domainmodels.MemberAccess.createIn(orderSearchEntityUserAccessRule);
        orderSearchEntityUserAccessMaximumValue.attribute = orderSearchMaxiumuValueAttribute;
        orderSearchEntityUserAccessMaximumValue.accessRights = domainmodels.MemberAccessRights.ReadWrite;e

Abschlüsse

Fast fertig! Als Nächstes setzen wir das Projekt auf die Sicherheitsstufe „Produktion“ (alles prüfen) und nehmen die Modulrollen, die wir zuvor erstellt haben, und ordnen sie den Projektrollen zu.

Löschen Sie dann die Änderungen und führen Sie ein Commit durch.

        // Set up the project level security roles. Set Production level security and put the module roles into the project roles
        const projectSecurity = await model.allProjectSecurities()[0].load();
        projectSecurity.securityLevel = security.SecurityLevel.CheckEverything;
        projectSecurity.checkSecurity = true;
    
        const adminRoleName = projectSecurity.adminUserRoleName;
        const projectAdminRole = projectSecurity.userRoles.find(t => t.name === adminRoleName);
        const projectUserRole = projectSecurity.userRoles.find(t => t.name === "User");
    
        if (projectAdminRole != undefined)
            projectAdminRole.moduleRoles.push(adminRole);
        if (projectUserRole != undefined)
            projectUserRole.moduleRoles.push(userRole);
    
        console.log("Committing changes to the repository");
    
        await model.flushChanges();
        await workingCopy.commitToRepository("trunk", {commitMessage: "App created and modified by my Create App Model SDK script"});
    
    }

Mit diesem Skript, das in einem NodeJS-initialisierten Ordner gespeichert ist (siehe SDK-Blog 1 für die Vorgehensweise) als TypeScript-Datei (ich habe meine 'createapp.ts' genannt), können Sie das TypeScript jetzt mit tsc und verwenden Sie den Knoten, um das generierte JavaScript auszuführen.

Wenn Sie dieses Skript ausführen, sollten Sie nun eine App mit einem hinzugefügten Modul und einem ausgefüllten Domänenmodell erstellt haben. OK, das ist noch nicht viel – wir werden im dritten Teil dieser Blogserie damit beginnen, das Ganze etwas konkreter zu gestalten.

A Mendix SDK Primer – Teil 2_Bild eines Laptops mit einem roten Bildschirm und einer Piratenflagge, die anzeigt, dass etwas nicht funktioniert hat

Fehler

Einige Warnungen aus der Erfahrung mit der Verwendung von SDKs zum Aktualisieren von Apps. Das SDK-System meldet nicht immer Fehler. Wenn Sie einen Fehler machen, können Sie dies an verschiedenen Stellen im Prozess erfahren:

  • Der TypeScript-Compiler meldet Probleme beim Kompilieren in JavaScript.
  • Wenn Sie JavaScript mithilfe von Node ausführen, werden Ihnen Probleme gemeldet.
  • Wenn Sie die App in Studio Pro öffnen, werden auf der Registerkarte „Fehler“ möglicherweise Fehler/Probleme gemeldet, oder Ihre Erstellung/Änderung sieht falsch aus.
  • Wenn es wirklich schlimm ist, meldet Studio Pro möglicherweise beim Öffnen Ausnahmen oder kann die App überhaupt nicht öffnen und stürzt ab.

Dies veranlasst mich zu folgender Aussage: immer eine Ausweichposition haben. Erstellen Sie eine Sicherungskopie des Projekts, wenn es ein bestehendes Projekt ist. Kopieren Sie das Projekt und arbeiten Sie an der Kopie. Arbeiten Sie an einem Zweig. Vielleicht alle davon…

A Mendix SDK Primer – Teil 2_Bild einer Holzoberfläche mit mehreren Karten darauf, eine Karte enthält den Satz „Tu, was ich sage, nicht, was ich tue“

Tu, was ich sage, nicht, was ich tue

OK, viele erfahrene Entwickler schrecken wahrscheinlich vor dem obigen Code zurück. Ich bin mir sicher, dass es offensichtlich ist, dass diese Skripte sehr lang und repetitiv werden können (Änderungen am Domänenmodell sind im Vergleich zum Einrichten von Formularen und Mikroflüssen beispielsweise einfach), aber ich möchte die Dinge so klar wie möglich halten, insbesondere für einen Blogbeitrag. Dazu gehört die Verwendung aussagekräftiger Variablennamen und die Vermeidung komplexer Syntax.

Als Erstes können wir Funktionen verwenden, die dabei helfen, das Skript kürzer zu machen, zum Beispiel so:

        createEnumerationOption(model, statusEnumeration, "_New", "New");
        createEnumerationOption(model, statusEnumeration, "Hold", "On Hold");
        createEnumerationOption(model, statusEnumeration, "Progress", "In Progress");
        createEnumerationOption(model, statusEnumeration, "Completed", "Completed");
    
    .....
    
    function createEnumerationOption(model: IModel, enumeration: enumerations.Enumeration, name: string, caption_en_US: string)
    {
        const value = enumerations.EnumerationValue.createIn(enumeration);
        value.name = name;
        const caption = texts.Text.createInEnumerationValueUnderCaption(value);
        const translation = texts.Translation.create(model);
        translation.text = caption_en_US;
        translation.languageCode = "en_US";
        caption.translations.push(translation);
    }

Sie werden wahrscheinlich den Wert des Aufbaus einer Bibliothek von Funktionen und/oder Klassen erkennen, die Sie in eine Vielzahl von SDK-Skripten, die Sie schreiben, einbinden und verwenden können.

Lange Skripte können aber auch aus anderen Gründen zu Leistungsproblemen führen. Ich habe drei Dinge gelernt, um den Speicheraufwand zu reduzieren und die Geschwindigkeit zu verbessern:

  • Rufen Sie häufig FlushChanges auf. Dadurch werden die bisher vorgenommenen Änderungen aus Ihrer lokalen NodeJS-Umgebung in die Mendix ModelServer reduziert Ihren lokalen Speicherbedarf.
  • Verwenden Sie nach Möglichkeit lokal gültige Variablen. Dies ist durch die Verwendung von Funktionen ganz einfach. Lokal gültige Variablen werden in dem von Ihnen definierten Block erstellt, leben und sterben dort. So wird die Ansammlung von Daten reduziert, die im Rest des Skripts mitgeführt werden. Jeder Variablenverweis in Ihrem Skript erfordert eine Art Nachschlagevorgang in den JavaScript-Interna. Je mehr Variablen Sie definiert haben, desto länger dauert dies (und desto mehr Speicher wird verbraucht).
  • Halten Sie Variablennamen kurz oder zumindest kürzer, da dies die Ausführungszeiten verbessert und den Speicheraufwand bei langen Skripten verringert. Natürlich muss man zwischen Kürze und Klarheit abwägen. Ich habe eine App, die TypeScript-Dateien schreibt, um Apps für mich zu ändern, und ich kann sie so einstellen, dass sie durch Umlegen eines Schalters aussagekräftige Variablennamen (zum Debuggen) oder kurze Variablennamen (für die Produktion) erzeugt. Alternativ können Sie ein separates Tool verwenden, um das generierte JavaScript vor der Ausführung zu minimieren, aber ich habe das nicht ausprobiert und weiß daher nicht, wie gut es funktioniert.

Im unten genannten Github-Repository habe ich zwei Skripte eingefügt, ein einfaches Skript, das in diesem Blog gezeigt wird („createapp.ts“), und ein alternatives Skript, das einige der hier vorgeschlagenen Änderungen implementiert („createapp2.ts“). Jedes Skript erstellt dieselbe App, aber das zweite ist deutlich kürzer und meiner Meinung nach leichter zu befolgen.

Damit ist dieser Beitrag abgeschlossen. Die Skripte sind auf Github verfügbar unter https://github.com/Adrian-Preston/SDKBlog2.

Ich werde mit der Entwicklung einiger Funktionen in Teil drei fortfahren – in Kürze verfügbar!

Wählen Sie Ihre Sprache