---
name: dataminer-qaction
description: 'QAction (C#) development for DataMiner connectors: writing QActions, SLProtocol API, table fill methods (FillArray, FillArrayNoDelete, FillArrayWithColumn, SetRow, AddRow, DeleteRow), Clear/Leave sentinels, exception handling, multi-threading, performance optimization, logging, and code organization patterns. Use when writing or modifying C# QAction code.'
argument-hint: 'Describe the QAction task: e.g. "parse JSON response into table", "write QAction for SNMP trap processing"'
license: LicenseRef-Skyline-Agent-Marketplace
metadata:
updated: 2026-09-27
version: 2.2
---
## Changelog
| Version | Date | Changes |
|---------|------|---------|
| 2.2 | 2026-09-27 | Aligned packaged resource references and execution contracts. |
| 2.1 | 2026-09-14 | Distinguished built-in SLProtocol APIs, Protocol.Extension methods, and generated typed helpers; made helper use conditional. |
| 2.0 | 2026-09-11 | Replaced direct Newtonsoft deserialization examples with the SecureCoding runtime API, added secret-safe logging/certificate rules, and corrected CS9057 handling so analyzers remain active. |
| 1.9 | 2026-09-11 | Corrected moved OpenConfig and Ember+ documentation URLs in the advanced patterns reference. |
| 1.8 | 2026-08-12 | Added "When to load this skill" trigger section. Added Known Toolchain Warning: Sdk.targets PowerShell API fallback. |
| 1.7 | 2026-07-02 | Added "Defensive Programming — Where It Helps vs. Where It's Noise" subsection: guard external/untrusted data, but don't add redundant null/length checks around SLProtocol methods with a guaranteed contract (e.g. `GetColumns`, `GetKeys`, `GetParameters`). |
| 1.6 | 2026-06-24 | Expanded "Do NOT hardcode parameter IDs" hard rule to explicitly cover response body and status code params. Added two new hard rules: no Hungarian notation variable names (SA1305) and no trailing blank lines at end of `.cs` files (SA1518). |
| 1.5 | 2026-05-08 | Added Hard Rules (anti-hallucination) section. Added `example-json-table.md` canonical example reference. |
| 1.4 | 2026-04-17 | Added canonical QAction .csproj template with explicit NEVER `enable` rule (CS8630 error on net48/C# 7.3). |
| 1.3 | 2026-04-16 | Added CS9057 known toolchain warning section with Directory.Build.props fix. |
| 1.2 | 2026-03-28 | Initial release. |
# DataMiner QAction (C#) Development
Covers writing and modifying QActions — the C# code blocks in DataMiner connectors. Load `dataminer-connector-core` alongside for naming conventions and code style.
> **Paired agent**: `dataminer-qaction-writer` — owns workflow, NuGet steps, and the conditional helper decision. This skill owns SLProtocol API reference, code patterns, and examples. Keep shared concepts (table fill methods, logging patterns) in sync.
## When to Load This Skill
Load this skill (and invoke `dataminer-qaction-writer`) whenever **any** of the following conditions apply:
- You are writing or modifying a `QAction_*.cs` file or the `` section of `protocol.xml`.
- The build output contains **any warning or error** lines from `Sdk.targets`, a `QAction_*.csproj`, or `QAction_Helper.csproj` — regardless of project type (connector, TestPackage, Automation solution).
- A build check reports WARN or FAIL findings mentioning `.csproj` files in a connector or related workspace.
- You are reviewing, generating, or regenerating `QAction_Helper.cs`.
- You are adding NuGet packages to a QAction project.
> **Do not skip this skill because the build warning originates in a `.TestPackage` or `.sln`-level project.** SDK-level warnings (e.g., from `Sdk.targets`) still indicate a toolchain configuration issue that must be reviewed against the Known Toolchain Warnings below.
## Reference Files
For deep-dive details on specific topics, load these references when needed:
| Topic | Reference File |
|-------|---------------|
| JSON parsing, HTTP response handling, DateTime conversion | `dataminer-qaction/references/parsing-patterns.md` |
| Inter-element communication, context menus, multithreaded timers, InterApp, DSI | `dataminer-qaction/references/advanced-patterns.md` |
| Rate calculations (Custom/SNMP, standalone/table) | `dataminer-qaction/references/rate-calculations.md` + `dataminer-nugets` package references |
| NuGet package APIs (`Dev.Protocol`, InterApp, Protocol.Extension, Rates) | `dataminer-nugets` |
| **Canonical QAction examples: JSON→table, bulk get/set, FillArrayWithColumn, precompile** | `dataminer-qaction/references/example-json-table.md` |
| QAction C# validator rules (44 checks, 118 error messages) for reviewing/writing QActions | `dataminer-qaction/references/dataminer-qaction-rules.md` |
| Raw NotifyProtocol / NotifyDataMinerQueued calls (NT types 127, 128, 193, 194, 220, 221, 321) | `dataminer-qaction/references/raw-notify-calls.md` |
---
## Hard Rules (Anti-Hallucination)
Read the [QAction guard rails](references/dataminer-qaction.instructions.md) when
working on QAction code. The publisher includes this authoritative source as a skill reference
and, for Copilot, a native rule. Automatic rule application depends on the client/version;
always follow this skill even when no instruction is automatically attached.
> **Do NOT invent or misclassify SLProtocol methods.** `FillArray`, `FillArrayNoDelete`, `FillArrayWithColumn`, `SetRow`, `AddRow`, `DeleteRow`, and `CheckTrigger` are built-in `SLProtocol` members. `GetColumns` and `SetColumns` require `Skyline.DataMiner.Utils.Protocol.Extension`. Generated `SLProtocolExt` is only for connector-specific typed properties/table wrappers.
> **Do NOT place `public class QAction` inside a namespace.** Silently never fires.
> **Do NOT use `e.Message` in catch blocks.** Always `e.ToString()`.
> **Do NOT call protocol methods inside loops.** Use bulk `GetParameters`/`SetParameters`.
> **Do NOT scatter parameter IDs as magic numbers.** Use `Parameter.xxx` constants when the project consumes a current generated helper. In an intentionally helper-free project, declare descriptive local constants and use the base `SLProtocol` API.
> **Do NOT use Hungarian notation** for variable names. Use descriptive full names: ❌ `swVersion`, `strName`, `intCount` → ✅ `softwareVersion`, `name`, `count`.
> **Do NOT leave blank lines at the end of a `.cs` file.** The last line must be the closing `}` of the class — no trailing newlines after it (SA1518).
> **Do NOT deserialize untrusted JSON through `JsonConvert.DeserializeObject`.** Add `Skyline.DataMiner.Utils.SecureCoding` and use `SecureNewtonsoftDeserialization.DeserializeObject`; keep the analyzer package enabled.
> **Do NOT log secrets or bypass TLS validation.** Redact credentials and payloads that may contain them. A certificate callback must not always return `true`.
> **Always load `example-json-table.md`** when writing a table-filling or parsing QAction.
---
## Utility Package Rule
**Before writing custom logic**, load `dataminer-nugets` and check `dataminer-connector-core/references/nuget-packages.md` for an existing Skyline utility package. Using official packages is **mandatory** when available. Key scenarios:
- **SNMP rate/bitrate/throughput calculations** → `Skyline.DataMiner.Utils.Rates.Protocol` (NEVER write manual delta/rate math)
- **Custom/non-SNMP rate calculations** → `Skyline.DataMiner.Utils.Rates.Common`
- **Interface utilization** → `Skyline.DataMiner.Utils.Interfaces`
- **SNMP counter delta tracking** → `Skyline.DataMiner.Utils.SNMP`
- **SNMP trap parsing** → `Skyline.DataMiner.Utils.SNMP.Traps.Protocol`
- **Table context menus** → `Skyline.DataMiner.Utils.Table.ContextMenu`
- **Safe type conversions** → `Skyline.DataMiner.Utils.SafeConverters`
- **Untrusted JSON deserialization** → `Skyline.DataMiner.Utils.SecureCoding`
---
## QAction Structure
```xml
```
> **CRITICAL — No Namespace on the Entry Point Class**
>
> `public static class QAction` with the `Run` method **must never be placed inside a namespace**.
> DataMiner's scripting engine searches for this class at the **global scope**. Wrapping it in a namespace causes the QAction to **silently never fire** — no error, no log entry.
>
> ❌ Wrong (QAction never fires):
> ```csharp
> namespace MyConnector
> {
> public class QAction // BROKEN — not at global scope
> {
> public static void Run(SLProtocol protocol) { ... }
> }
> }
> ```
>
> ✅ Correct:
> ```csharp
> using System;
> using Skyline.DataMiner.Scripting;
>
> public class QAction // global scope — no namespace wrapper
> {
> public static void Run(SLProtocol protocol) { ... }
> }
> ```
### QAction Key Attributes
| Attribute | Description |
|-----------|-------------|
| `id` | Unique QAction ID |
| `name` | Display name — must be **meaningful and contain a verb** |
| `encoding` | Always `csharp` for modern connectors |
| `triggers` | Comma-separated parameter IDs that trigger this QAction |
| `inputParameters` | Parameters passed as input (comma-separated IDs) |
| `dllImport` | External DLLs to import |
### Stub / AfterStartup QAction
When a QAction has no implementation yet (e.g. an AfterStartup QAction reserved for future initialization), use `// TODO: Implement.` as a placeholder inside the `try` body. **NEVER leave blank lines inside braces** — this triggers SA1505/SA1508 build warnings.
```csharp
using System;
using Skyline.DataMiner.Scripting;
///
/// DataMiner QAction Class: After Startup.
///
public static class QAction
{
///
/// The QAction entry point.
///
/// Link with SLProtocol process.
public static void Run(SLProtocol protocol)
{
try
{
// TODO: Implement.
}
catch (Exception ex)
{
protocol.Log($"QA{protocol.QActionID}|{protocol.GetTriggerParameter()}|Run|Exception thrown:{Environment.NewLine}{ex}", LogType.Error, LogLevel.NoLogging);
}
}
}
```
### Known Toolchain Warning: CS9057
`warning CS9057: The analyzer assembly references version 'X' of the compiler, which is newer than the currently running version 'Y'` means that analyzer cannot load reliably under the active compiler. Hiding the warning can leave the build without its SLC/StyleCop/SXA checks.
**Do not add `CS9057` to `NoWarn`.** Use a compatible .NET SDK/Roslyn compiler, or select an analyzer version compatible with the task's approved toolchain and target DataMiner dependency set. Rebuild and confirm `CS9057` is absent before treating the analyzer gate as successful.
### Known Toolchain Warning: Sdk.targets PowerShell API Fallback
`warning : Could not execute with PowerShell API, falling back to Shell with less runtime details...` emitted by `Sdk.targets` is an **environment-specific** warning that appears when the DataMiner SDK build targets cannot use the PowerShell API (e.g., in CI environments or machines where the PowerShell execution policy or host API is restricted). The build still succeeds; only the runtime detail level of the SDK's internal tool invocation is reduced.
This warning does **not** indicate a defect in the connector code, QAction code, or generated helpers. It is safe to ignore.
**When this warning appears**: it will show up for every project in the solution that uses the DataMiner SDK (including `.TestPackage` projects). Seeing it twice for the same `.csproj` is normal — the SDK targets run multiple tool steps per project.
**No code change is required.** If the warning must be suppressed in CI evaluation output, configure the build system to filter SDK-level informational warnings from the warning count. Do **not** attempt to suppress it via `` — it is not a C# compiler diagnostic code.
### GitHub Packages Authentication Warnings
Warnings such as `NuGet.targets(198,5): warning : Your request could not be authenticated by the GitHub Packages service.` or `Project.csproj : warning Undefined: Your request could not be authenticated by the GitHub Packages service.` are **not** safe-to-ignore SDK fallback warnings and do **not** indicate a connector-code defect.
Inspect the configured NuGet package source and the CI authentication token's GitHub Packages package-read permissions. Ensure the source URL is correct and that the token is available to the restore/build step with permission to read the required packages. Do **not** suppress these warnings with ``; fix the package-source authentication instead.
### Registering a QAction in protocol.xml
Every new QAction needs a matching `` XML entry. In an orchestrated run, the assigned
XML owner adds it; the QAction writer returns missing or changed registrations as requests to
the orchestrator, never edits `protocol.xml`, and pauses affected work until XML and conditional
helper gates have run again.
```xml
```
And create the corresponding `QAction_N/` project in the solution:
- `QAction_N.cs` — the C# code
- `QAction_N.csproj` — SDK-style project referencing the selected `Skyline.DataMiner.Dev.Protocol`.
After creating the project files, add the project to the **`QActions` solution folder** (not the root):
- **`.slnx`** (new format): add `` inside the existing `` element.
- **`.sln`** (classic format): run `dotnet sln add --solution-folder QActions QAction_N/QAction_N.csproj`.
```xml
net48True
```
The official connector template includes a `QAction_Helper` project and `ProjectReference` by default. Preserve that reference when this QAction consumes generated `Parameter`, `SLProtocolExt`, `{Table}QActionTable`, or `{Table}QActionRow` members. If the solution is intentionally helper-free, omit the project reference, use descriptive local constants, and compile against `Skyline.DataMiner.Dev.Protocol`; see the [compilable helper-free example](references/examples/helper-free-qaction/QAction.cs).
> ⚠️ **NEVER** add `enable` (or any `` element) to a QAction `.csproj`. The DataMiner SDK targets `net48`, which defaults to **C# 7.3** — nullable reference types require C# 8.0+. Adding `enable` causes **error CS8630** on every affected QAction project, blocking compilation entirely. If NuGet packages are required, add an `` block — do not modify the `` settings shown above.
---
## SLProtocol API Reference
### Important Methods
| Method | Owner | Description |
|--------|-------|-------------|
| `protocol.GetParameter(id)` | `SLProtocol` | Get a parameter value |
| `protocol.SetParameter(id, value)` | `SLProtocol` | Set a parameter value |
| `protocol.GetParameters(ids[])` | `SLProtocol` | Get multiple parameters at once |
| `protocol.SetParameters(ids[], values[])` | `SLProtocol` | Set multiple parameters at once |
| `protocol.FillArray(tablePid, columns)` | `SLProtocol` | Replace full table — **column-oriented** by default |
| `protocol.FillArrayNoDelete(tablePid, columns)` | `SLProtocol` | Upsert rows — **column-oriented** format |
| `protocol.FillArrayWithColumn(tablePid, columnPid, keys, values)` | `SLProtocol` | Update specific cells in one column |
| `protocol.SetRow(tablePid, primaryKey, rowData)` | `SLProtocol` | Update one existing row (use string-key overload) |
| `protocol.AddRow(tablePid, rowData)` | `SLProtocol` | Add a row when the primary key is absent |
| `protocol.DeleteRow(tablePid, key)` | `SLProtocol` | Remove row(s) — accepts `string` or `string[]` |
| `protocol.GetRow(tablePid, primaryKey)` | `SLProtocol` | Get all cell values in a row as `object[]` |
| `protocol.GetKeys(tablePid)` | `SLProtocol` | Get all primary keys as `string[]` |
| `protocol.Exists(tablePid, primaryKey)` | `SLProtocol` | Check if row exists |
| `protocol.GetColumns(tablePid, indexes)` | `Protocol.Extension` package | Read several table columns by 0-based index |
| `protocol.SetColumns(data)` | `Protocol.Extension` package | Set several table columns in one call |
| `protocol.Log(message, type, level)` | `SLProtocol` | Write to DataMiner logging |
| `protocol.NotifyProtocol(type, value1, value2)` | `SLProtocol` | Notify protocol engine |
| `protocol.CheckTrigger(triggerId)` | `SLProtocol` | Fire a trigger programmatically (use to re-poll a group) |
The public member links and generated-wrapper boundaries are centralized in `dataminer-connector-core/references/logic-qactions.md#api-surface-ownership`.
### Methods That Do NOT Exist on SLProtocol
These are common mistakes — these methods do NOT exist on `SLProtocol`:
| Wrong Call | Correct Alternative |
|-----------|-------------------|
| `protocol.ExecuteGroup(groupId)` | `protocol.CheckTrigger(triggerId)` — define a Trigger in protocol.xml that starts the group, then call CheckTrigger |
| `protocol.RunGroup(groupId)` | `protocol.CheckTrigger(triggerId)` |
| `protocol.PollGroup(groupId)` | `protocol.CheckTrigger(triggerId)` |
> To programmatically trigger a poll group from a QAction, define a `` in protocol.xml that starts the group, then call `protocol.CheckTrigger(triggerId)` from C#.
> **Raw NotifyProtocol / NotifyDataMinerQueued calls**: For exact parameter shapes of specific numeric call types (127, 128, 193, 194, 220, 221, 321), load `dataminer-qaction/references/raw-notify-calls.md`. Prefer wrapper methods when available.
---
## Table Fill Methods
All table methods operate on **retrieved** columns only; `custom`-type columns between retrieved columns are skipped automatically. None support `autoincrement`.
### Choosing the Right Method
| Goal | Method |
|------|--------|
| Replace full table — column-oriented (preferred bulk) | `FillArray(tablePid, object[] columns)` |
| Replace full table — row-oriented | `FillArray(tablePid, List