Stored Functions


A stored function is a block🧩 of code that is saved and executed directly on the server as an API. This code allows for deeper manipulation of data, enhances security, interacts with various APIs, and also performs more complex tasks (sending emails, generating reports, verification with external systems). Written in C#, these components can accept arguments and interact with all the functionalities of Activator, further enhancing its dynamic nature and giving it the ability to handle much more complex and global tasks. 

As a simple example of use, we could have a stored function that registers a new client, then sends them a welcome email, and afterward notifies the internal administration (with a certain privilege level) of this new client registration. Additionally, we could have functions that retrieve a certain number of records and return the results only to users with the required privileges. The application possibilities are truly immense.

On Activator, several components are of the stored function type (using the same documentation defined below for their operation) 📣:

 

Create A Stored Function

In Activator Admin, the stored function is located in the menu container as shown in (1) in the image below (this scenario is the same for other storedfunction components once their menu has been localized).

✋NB: Make sure you are in the System Component main module.

 

Stored Function
Stored Function

After initiating the creation of the Language Ressource, by clicking on the +Add new button (illustrated at (2) on the image above), you’ll observe a form (illustrated at (3) on the image above) appearing on the right side with some fields.

🔬After filling in the name and description fields, let's focus on the Definition and Meta Data fields. 

The Meta Data field gathers and provides additional information for the component. Regarding the data present in this field, we will mainly focus on the arguments property further #down. For more information on the other properties contained in this field, please refer to Meta Data for the complete list of additional information related to this component.

As for the Definition field, let's take a closer look at its Json content in the next point.

 

Definition Content

This code represents the definition of a stored function in the Activator environment.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using System.Threading.Tasks;
using Asm.Activator.Core.Apis.StoredFunctions.Core;
using Asm.Activator.Core.Apis.StoredFunctions.Core.Components;
using Asm.Activator.Core.Apis.StoredFunctions.Core.Handlers;

namespace Asm.Activator.StoredFunctions
{
	public class StoredFunction: ActivatorStoredFunctionBase
	{
		public override async Task<StoredFuctionResult> Execute()
		{
			var result = new StoredFuctionResult
			{
				Errors = new List<string>(),
				Payload = string.Empty
			};

			try
			{
				//DO YOUR WORK AND SET THE PAYLOAD
				//	The payload can be any object
				//	result.Payload = "The payload";
			}
			catch(Exception ex)
			{
				//Handle exception
				result.Errors.Add(ex.Message);
			}

			//Return the result
			return result;
		}
	}
}

 

Explanation:

SECTIONDESCRIPTION
Method Execute()This is the method that will be called when the stored function is executed. It is marked as asynchronous with async Task, allowing the method to be also in a non-blocking manner.
Result initialization
var result = new StoredFuctionResult
{
    Errors = new List<string>(),
    Payload = string.Empty
};

A StoredFunctionResult object is created to store the result of the execution. This object has two important properties:

  • Errors: A list of strings that will collect error messages in the event of execution failure.
  • Payload: A string (or an object) that will contain the result (success) of the function's execution. Initially, it is empty.
Block tryContains the business logic of the stored function. This is where the code to be executed should be written (calculations, queries, data manipulations). If everything works, the result to be returned should be stored in result.Payload.
Block catchIf an exception occurs during execution, it is caught, and its message is added to the result.Errors list.

This table summarizes each part of the code and its execution logic to help you create a stored function.

 

✋NB: Upon each registration, a comprehensive check (syntax, logic, and semantics) is conducted. If errors are detected, they are flagged; otherwise, the save is completed.

 

Additionnal Info

The arguments property in the Meta Data section of a stored function defines the parameters that are passed during the function's execution. Each argument is represented by an object containing several attributes, such as:

"arguments": [
    {
        "name": "recordId",
        "required": true,
        "dataType": "Guid"
    }
]

PROPERTYREQUIREDDESCRIPTION
nameYesName of the argument, used to reference it when the stored function is executed.
requiredYesBoolean value indicating whether the argument is mandatory for execution. 
dataTypeYesExpected data type for the argument. Possible types are: guid, string, boolean, and Datatype component name such as activatord.datatypes.name

 

Execute A Stored Function

 

PROPERTYDESCRIPTION
URL

https://v2_modulesapi_dev.asmlogic.com/api/tenants/{tenantId}/storedfunctions/{name}/execute

  • tenantId: The tenant's unique identifier in the system
  • name: The name of the stored function to be executed.
MethodPOST
BODY
{
    "arguments": [
        {
            "name": "string",
            "value": "string"
        }
    ]
}

 

Utilities

Activator already contains predefined functions, shortcuts, and features that help developers save time, particularly when communicating with Activator's APIs, other components, or external systems.

 

General

  • Get Function Argument Value

    var argumentValue = this.Context.Arguments.GetValue("argumentName");
  • Get the Tenant ID

    var tenantId = this.Context.TenantId;
  • Get the User ID

    var userId = this.Context.UserId;

 

Execute API

string url = "url";
string payloadModel = "payload model stringified";
        
string result = await this.Context.ExecuteActivatorApiCall(
    "put", // [get, post, delete]
    "module", // [schema, tenant]
    url,
    payloadModel);

The Context.ExecuteActivatorApiCall of the result function is called📞 to execute an API request to the Activator. This method takes several parameters described in this table👇:

 

PROPERTYDESCRIPTION
HTTP method

The first parameter specifies the HTTP method used for the API call. This can be :

  • put
  • post
  • get
  • delete
API typeThe second parameter indicates the type of API involved in the request. For a better understanding, go to Activator APIs. The value to set can be chosen between
[module, schema, tenant].
URL

The third parameter is the URL of the API to call, defined above in the url variable. 

This url variable will contain the URL of the API you wish to call. It is used to specify where the request should be sent.

payloadModelThe payloadModel variable contains the request body data as a string. This model is "stringified" before being sent to the API (for a POST or PUT request).

 

Serialization

  • Serialization to Json

    const new = {
      firstName: "John",
      lastName: "Adams"
    };
    const jsonEmployee = JSON.stringify(new);
    
    
  • Deserialize from Json

    var myTypeVariable = this.DeserializeObjectFromJson<Type>(jsonString);

 

User And Roles

  • Get All Roles

    var activatorRoles = await this.Context.AccessControl.GetRoles();
  • Get Role Attributes

    var attributes = await this.Context.AccessControl.GetRoleAttributes("activatord.roles.roleName");
  • Get Role Attribute Value

    string attributeValue = await this.Context.AccessControl.GetUserRoleAttributeValue("activatord.roles.roleName",
    "attributName");
  • User Is In Role

    bool check = await this.Context.AccessControl.IsUserInRole("activatord.roles.roleName");
  • Get Users with the attribute value

    var users = await this.Context.AccessControl.GetUsersWithRoleAttributeValue("activatord.roles.roleName",
    "attibuteName",
    "attributeValue");
  • Get Users In A Role

    var users = await this.Context.AccessControl.GetUsersInRole("activatord.roles.roleName");

 

System Setting

  • Get A Setting Value

    var settingValue = await this.Context.Settings.GetValue("activatord.configurationsettings.settingName");

 

Entities

  • Create an Entity Record

    var entityModelJson = "{\"firstName\":\"John\",\"lastName\":\"Doe\",\"middleName\":\"Alain\"}";
    Guid recordGuid = await this.Context.Entities.CreateRecord("activatord.entities.newEntity1",
     entityModelJson);
  • Update an Entity Record

    Guid recordId = Guid.Parse("ebbe2919-54d6-4891-b0ce-578a0f498000");
    string path = "";
    
    var firstNameAttr = new EntityAttributeValue
    {
        Name = "firstName",
        Value = "Christian"
    };
    
    var lastNameAttr = new EntityAttributeValue
    {
        Name = "lastName",
        Value = "Amougou"
    };
    
    bool success = await this.Context.Entities.UpdateRecord(
        "activatord.entities.newEntity1",
        recordId,
        path,
        new List<EntityAttributeValue> { firstNameAttr, lastNameAttr }
    );
    
  • Create A Child Record

    Guid recordId = Guid.Parse("ebbe2919-54d6-4891-b0ce-578a0f498000");
    string route = "/dependents";
    var modelJson = "{\"attribute1\":\"value5555\",\"attribute2\":\"555555\"}";
    
    Guid newRecordId = await this.Context.Entities.InsertChildRecord(
        "activatord.entities.newEntity1",
        recordId,
        route,
        modelJson
    );
    
  • Update Entity Child Record

    Guid recordId = Guid.Parse("ebbe2919-54d6-4891-b0ce-578a0f498000");
    string route = "/dependents/b2ea134b-903e-4bb7-92f6-1ade0c67d698";
    
    var firstNameAttr = new EntityAttributeValue
    {
        Name = "firstName",
        Value = "Christian"
    };
    
    var lastNameAttr = new EntityAttributeValue
    {
        Name = "lastName",
        Value = "Amougou"
    };
    
    bool success = await this.Context.Entities.UpdateChildRecord(
        "activatord.entities.newEntity1",
        recordId,
        route,
        new List<EntityAttributeValue> { firstNameAttr, lastNameAttr }
    );
    
  • Update Entity LanguageResources

    This concerns the translation of an entity's data.

    await this.Context.Entities.UpdateRecordLanguageResources(
        "activatord.entities.entityName",
        recordId,
        path,
        lang_keys);
    

    lang_keys must be of type List<EntityRecordLanguageResource> (which is a native Activator model), for more info on this, go to database data translation.

 

Stored Queries

  • Execution

    With no paramter

    string resultJson = await this.Context.StoredQueries.Execute("activatord.storedqueries.newStoredQuery1");

    With parameters

    var param1 = new QueryParameter
    {
        Name = "param1",
        Value = "Value1"
    };
    
    var param2 = new QueryParameter
    {
        Name = "param2",
        Value = "Value2"
    };
    
    string resultJson = await this.Context.StoredQueries.Execute(
        "activatord.storedqueries.newStoredQuery1",
        new List<QueryParameter> { param1, param2 }
    );
    
    

Dynamic Querie

 

//create a new instance
var queryBuilder = this.Context.CreateDynamicQueryBuilder();
//add filter
//filter with string
queryBuilder.addFilter("_id", recordId);

//filter with boolean
var isActiveObj = new JObject();
isActiveObj["$eq"] = true;
queryBuilder.addFilter("isActive", isActiveObj);

//filter with null element
var nullElment = new JObject();
nullElment["$eq"] = null;
queryBuilder.addFilter("report", nullElment);
// Add a projection
queryBuilder.addProjection("_id", 1);
queryBuilder.addProjection("tranckingNumber", 1);
// Add look up
// Add sort
// Add limit

--------------------------------------------------------------------

// Add look up
queryBuilder.addLookup(
    "sys.entities.school",
    "schoolRecordId",
    "_id",
    "schoolInfo"
);

// Add sort
// You need to master storquries
queryBuilder.addSort("_id", -1);
queryBuilder.setLimit(500);

// Add limit
string result = await this.Context.StoredQueries.ExecuteDynamicQuery(
    "Aggregation", // [selectOne, selectMany] this is the type of request that is detailed in storedQueries
    "activatord.entities.entityName", //The entity where we're going to execute
    queryBuilder.ToString()  //instance
);

//To make a projection

var createdDate = new JObject();
createdDate["$dateToString"] = new JObject
{
    ["format"] = "%d/%m/%Y",
    ["date"] = "$createdDate"
};

queryBuilder.addProjection("createdDate", createdDate);

//Or

queryBuilder.addProjection(
    "beginningDateOfService",
    JObject.Parse(@"{
        ""$dateToString"": { 
            ""format"": ""%m/%d/%Y %H:%M:%S"",
            ""date"": ""$beginningDateOfService""
        }
    }")
);

//This one involves implementing the library: using Newtonsoft.Json.Linq; among those provided natively by Activator

 

Stored Functions

With no argument

var data = await this.Context.StoredFunctions.Execute("activatord.storedfunctions.functionName");

With argument

var arg1 = new FunctionArgument
{
    Name = "arg1",
    Value = "value1"
};

var arg2 = new FunctionArgument
{
    Name = "arg2",
    Value = "value2"
};

var data = await this.Context.StoredFunctions.Execute(
    "activatord.storedfunctions.functionName",
    new List<FunctionArgument> { arg1, arg2 }
);

 

Translation

  • According To Active Language

    string translationInTheCurrentLanguage = await this.Translate(resourceName, titleResKey, defaultTitle);
  • According To The Language Specified As A Parameter

    string translationInFrench = await this.Translate(resourceName, "french", titleResKey, defaultTitle);

 

Send Email Notification

  • By sending content directly

    Send An Email

    var emailMsgModel = new EmailMessageModel
    {
         To = new List<string> 
         { 
              "john.doe@asmafrik.com", 
              "sarah@hotmail.com" 
         },
          DisplayFrom = "Activator Tenant Name",
          Subject = "No Salutations",
          Body = "Bonjour <strong>Mr. John Doe</strong>,<br><br>A plus."
    };
    
    bool success = await this.Context.Notifications.SendEmail(emailMsgModel);
    

    Send An Email With file Attachments

    var fileAttachments = new List<EmailMessageAttachment>();
    string demoBase64Content = "";
    
    fileAttachments.Add(new EmailMessageAttachment
    {
         FileName = "demo.pdf",
         FileBase64Content = demoBase64Content,
         ContentType = "application/pdf"
    });
    
    // valid content types:
    // (.txt) text/plain,
    // (.zip) application/zip
    // (.doc) application/msword
    // (.docx) application/vnd.openxmlformatsofficedocument.wordprocessingml.document
    // (.xls) application/vnd.ms-excel
    // (.xlsx) application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
    
    var emailMsgModel = new EmailMessageModel
    {
         To = new List<string> { "john.doe@asmafrik.com", "sarah@hotmail.com" },
         DisplayFrom = "Activatord e-dev",
         Subject = "No Salutations",
         Body = "Bonjour <strong>Mr. John Doe</strong>,<br><br>A plus.",
         FileAttachments = fileAttachments
    };
    
    bool success = await this.Context.Notifications.SendEmail(emailMsgModel);
    
  • Using a report as a mail Template

    Send An Email

    var to = new List<EmailAddressModel>();
    var reportParameters = new List<EmailTemplateReportParameters>();
    var fileAttachments = new List<EmailMessageAttachment>();
    
    to.Add(new EmailAddressModel
    {
        Display = "John Doe",
        EmailAddress = "john.doe@example.com"
    });
    
    reportParameters.Add(new EmailTemplateReportParameters
    {
        ReportName = "activatord.reports.reportName",
        Parameters = new List<ReportParameter>
        {
            new ReportParameter
            {
                Name = "Param Name",
                Value = "Param Value"
            }
        }
    });
    
    bool result = await context.Notifications.SendEmailFromTemplate(
        "activatord.emailtemplates.emailTemplateName",
        to,
        null,
        null,
        reportParameters,
        fileAttachments
    );
    

    Send An Email With file Attachments

    fileAttachments.Add(
        new EmailMessageAttachment
        {
            FileName = "FileName.pdf",
            FileBase64Content = base64EncodedContent
        }
    );
    

 

Reports

  • Execute with no parameters

    var base64Content = await this.Context.Reports.Execute("activatord.reports.reportName", "pdf");
  • Execute with parameters

    var param1 = new ReportParameter
    {
        Name = "param1",
        Value = "Value1"
    };
    
    var param2 = new ReportParameter
    {
        Name = "param2",
        Value = "Value2"
    };
    
    var base64Content = await this.Context.Reports.Execute(
        "activatord.reports.reportName",
        "pdf",
        new List<ReportParameter> { param1, param2 }
    );
    

 

Zip

Modules to be imported after the default ones:

using Newtonsoft.Json.Converters;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml;
using System.IO;
using Newtonsoft.Json.Linq;

The code to implement:

string zipFileBase64Content = this.CreateZipFile(
    new List<ZipFileEntry>
    {
        new ZipFileEntry
        {
            FileName = "file1.pdf",
            Base64Content = file1Base64FileContent
        },
        new ZipFileEntry
        {
            FileName = "file2.pdf",
            Base64Content = file2Base64FileContent
        },
        new ZipFileEntry
        {
            FileName = "file3.pdf",
            Base64Content = file3Base64FileContent
        }
    }
);

 

Conclusion

Stored functions provide a robust approach for executing complex tasks directly at the server side, ensuring better integration and enhanced performance. By centralizing critical operations and enabling rapid process execution, they simplify data management while minimizing the need for client-side processing. Their ability to handle various types of arguments and integrate seamlessly into API workflows demonstrates their value in designing efficient and scalable systems. In summary, stored functions are a valuable tool for optimizing data processing operations and enhancing application consistency.