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.0target. 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:
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:
_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:
_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:
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:
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
_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
- Use async/await consistently - Don't mix sync and async
- Dispose resources properly - Use
usingstatements - Configure logging levels - Set to Warning/Error in production
- 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
- Check dependencies are included in publish
- Use
--self-containedflag - 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
});
}