C# / .NET
The C# connector targets .NET 8.0 with zero external dependencies. Every method
is async and returns byte[] for rendered output.
Add the connector
There's no NuGet package to add (yet) — the client is a single, dependency-free
file. Copy CalcoClient.cs into your project, keeping its namespace Calco;, and
use it:
using Calco;Quick start
using Calco;
using var client = new CalcoClient(
"http://localhost:8080",
apiKey: "dc_live_...",
timeout: TimeSpan.FromMinutes(5));CalcoClient is IDisposable — wrap it in using or register it as a singleton
(see below). The key is sent as Authorization: Bearer <key> on every request.
Converting a file
byte[] pdf = await client.ConvertAsync(
filePath: "report.xlsx",
format: "pdf",
dataJson: """{"company": "Acme"}""",
sheet: "Summary",
title: "Q4 Report");
await File.WriteAllBytesAsync("report.pdf", pdf);You can convert from a stream instead of a path:
await using var stream = File.OpenRead("report.xlsx");
byte[] pdf = await client.ConvertAsync(stream, "report.xlsx", format: "pdf");Extract the placeholder map with ScaffoldAsync:
var scaffold = await client.ScaffoldAsync("spreadsheet.xlsx");Managing templates
// Create
var template = await client.CreateTemplateAsync(
filePath: "template.xlsx", name: "Invoice", category: "finance", tags: "billing,monthly");
// List
var templates = await client.ListTemplatesAsync();
foreach (var t in templates.EnumerateArray())
Console.WriteLine($"{t.GetProperty("id")}: {t.GetProperty("name")}");
// Update metadata
await client.UpdateTemplateAsync(id: "42", name: "Invoice v2", category: "accounting");
// Add a new version
await client.CreateTemplateVersionAsync(id: "42", filePath: "template_v2.xlsx");
// Delete
await client.DeleteTemplateAsync("42");Rendering a stored template
byte[] html = await client.RenderTemplateVersionAsync(
id: "42",
version: "2",
format: "html",
dataJson: """{"customer": "Jane"}""",
title: "Invoice #1001");
await File.WriteAllBytesAsync("invoice.html", html);Batch rendering
Pass a JSON array of records and receive a ZIP archive:
var records = """[{"name":"Alice","amount":100},{"name":"Bob","amount":200}]""";
byte[] zip = await client.BatchRenderAsync(
recordsJson: records,
templateId: "42",
version: "1",
format: "pdf",
titleTemplate: "invoice_{{name}}");
await File.WriteAllBytesAsync("invoices.zip", zip);By-hash workflow
Every stored version exposes a content hash (sha256 of the file). Address a
version directly by hash:
var meta = await client.GetTemplateByHashAsync("3c9f...d1ab");
byte[] pdf = await client.RenderByHashAsync("3c9f...d1ab", "pdf", dataJson: """{"customer":"Jane"}""");
byte[] xlsx = await client.DownloadByHashAsync("3c9f...d1ab", "xlsx");Error handling
All errors throw CalcoException, which carries the HTTP status code:
try
{
await client.ConvertAsync("missing.xlsx", "pdf");
}
catch (CalcoException ex)
{
Console.WriteLine($"Status: {ex.StatusCode}");
Console.WriteLine($"Message: {ex.Message}");
}ASP.NET Core integration
Dependency injection
// Program.cs
builder.Services.AddSingleton<CalcoClient>(_ =>
new CalcoClient(
builder.Configuration["Calco:Url"]!,
apiKey: builder.Configuration["Calco:ApiKey"]));Minimal API
app.MapPost("/export", async (IFormFile file, string format, CalcoClient dc) =>
{
await using var stream = file.OpenReadStream();
byte[] result = await dc.ConvertAsync(stream, file.FileName, format);
return Results.File(result,
contentType: "application/octet-stream",
fileDownloadName: $"export.{format}");
});Controller
[ApiController]
[Route("api/[controller]")]
public class ReportsController : ControllerBase
{
private readonly CalcoClient _dc;
public ReportsController(CalcoClient dc) => _dc = dc;
[HttpPost("convert")]
public async Task<IActionResult> Convert(IFormFile file, [FromQuery] string format)
{
await using var stream = file.OpenReadStream();
var bytes = await _dc.ConvertAsync(stream, file.FileName, format);
return File(bytes, "application/octet-stream", $"converted.{format}");
}
}Configuration options
| Parameter | Default | Description |
|---|---|---|
baseUrl | — | Root URL of the service (required) |
apiKey | null | Bearer token for authentication |
httpClient | null | Optional pre-configured HttpClient |
timeout | 120 seconds | Request timeout |