C# (.NET)
A C# console program that uses the API with HttpClient and System.Net.Http.Json, both part of .NET: no packages to install. It lists a template's labelled variables, measures the drawing it makes with the values given, and exports it as a PDF.
Download it: Program.cs, or the whole project, the program with its project file, as piq-saas-csharp.zip.
// Program.cs: list a template's inputs, measure it and export it as a PDF with the PiQ SaaS API.
// .NET 8 or later; HttpClient and System.Net.Http.Json, no packages needed.
using System.Net.Http.Json;
using System.Text.Json;
var key = Environment.GetEnvironmentVariable("PIQ_API_KEY");
var baseUrl = Environment.GetEnvironmentVariable("PIQ_URL") ?? "https://saas.parametriq.co.uk";
var storage = Environment.GetEnvironmentVariable("PIQ_STORAGE"); // optional: a registered storage's name
if (string.IsNullOrEmpty(key))
{
Console.Error.WriteLine("Set PIQ_API_KEY to your API key.");
return 1;
}
const string Template =
"L=400,\"Length\"\nW=300,\"Width\"\nP1=[L,0]\nP2=[L,W]\nP3=[0,W]\nPOLYLINE @1,[P0,P1,P2,P3],TRUE,0";
// the template itself, or (optional) where it is kept in your storage
object? source = storage is null ? null : new { storage, key = "templates/box.piq" };
string? template = storage is null ? Template : null;
var values = new Dictionary<string, object> { ["L"] = 500, ["W"] = 250 };
using var http = new HttpClient { BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/") };
http.DefaultRequestHeaders.Add("X-API-KEY", key);
var json = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{ DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull };
async Task<HttpResponseMessage> Post(string path, object body)
{
var response = await http.PostAsJsonAsync(path, body, json);
if (response.IsSuccessStatusCode) return response;
Console.Error.WriteLine($"Error {(int)response.StatusCode} {response.ReasonPhrase}");
var text = await response.Content.ReadAsStringAsync(); // a 401 has no body
if (text.Length > 0)
{
try
{
var problem = JsonSerializer.Deserialize<Problem>(text, json)!;
if (problem.Title is not null) Console.Error.WriteLine(problem.Title);
if (problem.Detail is not null) Console.Error.WriteLine(problem.Detail);
foreach (var (field, messages) in problem.Errors ?? [])
Console.Error.WriteLine($" {field}: {string.Join(" ", messages)}");
}
catch (JsonException) { Console.Error.WriteLine(text); }
}
Environment.Exit(1);
return response;
}
async Task<T> PostFor<T>(string path, object body) =>
(await (await Post(path, body)).Content.ReadFromJsonAsync<T>(json))!;
// 1. The template's labelled variables
var inputs = await PostFor<InputsResponse>("inputs", new { template, source });
foreach (var i in inputs.Inputs)
Console.WriteLine($"{i.Name}: {i.Label} ({i.Kind}, {i.Value})");
// 2. The drawing measured, with the values given
var m = await PostFor<MeasureResponse>("measure", new { template, source, values });
var cut = m.Layers.FirstOrDefault(l => l.Layer == "Cut")?.Length ?? 0;
Console.WriteLine($"Size: {m.Width} x {m.Height}, cut length: {cut}");
// 3. The drawing as a PDF
var pdf = await Post("export/pdf", new { template, source, values });
await File.WriteAllBytesAsync("box.pdf", await pdf.Content.ReadAsByteArrayAsync());
Console.WriteLine($"Saved box.pdf ({pdf.Headers.GetValues("X-PiQ-Warnings").First()} warnings)");
// 4. Optional: the PDF saved to your storage instead of downloaded
if (storage is not null)
{
var destination = new { storage, key = "drawings/box.pdf" };
var body = new { source, values, destination, overwrite = true };
var saved = await PostFor<SavedFileResponse>("save/pdf", body);
Console.WriteLine($"Saved {saved.Key} to {saved.Storage} ({saved.Size} bytes)");
}
return 0;
record InputResponse(string Name, string Label, string Kind, JsonElement Value);
record InputsResponse(List<InputResponse> Inputs);
record LayerLengthResponse(string Layer, double Length);
record MeasureResponse(double Width, double Height, List<LayerLengthResponse> Layers);
record SavedFileResponse(string Storage, string Key, long Size);
record Problem(string? Title, string? Detail, Dictionary<string, string[]>? Errors);
How it works
- Settings. The key comes from
PIQ_API_KEY, the address fromPIQ_URL(the service itself if not set) and, optionally, a storage's name fromPIQ_STORAGE. Without a key the program stops at once. - The template. Each request gives
template(the template's text) or, whenPIQ_STORAGEis set,source(where it is kept in your storage); the other isnull.valuesgivesLandWby name. - The client has the service's address as its
BaseAddressand the key in theX-API-KEYheader of every request. The serializer options are .NET's web defaults (camelCase names, as the API uses), leaving outnullproperties, so only one oftemplateandsourceis sent. Postsends a body (an anonymous object) to an operation as JSON. A success is handed back to be read. Any other response is read as text: a401has no body, so it is only parsed, as aProblem, when there is one, and itstitle,detailand each entry oferrors(the field and what is wrong with it) are printed. A body that isn't JSON is printed as it is. The program then exits with1.PostFor<T>posts and reads the JSON answer asT: the records at the end of the program have only the properties used here.- Inputs.
POST /inputsneeds only the template. Each of itsInputshas a name, label, kind and value; the value is aJsonElement, since it may be a number, text ortrueorfalse. - Measure.
POST /measurewith the values gives the drawing's width and height, and its lines' lengths by layer; the cut length is the length on theCutlayer. - Export.
POST /export/pdfanswers with the PDF itself, not JSON: its bytes are written tobox.pdf. TheX-PiQ-Warningsheader says how many things couldn't be exported as they are. - Save (optional). With a storage,
POST /save/pdfexports the same drawing and saves it to your storage atdestination,overwrite = truereplacing a file already there. It answers with where it saved it and its size.
Running it
Unzip piq-saas-csharp.zip (it makes a Box folder, with the program and its project file), and run it there:
export PIQ_API_KEY=XXXX-XXXX-XXXX-XXXXX
dotnet run
Or make a console project, put the program in its Program.cs, and run it:
dotnet new console -o Box
cd Box
# replace Program.cs with the program above
export PIQ_API_KEY=XXXX-XXXX-XXXX-XXXXX
dotnet run
In PowerShell, set the key with $env:PIQ_API_KEY = "XXXX-XXXX-XXXX-XXXXX".
With .NET 10 the program can also be run on its own, as dotnet run box.cs, without a project. Such a program is
compiled ahead of time, which leaves out the reflection System.Text.Json uses here, so make its first line
#:property PublishAot=false.
It prints the template's labelled variables, the drawing's size and cut length, and saves box.pdf:
L: Length (Number, 400)
W: Width (Number, 300)
D: Depth (Number, 300)
Size: 500 x 250, cut length: 1500
Saved box.pdf (0 warnings)
With a value the template can't use ("L": "long", say), it prints the problem and stops:
Error 400 Bad Request
One or more validation errors occurred.
Some of the values given can't be used.
L: L is a number.
With a key that isn't right it prints only Error 401 Unauthorized: a 401 has no
problem description.
To try the storage part, register a storage (see Register a storage), put a template in
it as templates/box.piq, and set PIQ_STORAGE to the storage's name. The template is then read from your storage,
and the PDF is also saved to it as drawings/box.pdf, replacing any file already there.
See also: API, Inputs, Measure, Export a drawing, Save to your storage, Register a storage, Clients.