Skip to main content

C# Starter Template

Professional plugin template for C# with async/await patterns and modern .NET features.

Overview​

The C# starter demonstrates the executable plugin contract with:

  • Modern async/await patterns throughout
  • Challenge-response authentication
  • SocketIOClient library integration
  • Microsoft.Extensions.Logging for structured logging
  • Single-file deployment option

Prerequisites​

  • A .NET SDK able to build the bundled net6.0 target. For production, retarget and validate with a supported .NET version; .NET 6 support ended November 12, 2024. See Microsoft's support policy.
  • Visual Studio, VS Code, or Rider (optional)
  • Basic understanding of C# and async programming

Quick Start​

1. Get the Template​

Download the csharp starter and extract the archive. Open a terminal in the extracted folder:

cd csharp-starter

Use Build and install on Linux for Linux. The numbered build and install example below targets Windows.

2. Restore Dependencies​

dotnet restore

3. Build​

dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o ./publish

4. Install in HyperHQ on Windows​

Use the HyperSpin root shown in Settings, Paths. Replace this example with your configured plugin folder.

$pluginDir = 'C:/ProgramData/HyperSpin/plugins/my-csharp-plugin'
New-Item -ItemType Directory -Force -Path $pluginDir
Copy-Item -Path 'publish/.' -Destination $pluginDir -Recurse
Copy-Item -LiteralPath 'plugin.json' -Destination $pluginDir

Build and install on Linux​

Run this command inside the extracted starter directory on Linux. The runtime argument overrides the template's Windows runtime identifier. Use linux-arm64 for an arm64 target.

dotnet publish -c Release -r linux-x64 --self-contained true -p:PublishSingleFile=true -o ./publish-linux

Set executableProviders.linux to HyperHQPlugin in the published plugin.json.

plugin_dir="/absolute/path/to/plugins/my-csharp-plugin"
mkdir -p "$plugin_dir"
cp -R publish-linux/. "$plugin_dir/"
chmod +x "$plugin_dir/HyperHQPlugin"

Preserve adjacent native libraries and runtime files from the publish output.

Replace the example directory with your configured plugin path. Restart or reload the plugin in HyperHQ and verify authentication, an action, and shutdown. Follow the Linux release requirements before publishing.

Key Features​

Environment Variables​

The template validates required environment variables in the constructor:

Program.cs
public Plugin()
{
_pluginId = Environment.GetEnvironmentVariable("HYPERHQ_PLUGIN_ID");
if (string.IsNullOrEmpty(_pluginId))
{
throw new InvalidOperationException(
"Missing HYPERHQ_PLUGIN_ID - plugin must be launched by HyperHQ"
);
}

_authChallenge = Environment.GetEnvironmentVariable("HYPERHQ_AUTH_CHALLENGE");
// ... more validation
}

Async Socket.IO Communication​

Full async/await pattern support:

Program.cs
_socket.On("authenticated", async response =>
{
var data = response.GetValue<Dictionary<string, object>>();
if (data["success"] is true)
{
_sessionToken = data["sessionToken"].ToString();
_isAuthenticated = true;

await RegisterPlugin();
await SendNotification("Connected to HyperHQ", "info");
}
});

Structured Logging​

Built-in logging with Microsoft.Extensions.Logging:

Program.cs
_logger.LogInformation("Authentication successful");
_logger.LogDebug("Data request sent: {Method} (ID: {RequestId})", method, requestId);
_logger.LogError(ex, "Failed to connect to Socket.IO server");

Authenticated Requests​

Helper method for making authenticated data requests:

Program.cs
private async Task<string> RequestData(string method, object parameters)
{
if (!_isAuthenticated)
{
throw new InvalidOperationException("Not authenticated with HyperHQ");
}

var requestId = $"{method}-{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}";

await _socket.EmitAsync("request_data", new
{
method = method,
@params = parameters,
requestId = requestId,
sessionToken = _sessionToken
});

return requestId;
}

Template Structure​

csharp-starter/
├── Program.cs # Main plugin implementation
├── Plugin.csproj # Project configuration
├── plugin.json # Plugin manifest
├── build.sh # Build script (Unix)
└── build.bat # Build script (Windows)

Customization​

1. Update Project Settings​

Edit Plugin.csproj:

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>MyPlugin</AssemblyName>
</PropertyGroup>

2. Add Your Logic​

Implement custom actions:

Program.cs
public async Task<object> Execute(Dictionary<string, object> data)
{
var action = data.ContainsKey("action") ? data["action"].ToString() : "default";

return action switch
{
"my_action" => await HandleMyAction(data),
"sync_data" => await HandleSyncData(data),
_ => new { error = $"Unknown action: {action}" }
};
}

3. Add Event Handlers​

Program.cs
_socket.On("my:custom:event", response =>
{
var data = response.GetValue<Dictionary<string, object>>();
HandleCustomEvent(data);
});

Building for Production​

Single-File Deployment​

dotnet publish -c Release -r win-x64 \
--self-contained \
-p:PublishSingleFile=true \
-p:PublishTrimmed=true \
-o ./dist

Reduce File Size​

dotnet publish -c Release -r win-x64 \
--self-contained \
-p:PublishSingleFile=true \
-p:PublishTrimmed=true \
-p:PublishReadyToRun=true \
-p:TrimMode=Link

Performance Tips​

  1. Use async/await consistently - Don't mix sync and async
  2. Dispose resources properly - Use using statements
  3. Configure logging levels - Set to Warning/Error in production
  4. Use object pooling for frequently allocated objects

Troubleshooting​

"Microsoft.Extensions.Logging not found"​

dotnet restore
dotnet add package Microsoft.Extensions.Logging
dotnet add package Microsoft.Extensions.Logging.Console

"SocketIOClient not found"​

dotnet add package SocketIOClient

Plugin crashes on startup​

  1. Check dependencies are included in publish
  2. Use --self-contained flag
  3. Test executable from command line first

Authentication timeout​

// Increase connection timeout
var options = new SocketIOOptions
{
ConnectionTimeout = TimeSpan.FromSeconds(10),
// ...
};

Advanced Features​

Progress Reporting​

for (int progress = 0; progress <= 100; progress += 10)
{
await _socket.EmitAsync("statusUpdate", new
{
status = "processing",
message = $"Processing... {progress}%"
});

await Task.Delay(100);
}

Error Handling​

try
{
await ProcessData();
}
catch (Exception ex)
{
_logger.LogError(ex, "Processing failed");

await _socket.EmitAsync("statusUpdate", new
{
status = "error",
message = "Processing failed: " + ex.Message
});
}

Learn More​