A Mendix SDK 入门 — 第 2 部分 | Mendix

跳到主要内容

A Mendix SDK 入门 — 第二部分

Mendix SDK Primer Pt 2 主图显示紫色和珊瑚色背景,中间有一个 3D 灰色框,显示 Mendix 一面是徽标,另一面是一些伪代码,第三面是连接在一起的元素图。

这是本系列的第二篇博客文章,阐述了 Mendix 平台/模型 SDK 可让你以编程方式访问应用模型,而无需使用 Mendix 工作室或 Mendix 工作室专业版。

创建应用程序并构建模型

第一篇 SDK 博客文章 我演示了如何使用 TypeScript 脚本和 NodeJS 来访问 Mendix 应用模型并将模型中的信息导出到 JavaScript 脚本中。现在我将创建一个新的 Mendix 使用平台/模型 SDK 脚本开发应用程序,在应用程序中构建一个新模块,并将一些元素放入该模块中。

A Mendix SDK 入门 — 第 2 部分_计算机的图像,屏幕上显示空白,表示可以从空白处开始

一个新的应用程序

对于博客来说,用例必须非常简单,因此我将创建一个模拟非常简单的订单输入场景的应用程序。该应用程序仅包含以下内容:

  • 订单实体,具有订单 ID、订单价值、订单状态和客户名称的自动编号,还可以保存收到的订单的文档(可能是 PDF)。
  • 具有订单行 ID、产品名称和行值的订单行实体。
  • 订单和订单行实体之间的级联删除关联。
  • 非持久实体,可用于搜索用户输入的最大值和最小值之间的订单。
  • 表示订单状态的枚举。
  • 配置了管理员角色和普通用户角色的生产级安全性。我已将持久实体设置为管理员可写,但普通用户只读,只是为了说明如何做到这一点。

该脚本将从头开始构建所有这些,并允许控制 Mendix 版本和起始应用程序模板,作为创建应用程序的基础。

我希望我们可以在后续的博客文章中构建这个应用程序。

首先:所有活动都发生在名为 main() 的异步函数中。这使我们能够使用 await 修饰符来简化代码。

创建应用程序

这很简单,我们只需要为应用程序命名,以及可选的摘要(描述)和模板 ID。如果您不指定模板 ID,则默认模板为最新可用的 Mendix 版本将被使用。

我将使用空白 Web 应用模板 Mendix 9.18.1.

您可以通过查看 Mendix 市场并选择“模板”选项:

A Mendix SDK 入门 — 第 2 部分_通过查看 Mendix 车型市场

单击您想要的模板,然后单击模板页面内的“发布”选项卡。

A Mendix SDK 入门 — 第 2 部分_发布选项卡的图像显示了模板的页面

如果你打开各个版本,你会发现与每个版本相关的详细信息,包括与“Mendix 版本”。并非所有 Mendix 版本已显示。如果您需要使用未显示的版本,请选择所需版本之前的最新框架版本,然后在构建应用程序后在 Studio Pro 中升级应用程序。

A Mendix SDK 入门 — 第 2 部分_市场模板发布详情

复制您想要使用的版本所给出的 UUID — 这是可插入到 createNewApp 语句中的模板 ID。

    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);

接下来,我们为应用程序创建一个工作副本并打开模型,以便我们可以更新它。

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

设置模块

接下来,我们创建新的应用模块并为其指定我们选择的名称,然后初始化模块的安全性。在此应用中,我们将使用两个角色,一个用于管理员访问,一个用于普通用户。我设置的方式不太现实,因为我只是想展示如何在 SDK 中设置安全性 — 管理员将拥有对实体的写入权限,而普通用户只有读取权限(有一个例外)。

        // 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";

最后,我们需要为模块创建领域模型,并准备开始填充它。

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

创建枚举

订单实体上的“订单状态”是一个枚举,因此我们首先创建它并赋予它四个可能的值: 全新发布, 預約申請 (On Hold), 处理中完成。为了简单起见,我们只为每个值提供美式英语标题。

        // 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);

好的,这很简单,那么让我们继续讨论一些更复杂的内容。

创建实体

当我们完成这项工作时,您应该会看到我们这里的代码与在 Studio Pro 中执行相同操作之间存在清晰的对应关系。

让我们从订单行实体开始。实体创建很简单。位置决定了实体在 Studio Pro 域模型图中的位置:其中 x 指定从图左侧测量的图点的水平位置,y 指定从图顶部测量的图点的垂直位置。两者都反映了实体框的左上角。

        // 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";

前两个属性也很简单——一个是整数,另一个是小数。

        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";

第三个属性添加了一些细节:

  • 该属性是一个字符串,并指定了长度(300)。
  • 该属性有一个不为空的默认值:“快乐的日子”。
  • 该属性已应用强制验证规则。这需要定义错误消息。同样,为了简单起见,我们仅使用美国英语。
        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);

接下来,我们将创建订单实体。这与订单行略有不同,因为它是 System.FileDocument 的特化。正如我在 SDK 博客第 1 部分,使用系统模块元素有点尴尬,因为出于安全原因,您不能在 SDK 脚本中直接引用它们。所以我们使用了一种解决方法。如果泛化实体不在系统模块中,那么我们可以直接引用它。

        // 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");

目前,我们还没有对订单实体中的属性进行任何花哨的操作,因此代码很简单,但请再次注意,有一个指定长度的字符串属性(100),AutoNumber OrderId 属性具有定义的默认起始值​​(设置为'1'),并且 OrderStatus(使用我们上面创建的枚举)具有其默认值设置('_New')。

        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";

创建非持久性 OrderSearch 实体及其属性。使用简单标志设置非持久性。

        // 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";

最后,我们需要创建订单和订单行实体之间的关联。 childConnection 以及 父连接 设置与实体的设置类似 值,但它们不是图表中的绝对位置,而是百分比。因此 x 指的是图中实体的宽度,以及 y 指图中实体的下行距离。使用这些,您可以将表示关联的线设置为在实体框上的任何点结束。

当关联创建后,我们设置删除行为,以便在删除订单对象时删除关联的 OrderLine 对象。

        // 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;

这一切应该给出如下所示的领域模型图:

A Mendix SDK 入门——第二部分_领域模型图

实体安全设置

现在我们需要执行一系列设置来控制我们创建的每个管理员和用户角色的安全性。每个属性和关联都必须单独设置,如果角色不同,则每个角色也必须单独设置。这相对简单,但有点冗长。

另外,由于系统模块不能直接访问,因此我们必须使用与以前类似的解决方法来定义从 System.FileDocument 继承的属性的设置。

        // 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

完成

现在快完成了!接下来,我们将项目设置为“生产”(检查所有内容)安全级别,并将我们之前创建的模块角色映射到项目角色。

然后刷新更改并提交。

        // 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"});
    
    }

因此,将此脚本保存到 NodeJS 初始化文件夹中(参见 SDK 博客 1 有关如何执行此操作)作为 TypeScript 文件(我将我的命名为“createapp.ts”),现在你可以使用以下方法将 TypeScript 编译为 JavaScript TSC 并使用节点执行生成的 JavaScript。

运行此脚本后,您现在应该已经创建了一个应用程序,其中添加了模块并填充了域模型。好吧,它现在还没有做太多事情——我们将在本博客系列的第三部分开始添加一些内容。

A Mendix SDK 入门 — 第 2 部分_笔记本电脑的图片,屏幕变红,上面挂着一面海盗旗,表示出现问题

故障

使用 SDK 更新应用时,根据经验,需要注意以下几点。SDK 系统并不总是会报告错误。当您犯错时,您可以在流程的不同阶段了解到这一事实:

  • TypeScript 编译器在编译为 JavaScript 时报告问题。
  • 当您使用节点运行 JavaScript 时,问题就会报告给您。
  • 当您在 Studio Pro 中打开应用程序时,错误选项卡中可能会报告错误/问题,或者您创建/更改的内容看起来有误。
  • 如果情况真的很糟糕,那么 Studio Pro 可能会在打开时报告异常,或者根本无法打开应用程序并失败。

这让我想说: 总是有后备位置。如果项目是现有项目,请备份该项目。复制项目并在副本上工作。在分支上工作。也许所有这些……

A Mendix SDK 入门 — 第 2 部分_木质表面的图片,上面有几张卡片,其中一张卡片上写着一句话 - 照我说的做,不要照我做的做

按我说的做,不要按我做的做

好吧,许多经验丰富的开发人员可能对上面的代码感到畏缩。我确信这些脚本可能非常长且重复(与设置表单和微流相比,域模型更改很简单),但我喜欢尽可能保持清晰,尤其是对于博客文章而言。这涉及使用有意义的变量名并避免复杂的语法。

我们可以做的第一件事是使用有助于缩短脚本的函数,例如这样:

        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);
    }

您可能会看到构建一个函数和/或类库的价值,您可以在您编写的各种 SDK 脚本中包含和使用它。

但长脚本也可能因为其他原因而遇到性能问题。我学会了三件事来减少内存开销并提高速度:

  • 频繁调用 FlushChanges。这会将你迄今为止所做的更改从本地 NodeJS 环境移动到 Mendix ModelServer 减少您的本地内存占用。
  • 尽可能使用局部作用域变量 — 使用函数可以轻松实现这一点。局部作用域变量在您定义的块中诞生、生存和消亡,从而减少在脚本其余部分中携带的数据积累。脚本中的每个变量引用都需要在 JavaScript 内部进行某种查找,您定义的变量越多,所需的时间就越长(并且消耗的内存越多)。
  • 保持变量名简短,或者至少更短,因为这将缩短执行时间并减少长脚本中的内存开销。显然,简洁和清晰之间存在权衡。我有一个应用程序,它编写 TypeScript 文件来为我修改应用程序,我可以通过轻按开关将其设置为生成有意义的变量名(用于调试)或短变量名(用于生产)。或者,您可以选择在运行生成的 JavaScript 之前使用单独的工具对其进行最小化,但我没有尝试过,所以我不知道它的效果如何。

在下面提到的 Github 存储库中,我包含了两个脚本,一个是本博客中显示的简单脚本(“createapp.ts”),另一个是实现我在此处建议的一些更改的替代脚本(“createapp2.ts”)。每个脚本都会生成相同的应用程序,但第二个脚本明显更短,而且我认为更容易理解。

这篇文章到此结束。脚本可以在 Github 上找到 https://github.com/Adrian-Preston/SDKBlog2.

我将继续开发第三部分的一些功能 — — 即将推出!

选择你的语言