Skip to content

C# Scripts

C# scripts run via Roslyn — Microsoft's in-process C# compiler. Unlike the other languages which spawn a subprocess, C# scripts are compiled and executed directly within the ApiMeld process, giving them full access to the .NET runtime, async/await, LINQ, and strong typing with no shell overhead.

Key differences from other languages

C#PowerShell / JS / Python / Bash
RuntimeRoslyn (in-process)Subprocess (pwsh / node / python3 / bash)
TypingStrong, staticDynamic / loosely typed
AsyncFull async/awaitVaries
Entry pointTop-level statementsTop-level statements

Injected variables

The following variables are available at the top level of every C# script — no imports or declarations needed:

VariableTypeDescription
LoggerCSharpScriptLoggerStructured log output
DatasourcesdynamicNamed data source connections
AuthHeadersDictionary<string, string>Pre-built auth headers (if configured)
NotificationsdynamicSend Slack, Teams, or email notifications
Payloadstring?Webhook request body (webhook-triggered runs)
MqttTopicstring?MQTT topic that triggered the run
MqttPayloadstring?MQTT message payload

Logging

csharp
Logger.Info("Starting sync");
Logger.Warn("Rate limit approaching — slowing down");
Logger.Error("Connection failed: " + ex.Message);
Logger.Debug("Response body: " + json);

Log entries appear colour-coded in the Run History viewer. Logger.Error() and Logger.Warn() cause the run to finish with a CompletedWithWarnings status if no exception is thrown.

Basic example

csharp
using System.Net.Http;
using System.Text.Json;

Logger.Info("Fetching data");

var client = new HttpClient();
var response = await client.GetStringAsync("https://api.example.com/data");
var doc = JsonDocument.Parse(response);

var count = doc.RootElement.GetProperty("items").GetArrayLength();
Logger.Info($"Received {count} items");

Querying a database

Data source connections are accessed via the Datasources dynamic object. The name matches the data source name configured in ApiMeld.

csharp
// Query returns IEnumerable<dynamic>
var rows = await Datasources.ProductionDB.QueryAsync(
    "SELECT id, name, email FROM users WHERE active = @active",
    new { active = true }
);

foreach (var row in rows)
{
    Logger.Info($"User: {row.name} <{row.email}>");
}

Parameterised queries use named @param placeholders regardless of the underlying database type — ApiMeld maps them appropriately.

Calling a REST API data source

csharp
// Call an endpoint defined in the data source's OpenAPI spec
var forecast = await Datasources.WeatherApi.GET_V1_FORECAST(new
{
    latitude = "51.5",
    longitude = "-0.1"
});

Logger.Info($"Temperature: {forecast.current.temperature_2m}");

Or call any endpoint directly:

csharp
var result = await Datasources.WeatherApi.GetAsync("/v1/forecast?latitude=51.5&longitude=-0.1");

Reading from a network share or SFTP

csharp
// Read a CSV from a network share
var rows = await Datasources.ReportsShare.ReadCsvAsync("Incoming/sales.csv");
Logger.Info($"Rows: {rows.Count()}");

// Upload a result file to SFTP
await Datasources.VendorSftp.UploadAsync("Outgoing/summary.txt", $"Total: {rows.Count()}");

Using auth headers

If the task has an auth configuration, the pre-built headers are available as AuthHeaders:

csharp
var client = new HttpClient();
foreach (var h in AuthHeaders)
    client.DefaultRequestHeaders.Add(h.Key, h.Value);

var response = await client.GetStringAsync("https://api.example.com/secure");

Sending notifications

csharp
await Notifications.Slack("deployments").SendAsync("Deploy completed successfully");
await Notifications.Teams("ops-alerts").SendAsync("Disk usage exceeded 90%");
await Notifications.Email("admin@example.com", "Alert", "Disk usage exceeded 90%");

Webhook and MQTT context

csharp
// Webhook-triggered run
if (Payload != null)
{
    var data = JsonDocument.Parse(Payload);
    var version = data.RootElement.GetProperty("version").GetString();
    Logger.Info($"Deploying version: {version}");
}

// MQTT-triggered run
if (MqttPayload != null)
{
    Logger.Info($"Message on {MqttTopic}: {MqttPayload}");
}

Sandbox restrictions

The Roslyn executor validates scripts with an AST (syntax tree) analyser before running. The following are blocked regardless of sandbox mode:

  • System.Reflection — prevents runtime type inspection and privilege escalation
  • System.Diagnostics.Process — prevents spawning child processes
  • System.IO file operations — use data source file methods instead
  • unsafe code blocks
  • P/Invoke and native interop

Safe APIs are available by default: LINQ, System.Text.Json, System.Net.Http.HttpClient, regex, string manipulation, and math.

Unrestricted mode

Admins can enable unrestricted mode per role (Admin → Settings → Access Restrictions), which bypasses the AST validator. OS-level isolation via the script-runner user remains active regardless.

Supported using namespaces

Common namespaces are pre-imported — you do not need to add using statements for:

  • System
  • System.Collections.Generic
  • System.Linq
  • System.Text
  • System.Text.Json
  • System.Net.Http

Add explicit using statements for anything else you need.

ApiMeld Task Scheduler