C# (.NET)
一个 C# 控制台程序,使用 HttpClient 和 System.Net.Http.Json(二者都是 .NET 的一部分)调用 API:无需安装任何包。它列出模板中带标签的变量,用给定的值测量所生成的图纸,并将其导出为 PDF。
下载:Program.cs,或者下载整个项目(程序及其项目文件):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);
工作原理
- 设置。 密钥来自
PIQ_API_KEY,地址来自PIQ_URL(未设置时为服务本身的地址),还可以选择从PIQ_STORAGE读取存储名称。没有密钥时,程序会立即停止。 - 模板。 每个请求都给出
template(模板的文本),或者在设置了PIQ_STORAGE时给出source(模板在您存储中的保存位置);另一个为null。values按名称给出L和W。 - 客户端 以服务地址作为其
BaseAddress,并在每个请求的X-API-KEY标头中携带密钥。序列化选项采用 .NET 的 Web 默认设置(camelCase 名称,与 API 一致),并省略null属性,因此template和source只会发送其中一个。 Post将正文(一个匿名对象)以 JSON 形式发送到某个操作。成功的响应会交回以供读取。任何其他响应都会作为文本读取:401没有正文,因此只有在有正文时才会将其解析为Problem,并打印其title、detail以及errors中的每一项(字段及其问题)。不是 JSON 的正文按原样打印。然后程序以1退出。PostFor<T>发送请求,并将 JSON 响应读取为T:程序末尾的记录类型只包含此处用到的属性。- Inputs。
POST /inputs只需要模板。其每个Inputs都有名称、标签、类型和值;值是JsonElement,因为它可能是数字、文本,或true、false。 - Measure。 使用这些值调用
POST /measure,可得到图纸的宽度和高度,以及按图层统计的线长;切割长度即Cut图层上的长度。 - Export。
POST /export/pdf返回 PDF 本身而不是 JSON:其字节被写入box.pdf。X-PiQ-Warnings标头表示有多少内容无法按原样导出。 - Save(可选)。 指定了存储时,
POST /save/pdf会导出同一图纸,并将其保存到您存储中的destination,overwrite = true表示替换已存在的文件。响应会说明保存位置及文件大小。
运行
解压 piq-saas-csharp.zip(会生成一个 Box 文件夹,其中包含程序及其项目文件),然后在其中运行:
export PIQ_API_KEY=XXXX-XXXX-XXXX-XXXXX
dotnet run
或者新建一个控制台项目,将程序放入其 Program.cs,然后运行:
dotnet new console -o Box
cd Box
# replace Program.cs with the program above
export PIQ_API_KEY=XXXX-XXXX-XXXX-XXXXX
dotnet run
在 PowerShell 中,使用 $env:PIQ_API_KEY = "XXXX-XXXX-XXXX-XXXXX" 设置密钥。
使用 .NET 10 时,也可以不建项目、直接以 dotnet run box.cs 单独运行该程序。这样的程序会预先编译(AOT),从而去掉了此处 System.Text.Json 所用的反射,因此请将其第一行设为
#:property PublishAot=false。
它会打印模板中带标签的变量、图纸的尺寸和切割长度,并保存 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)
如果给出模板无法使用的值(例如 "L": "long"),它会打印问题并停止:
Error 400 Bad Request
One or more validation errors occurred.
Some of the values given can't be used.
L: L is a number.
如果密钥不正确,它只会打印 Error 401 Unauthorized:401 没有问题描述。
要试用存储部分,请注册一个存储(请参阅注册存储),将模板以 templates/box.piq
放入其中,并将 PIQ_STORAGE 设置为该存储的名称。这样,模板将从您的存储中读取,
PDF 也会以 drawings/box.pdf 保存到其中,替换已存在的任何文件。