Building a C# Client
C# is an industry standard for enterprise applications and Windows-based service architectures.
A simple file poller
This console application uses a simple loop to monitor the SUB_DIR for new, non-hidden files.
using System;
using System.IO;
using System.Linq;
using System.Threading;
// 🔥 set this to your subscription folder, e.g. <home folder>/hypermass/subscriptions/<stream id>
const string SUB_DIR = @"<<YOUR PATH HERE>>";
Console.WriteLine($"Monitoring Hypermass folder {SUB_DIR}...");
while (true)
{
ScanAndProcess();
Thread.Sleep(5000); // Wait 5 seconds
}
void ScanAndProcess()
{
var dir = new DirectoryInfo(SUB_DIR);
if (!dir.Exists) return;
// List files, ignoring hidden files (like .hypermass metadata)
// Sort by name - payload ids are guaranteed to sort in publication order
var files = dir.GetFiles()
.Where(f => !f.Attributes.HasFlag(FileAttributes.Hidden) && !f.Name.StartsWith("."))
.OrderBy(f => f.Name, StringComparer.Ordinal)
.ToList();
foreach (var file in files)
{
Console.WriteLine($"Processing: {file.Name}");
try
{
// ⚠️⚠️⚠️ YOUR LOGIC HERE ⚠️⚠️⚠️
// string content = File.ReadAllText(file.FullName);
// Delete once processed
file.Delete();
}
catch (Exception ex)
{
Console.WriteLine($"Error processing {file.Name}: {ex.Message}");
// Break to preserve order - try again on next scan
break;
}
}
}
About this code
We suggest starting with this and modifying it to suit your needs. Some notes about the approach;
- LINQ Filtering: We use
.Whereto easily filter out hidden files including the.hypermassmetadata folder. - Name Sorting: Each file is named with its payload id, and payload ids sort in publication order, so
OrderBy(f => f.Name, StringComparer.Ordinal)processes files in publication order. - Atomic Safety: Because the Hypermass CLI performs an atomic "Move" into the directory,
file.Delete()will not interfere with active writes.
The hypermass sync command relies on the hidden .hypermass directory for tracking progress. The logic above ensures your code ignores this system folder while keeping it intact.
How to productionize this code example
Moving from a Console App to a robust production service:
Small file subscriptions
For standard JSON or XML files, File.ReadAllText() or File.ReadAllBytes() is usually sufficient for files up to
50MB at low message cadences.
Large file subscriptions
If you are processing larger files, use FileStream to avoid memory exhaustion:
- JSON: Use System.Text.Json with
Utf8JsonReaderfor high-performance streaming. - XML: Use XmlReader for a memory-efficient "forward-only" stream.
Stop on fail
In the example above, the break inside the catch block is intentional - you may want to go further and stop the
poller completely. In many enterprise scenarios, if payload #5 fails, you should not move on to payload #6 until the
issue is resolved to maintain data integrity.
Structure
You'll likely want to convert this over to a class based approach so that you can more easily control and interact with various instances.
Using Worker Services
For a modern .NET approach to persistent background services, you may want to wrap this logic in a BackgroundService.
This simplifies the management of the background thread and integrates well with the program life cycle.
public class HypermassSubscriptionWorker : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
ScanAndProcess();
await Task.Delay(5000, stoppingToken);
}
}
}