Your first .NET pipeline
Run an HTTP worker, submit a typed stage, inspect its result, and stop cleanly.
Build a console application that starts an HTTP worker, submits a greeting pipeline, waits for its result, and shuts down. Download the example project or follow the steps below. The handler runs in your application; Pipelogiq stores and schedules the work.
Prerequisites
You need a .NET 8 SDK, a running Pipelogiq 0.5.0 server with its migrations applied, and an application API key. Complete the server setup first. Use the external worker API URL, normally http://localhost:8081 for local development, rather than the dashboard URL.
This application only needs outbound access to the API. The server still needs its configured infrastructure. For direct broker delivery, see HTTP and AMQP workers.
Install the packages
These docs describe SDK 0.5.0. On September 22, 2026, that version was not available from NuGet.org: the existing PipelogiqSDK index ended at 0.4.0-preview.1, and the six split package indexes were absent. Use a local or private feed containing all seven 0.5.0 packages. An older public package is not a substitute for this tutorial.
Prepare a feed from the current source
The following commands check out the public commit verified for these docs and pack the SDK locally. Run them in a directory outside your application. They require the .NET 8 SDK selected by the repository's global.json and internet access for third-party dependencies.
git clone https://github.com/pipelogiq/pipelogiq-sdk-net.git
cd pipelogiq-sdk-net
git checkout --detach 79fdf7ea22f5c972c9460099c8b4e6c2f26c926e
dotnet pack PipelogiqSdk.sln --configuration Release --output ./artifacts/docs-feedUse the resulting pipelogiq-sdk-net/artifacts/docs-feed directory as the feed path below. It contains all seven SDK .nupkg files. These commands build local artifacts; they do not publish packages or run a Pipelogiq server. The release preparation guide documents the full maintainer verification workflow.
Alternatively, use the exact 0.5.0 package folder supplied by your deployment team.
Create the consumer
Create the application:
dotnet new console --framework net8.0 --name HelloPipelogiq
cd HelloPipelogiqCreate NuGet.Config in this application directory. Replace /absolute/path/to/pipelogiq-packages with the package folder. On Windows, use the corresponding absolute Windows path.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="pipelogiq" value="/absolute/path/to/pipelogiq-packages" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="pipelogiq">
<package pattern="PipelogiqSDK*" />
</packageSource>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>The specific PipelogiqSDK* mapping sends SDK packages to your feed; other dependencies come from NuGet.org. Then install:
dotnet add package PipelogiqSDK.Http --version 0.5.0
dotnet add package Microsoft.Extensions.Hosting --version 8.0.1PipelogiqSDK.Http includes Core transitively. It does not include RabbitMQ.Client.
Write the application
Replace the entire Program.cs with the following. There are no additional handler or model files to create.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using PipelogiqSDK.Abstractions;
using PipelogiqSDK.Api;
using PipelogiqSDK.Builders;
using PipelogiqSDK.Configuration;
using PipelogiqSDK.Contracts;
using PipelogiqSDK.Http.Api;
using PipelogiqSDK.Http.Runner;
using PipelogiqSDK.StageHelper;
var apiKey = Environment.GetEnvironmentVariable("PIPELOGIQ_API_KEY");
if (string.IsNullOrWhiteSpace(apiKey))
throw new InvalidOperationException("Set PIPELOGIQ_API_KEY first.");
var options = new PipelogiqRunnerOptions
{
ApiKey = apiKey,
ApiUrl = Environment.GetEnvironmentVariable("PIPELOGIQ_API_URL")
?? "http://localhost:8081",
WorkerName = "hello-http-worker",
DrainGracePeriod = TimeSpan.FromSeconds(30)
};
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddPipelogiqHttpWorker(options);
builder.Services.AddTransient<HelloHandler>();
using var host = builder.Build();
var lifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();
using var stop = CancellationTokenSource.CreateLinkedTokenSource(
lifetime.ApplicationStopping);
using var deadline = CancellationTokenSource.CreateLinkedTokenSource(stop.Token);
deadline.CancelAfter(TimeSpan.FromSeconds(90));
var runner = host.Services.GetRequiredService<HttpPipelineRunner>();
runner.RegisterHandler("HelloHandler", typeof(HelloHandler));
await host.StartAsync(stop.Token);
var workerTask = runner.StartAsync(stop.Token);
try
{
// A new logical run gets a new key. Retrying this builder uses the same key.
using var pipeline = PipelineBuilder.Create("hello-dotnet", options)
.WithIdempotencyKey($"hello-{Guid.NewGuid():N}")
.AddLabel("example", "dotnet-quickstart")
.WithAction("greet", "HelloHandler", new HelloInput("world"));
var created = await pipeline.SendAsync(deadline.Token);
Console.WriteLine($"Pipeline {created.Id} created.");
using var client = new PipelogiqApiClient(options);
while (true)
{
if (workerTask.IsCompleted)
{
await workerTask; // Surface a worker failure before waiting further.
throw new InvalidOperationException("The worker stopped early.");
}
var current = await client.GetPipelineAsync(created.Id, deadline.Token);
if (current.IsTerminal ?? PipelineStatuses.IsTerminal(current.Status))
{
Console.WriteLine($"Pipeline {current.Id}: {current.Status}");
foreach (var stage in current.Stages ?? new List<StageDto>())
Console.WriteLine($"{stage.Name}: {stage.Status} — {stage.Output}");
if (!string.Equals(current.Status, PipelineStatuses.Completed,
StringComparison.OrdinalIgnoreCase))
Environment.ExitCode = 1;
break;
}
await Task.Delay(TimeSpan.FromMilliseconds(500), deadline.Token);
}
}
catch (OperationCanceledException) when (deadline.IsCancellationRequested)
{
Console.WriteLine("Stopped waiting. The pipeline may still exist on the server.");
Environment.ExitCode = 1;
}
finally
{
stop.Cancel();
try { await workerTask; }
catch (OperationCanceledException) when (stop.IsCancellationRequested) { }
finally
{
using var shutdown = new CancellationTokenSource(TimeSpan.FromSeconds(40));
await host.StopAsync(shutdown.Token);
}
}
internal sealed record HelloInput(string Name);
internal sealed class HelloHandler : IStageHandler<HelloInput>
{
public Task<IStageResult> ExecuteAsync(
HelloInput input, IStageContext? context = null)
{
context.GetCancellationToken().ThrowIfCancellationRequested();
var greeting = $"Hello {input.Name}";
context.AddItem("greeting", greeting);
context.LogInfo("Greeting prepared.");
return Task.FromResult<IStageResult>(StageResult.Success(greeting));
}
}Run and inspect the result
Set configuration in your terminal; keep the key out of source control:
export PIPELOGIQ_API_URL="http://localhost:8081"
export PIPELOGIQ_API_KEY="your-application-api-key"
dotnet runPowerShell equivalents:
$env:PIPELOGIQ_API_URL = "http://localhost:8081"
$env:PIPELOGIQ_API_KEY = "your-application-api-key"
dotnet runIn addition to runtime logs, expect output similar to:
Pipeline 123 created.
Pipeline 123: Completed
greet: Completed — Hello worldThe numeric ID varies. Open that pipeline in the dashboard to inspect the stage input, output, log, and greeting context value. Submission returns a pipeline ID before the work finishes; the status loop observes completion separately.
The program stops the worker after observing a terminal result. Ctrl+C uses the host's shutdown signal. A timeout only stops this local example from waiting; it does not call the pipeline cancellation API.
If it does not complete
| Symptom | Check |
|---|---|
NU1101 or missing version | Your configured feed contains both HTTP and Core 0.5.0 packages and their internal dependencies. |
| Authentication failure | Use an application API key and the external API URL. |
| Pipeline stays unstarted | API and scheduler are running; HelloHandler is registered under exactly the submitted name. |
| Worker repeatedly bootstraps | Check runtime logs, server compatibility, API reachability, and queue provisioning on the server. |
| Pipeline fails | Inspect each stage's output, LastErrorCode, attempts, and logs. |
Next, add workflow dependencies and retries, or separate this producer and worker into different processes using the worker guide.