Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

10 Commits

Folders and files

Repository files navigation

a1 DevKit

Open-source library of backend patterns and helpers for Creatio development, shipped as a Creatio configuration package (source code schemas compiled inside the configuration, not an external-only DLL).

It removes boilerplate that gets re-implemented on almost every Creatio project: ESQ / data access, outbound & inbound integrations, entity eventing, platform access (settings, localization, DI), and cross-cutting concerns such as logging, retries and error handling.

Requirements

  • Creatio 8.2+
  • Runs on a consumer instance under .NET Framework 4.8 or .NET 8 — the public API surface stays within the .NET Standard 2.0 API set so it works on both.

Installation

a1DevKit is a Creatio configuration package. Install it into a Creatio 8.2+ environment in one of two ways.

Option A — clio (CLI)

clio is the community command-line tool for Creatio.

# 1. Install clio (one-time). Requires the .NET SDK.
dotnet tool install clio -g

# 2. Register your Creatio environment (one-time).
clio reg-web-app my-creatio -u https://my-instance.creatio.com -l Supervisor -p ********

# 3. Install the package into that environment.
clio push-pkg a1DevKit.zip -e my-creatio

Replace the URL, login and password with your instance's values. a1DevKit.zip is the package archive from the latest release — or build one from the source package with clio generate-pkg-zip packages/a1DevKit.

If you cloned this repository as a clio workspace, you can push it directly instead of packaging a zip:

clio push-workspace -e my-creatio

Option B — Application Hub (no CLI)

  1. Download the a1DevKit .zip from the latest release.
  2. In Creatio, open the Application Hub.
  3. Install an application from file and upload the .zip.
  4. Confirm the installation and wait for the configuration to compile.

The Application Hub route expects the release to be packaged as a composable application (with app-descriptor.json). A bare configuration package can instead be installed from System Designer → Installed applications → Install from file.

Design principles

  • Built on the platform, not around it. Each helper is a Creatio source code schema that wraps the platform's own APIs (EntitySchemaQuery, UserConnection, Common.Logging, Terrasoft.File.Abstractions, Polly) directly — no abstraction layer and no external framework to learn. A helper takes a UserConnection and is used exactly where you would have written the raw platform call, so it stays idiomatic Creatio code.
  • ESQ-first, cloud-safe. The default data access path is EntitySchemaQuery; low-level Select/DBExecutor access — which can be blocked on hardened/cloud instances — is isolated in a clearly labeled, "on-prem only" module.
  • One helper = one source code schema. Each helper lives in its own schema under packages/a1DevKit/Schemas, is documented with English XML-doc, and ships with a usage example.

Modules (v1 scope)

Area Helper(s)
Logging a1CustomLogger — drop-in ILog whose verbosity is switched at runtime by a system setting
Data access a1ESQExtention — typed per-schema CRUD: get by Id/filter, batched ForEach over large tables, event-bypassing column updates
Serialization a1EntitySerializer — Entity → JSON with lookup (Id + display value) and detail handling
Integration — outbound a1IntegrationDTO + a1RESTClient (REST) and a1SOAPClient (SOAP): Polly per-attempt timeout/retry, plus request/response logging to a1IntegrationLog
File parsing a1CSVHelper, a1ExcelHelper — CSV/Excel → JSON with a shared set of inputs (stream, Creatio file record, or IFile)
Case management a1DCMHelper (+ two business-process user tasks) — start/cancel DCM cases for a record

Planned for future versions (not yet included): inbound configuration-service base class; typed system settings and feature-flag accessors; localization helpers; EntityEventListener / ScriptTask bases; and standalone Result<T>, guard-clause and retry-policy primitives.

Out of scope: Freedom UI / frontend components, data migrations, no-code artifacts.


a1CustomLogger

Schema: packages/a1DevKit/Schemas/a1CustomLogger/a1CustomLogger.cs

A drop-in replacement for a logger obtained via Common.Logging.LogManager.GetLogger(...) that is gated by a single system setting instead of by comments in the code.

Why

  • Forgetting to reference Common.Logging is a common source of compile errors when adding logging to a Creatio class — a1CustomLogger is defined once, in one schema, with the reference already resolved.
  • Debug/Trace instrumentation added during development normally has to be commented out afterwards so it doesn't flood the log. With a1CustomLogger it can stay in the code permanently: verbosity is controlled by a system setting, so it can be turned on in production for a few minutes to diagnose an issue and turned back off — no redeploy, no restart.

How it works

Two types make up this helper:

  • a1LogLevelEnum — a static class holding the fixed Guid identifiers for each verbosity level (NoneId, FatalId, ErrorId, WarnId, InfoId, DebugId, TraceId), ordered from least to most verbose, plus the pure comparison logic IsEnabled(configuredLevelId, messageLevelId) that decides whether a message at a given level should be written.
  • a1CustomLogger — implements the full Common.Logging.ILog interface. Every Trace/Debug/Info/Warn/Error/Fatal call (and all of their overloads) is gated by the corresponding Is*Enabled property, which reads the current threshold from the a1LogLevel system setting on every call — so a change to the setting takes effect immediately. Calls to GlobalVariablesContext / ThreadVariablesContext / NestedThreadVariablesContext pass straight through, since they are not log writes.

Because a1CustomLogger implements ILog, it can be used anywhere a regular Common.Logging.ILog is expected.

Setup

The a1LogLevel system setting is expected to be a Lookup pointing to a Value List whose items carry exactly the Guid values defined in a1LogLevelEnum. Create that Value List and the system setting once per environment; a1LogLevelEnum's Guid constants must match the Value List item identifiers. An unset or unrecognized setting value is treated as NoneId (logging disabled) — a misconfigured setting fails closed instead of flooding the log.

Usage

using Terrasoft.Configuration;

public class MyService {

    private readonly ILog _log;

    public MyService(UserConnection userConnection) {
        _log = new a1CustomLogger(userConnection, "MyServiceLogger");
    }

    public void DoWork(string input) {
        _log.InfoFormat("[DoWork] Started. input={0}", input);
        _log.DebugFormat("[DoWork] Intermediate step completed. value={0}", input);
    }
}

Raising the a1LogLevel system setting to Debug or Trace reveals the detailed calls above; leaving it at Info or Warn suppresses them without touching this code.


a1DCMHelper

Schema: packages/a1DevKit/Schemas/a1DCMHelper/a1DCMHelper.cs

Starts and cancels DCM (Dynamic Case Management) cases for a specific record — the two operations every "restart the case flow for this record" scenario needs, instead of re-implementing them per business process user task.

Why

  • Starting a DCM case is not a plain Process.Run call — the platform activates it by sending the case's start signal for the target record. Getting this right (finding the right enabled DcmSchema, building the signal options) is easy to get subtly wrong at each call site.
  • There is no public bulk API to cancel every running DCM process instance for a record. Doing it correctly means querying the system process tables (SysEntityCommonPrcEl / SysProcessElementData / SysProcessData / SysSchema) with a parameterized query — a1DCMHelper centralizes that once instead of leaving raw SQL copy-pasted across user tasks.

How it works

a1DCMHelper wraps a UserConnection and exposes two operations:

  • RunDcmCase(recordId, entitySchemaName, dcmSchemaName = null) — resolves the entity schema by name, finds the matching enabled DcmSchema (the first enabled one when dcmSchemaName is omitted, or the one with that name), and starts it via UserConnection.ProcessEngine.RunDcmProcess. Throws InvalidOperationException when the entity schema or a matching enabled case cannot be found.
  • CancelDcmCases(recordId) — looks up every currently running DCM process instance linked to recordId, regardless of which case schema started it or whether that schema is still enabled, and calls CancelExecution() on each. This is the low-level/on-prem-oriented path in the helper: it reads system process tables directly because no ESQ-friendly public API exists for a per-record bulk cancel.

Usage

using Terrasoft.Configuration;

var dcmHelper = new a1DCMHelper(userConnection);

dcmHelper.RunDcmCase(orderId, "Order");                  // first enabled case for Order
dcmHelper.RunDcmCase(orderId, "Order", "OrderDcmCase");  // a specific case by name

dcmHelper.CancelDcmCases(orderId);                       // cancel every running case for the record

DCM user tasks

Two ready-to-use business process user tasks build on a1DCMHelper so a process designer can start or cancel a DCM case for the current record without writing any code:

  • a1RunDCMForRecord_UserTask (packages/a1DevKit/Schemas/a1RunDCMForRecord_UserTask/a1RunDCMForRecord_UserTask.cs) — reads the RecordId, EntitySchemaName and DcmSchemaName process parameters and calls a1DCMHelper.RunDcmCase.
  • a1CancelDCMForRecord_UserTask (packages/a1DevKit/Schemas/a1CancelDCMForRecord_UserTask/a1CancelDCMForRecord_UserTask.cs) — reads the RecordId process parameter and calls a1DCMHelper.CancelDcmCases.

Both user tasks are thin wrappers — all the actual logic lives in a1DCMHelper, so it stays covered by a single implementation instead of being duplicated per user task.


a1IntegrationDTO and a1RESTClient

Schemas: packages/a1DevKit/Schemas/a1IntegrationDTO/a1IntegrationDTO.cs, packages/a1DevKit/Schemas/a1RESTClient/a1RESTClient.cs

Together these carry a single outbound HTTP call end to end and persist it to the "Integration log (a1)" (a1IntegrationLog) entity.

a1IntegrationDTO

Property names mirror the a1IntegrationLog entity's columns one-to-one:

Property Column Set by
Service a1Service constructor
Endpoint a1Endpoint CreateRequest
Request a1Request CreateRequest
Response a1Response the HTTP client, after the call
StatusCode a1StatusCode the HTTP client, after the call
Success a1Success the HTTP client, after the call
RetryCount a1RetryCount caller, before the call (default 0 — no retries)
Timeout a1Timeout caller, before the call (default 100 seconds)

LogRequest() persists all of the above as a new a1IntegrationLog record. Logging failures are caught and written to the application log instead of being thrown, so a broken audit write never fails the integration call it is logging.

a1RESTClient

Executes the call described by an a1IntegrationDTO — Get/Post/Put/ Patch/Delete, each taking the DTO and an optional logRequest flag (default false) — and returns the same DTO with Response/StatusCode/ Success filled in.

Every call is guarded by a Polly resilience policy (Creatio already ships Polly.dll, so no new third-party dependency is introduced) built from the DTO's own Timeout/RetryCount:

  • Timeout is applied per attempt — each retry gets its own fresh timeout window rather than sharing one budget across all attempts.
  • Retry triggers on transport-level failures (HttpRequestException) and on a timed-out attempt, with exponential backoff (2, 4, 8... seconds) between attempts. A response that simply carries a non-success HTTP status code (e.g. 500) is not retried — it is written to the DTO like any other response, since that is a normal outcome, not a transport failure.

Network failures that survive every retry are caught rather than thrown, so a caller can always check dto.Success instead of wrapping every call in try/catch; in that case dto.StatusCode is 0 and dto.Response holds the exception message.

Usage

using Terrasoft.Configuration;

var dto = new a1IntegrationDTO(userConnection, "https://api.example.com");
dto.CreateRequest("/v1/orders", requestJson);
dto.RetryCount = 3;   // up to 3 extra attempts on transport failure/timeout
dto.Timeout = 15;     // 15 seconds per attempt

var restClient = new a1RESTClient();
restClient.Post(dto, logRequest: true);

if (dto.Success) {
    // use dto.Response
}

a1SOAPClient

Schema: packages/a1DevKit/Schemas/a1SOAPClient/a1SOAPClient.cs

The SOAP counterpart to a1RESTClient — same a1IntegrationDTO in/out contract, same Polly per-attempt-timeout/exponential-backoff-retry policy, same "network failures are caught, not thrown" behavior. It differs only where SOAP itself differs from a typical REST call:

  • Always sends a POST with content type text/xml — dto.Request must already be the complete SOAP envelope (this helper does not build XML for you).
  • Takes an optional soapAction parameter and sends it as the SOAP 1.1 SOAPAction header, auto-quoting the value if it isn't already quoted (the spec requires the header value to be a quoted string). Omit it for services that don't require one (SOAP 1.2 typically encodes the action in the content type instead).
  • A SOAP fault is just a response with a non-success status code — written to the DTO like any other response, not retried, exactly like a REST 500.
using Terrasoft.Configuration;

var dto = new a1IntegrationDTO(userConnection, "https://api.example.com");
dto.CreateRequest("/soap/OrderService", soapEnvelopeXml);

var soapClient = new a1SOAPClient();
soapClient.Send(dto, soapAction: "http://example.com/GetOrder", logRequest: true);

if (dto.Success) {
    // parse dto.Response as XML
}

Known duplication: a1RESTClient and a1SOAPClient each carry their own copy of the Polly policy-building and URI-building helpers rather than sharing a base class. This was a deliberate call to keep each schema fully self-contained (see "One helper = one source code schema"); if a third HTTP transport is added later, that logic is worth extracting into a shared internal type instead of copying it a third time.


a1CSVHelper and a1ExcelHelper

Schemas: packages/a1DevKit/Schemas/a1CSVHelper/a1CSVHelper.cs, packages/a1DevKit/Schemas/a1ExcelHelper/a1ExcelHelper.cs

Both convert tabular file content into a JSON array: the first row becomes the property names, every following row becomes one JSON object, all values read as plain strings (no type inference). Both expose the same three input overloads, so a caller doesn't need to care where the file is coming from:

Overload Use when
Parse(Stream stream) you already have the file open (e.g. a web service upload)
Parse(string tableName, Guid fileId) the file is stored in a Creatio file record — resolved via EntityFileLocator, so it works whether the file physically lives in the DB, S3, or the filesystem
Parse(IFile file) you already resolved the Terrasoft.File.Abstractions.IFile yourself

a1CSVHelper parses CSV by hand (an RFC 4180 tokenizer — handles quoted fields with embedded commas/newlines and escaped "" quotes), no third-party dependency needed.

a1ExcelHelper reads .xls/.xlsx via ExcelDataReader (a new third-party dependency this package bundles as a NuGet PackageReference in packages/a1DevKit/Files/a1DevKit.csproj — restored automatically at build time, alongside System.Text.Encoding.CodePages which it needs to read legacy code-page-encoded .xls files; both target netstandard2.0, so they run on both .NET Framework 4.8 and .NET 8 per the runtime-portability rule) and takes an additional zero-based sheetIndex parameter (default 0, the first sheet). Once the raw rows are read, it converts them to JSON with the exact same row-to-JSON logic as a1CSVHelper (a1CSVHelper.RowsToJArray, shared rather than duplicated, since the two are explicitly meant to behave identically beyond the file format itself).

using Terrasoft.Configuration;

var csvHelper = new a1CSVHelper(userConnection);
var rowsFromUpload = csvHelper.Parse(uploadedStream);
var rowsFromFileRecord = csvHelper.Parse("LeadFile", fileId);

var excelHelper = new a1ExcelHelper(userConnection);
var sheetRows = excelHelper.Parse("LeadFile", fileId, sheetIndex: 0);

a1EntitySerializer

Schema: packages/a1DevKit/Schemas/a1EntitySerializer/a1EntitySerializer.cs

Serializes a Creatio Entity to JSON (JObject) for integration payloads, audit logs, or API responses — without manually enumerating every column.

Why

  • Hand-writing entity.GetColumnValue(...) for every column of every schema you want to expose as JSON is repetitive, and breaks silently whenever a column is added or renamed.
  • Lookup columns need special handling to get both the Id and a human-readable display value, which is easy to get backwards (see below).

How it works

  • Serialize(schemaName, recordId) loads the record via ESQ (AddAllSchemaColumns) and returns one JObject with one property per column. Regular columns serialize to their raw value; lookup columns serialize as { "Id": "...", "DisplayValue": "..." } — the Id comes from entity.GetColumnValue(column.Name), the display text from entity.GetColumnValue(column.DisplayColumnValueName).
  • Ignore(params string[] columnNames) adds columns to a permanent skip list, on top of the built-in defaults (ProcessListeners, SysModifiedInSchemaUId, SysCreatedInSchemaUId) — use it for large blobs or sensitive columns you never want in the output.
  • Serialize(schemaName, recordId, columnNames) — whitelist overload, serializes only the given columns instead of the whole schema.
  • SerializeWithDetails(schemaName, recordId, detailColumns) — serializes the record, then adds one JSON array per related detail schema. detailColumns maps detail schema name → the detail's FK column that points back at the parent record.

Usage

using Terrasoft.Configuration;

var serializer = new a1EntitySerializer(userConnection);
serializer.Ignore("Photo", "Notes"); // skip large/sensitive columns

JObject order = serializer.Serialize("Order", orderId);

JObject orderWithLines = serializer.SerializeWithDetails("Order", orderId,
    new Dictionary<string, string> { ["OrderLine"] = "OrderId" });

a1ESQExtention

Schema: packages/a1DevKit/Schemas/a1ESQExtention/a1ESQExtention.cs

Typed helper for a single entity schema — construct once with a schema name and reuse across multiple operations instead of repeating ESQ setup, null-checks and try/catch at every call site.

Why

Most CRUD call sites in a Creatio backend repeat the same handful of steps (build an ESQ or fetch an entity, check for null, wrap Save() in try/catch...). a1ESQExtention wraps those once per schema.

How it works

Methods split into two groups:

  • Entity-based (fires OnSaving/OnSaved etc. event handlers): GetById/TryGetById (throwing vs. safe fetch by Id), TryGetByColumn, GetWithColumns (fetch only specific columns), GetColumnValue<T> (read a single column without loading the full entity), Create, Save, DeleteById.
  • SQL-based (bypasses event handlers, faster): UpdateColumn / UpdateColumns run direct Update statements — choose these only when you deliberately want to skip entity events.
  • Collection: GetByFilter (all records matching one column filter) and ForEach (iterates all records — or a pre-configured EntitySchemaQuery — in batches of batchSize rows, default 5000, so a large table is never loaded into memory at once).

GetById, Create and GetColumnValue<T> throw a1ESQExtentionException when the record isn't found or can't be saved; use the Try... variants instead when a missing record is an expected outcome rather than an error.

Usage

using Terrasoft.Configuration;

var orders = new a1ESQExtention(userConnection, "Order");

var order = orders.TryGetById(orderId);
if (order == null) {
    var newOrderId = orders.Create(new Dictionary<string, object> {
        ["Name"] = "New order",
        ["AccountId"] = accountId
    });
}

orders.UpdateColumn(orderId, "StatusId", newStatusId); // bypasses OnSaving/OnSaved

orders.ForEach(entity => {
    // process every Order record in batches of 5000
});

Tests

Automated tests for every helper live in tests/.

License

Released under the MIT License — see LICENSE.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages