Skip to content

Document the PublishingActivityProgressReporter API #4138

@captainsafia

Description

@captainsafia

Proposed topic or title

Progress reporting for .NET Aspire publishing and deployment

Location in table of contents

Fundamentals > Publishing and Deployment > Building container images

Reason for the article

Clear, structured output during long-running aspire publish and aspire deploy commands reduces uncertainty and surfaces failures early.
PublishingActivityProgressReporter (injected as IPublishingActivityReporter) enables publishers and deployers to emit:

  • Steps — top-level phases such as Build images or Deploy workloads.
  • Tasks — discrete units of work nested under a step.
  • Completion statesCompleted, Warning, or Error, each rendered by the CLI with ✅, ⚠, or ✗.

The API guarantees ordered emissions, enforces completion, and streams updates to the console and dashboard in real time. This article standardizes usage across first- and third-party extensions.

Article abstract

The document should outline the following details WRT to the lifecycle and contract of PublishingActivityProgressReporter.

  • Acquisition — retrieved from PublishingContext.ActivityReporter or DeployingContext.ActivityReporter.
  • Step creationCreateStepAsync(title, ct) returns an IPublishingActivityStep; disposal finalises the step object.
  • Task creationIPublishingActivityStep.CreateTaskAsync(title, ct) returns an IPublishingActivityTask.
  • State transitionsSucceedAsync, WarnAsync, FailAsync; each accepts a summary message and propagates cancellation tokens.
  • CompletionCompletePublishAsync(message, state, isDeploy, ct) marks the entire operation, flushes buffered events, and sets the final exit code used by the CLI.

End-to-end example: Publishing and deploy callbacks

using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Publishing;
using Microsoft.Extensions.Logging;

public sealed class DemoEnvironmentResource : Resource
{
    public DemoEnvironmentResource(string name) : base(name)
    {
        Annotations.Add(new PublishingCallbackAnnotation(PublishAsync));
        Annotations.Add(new DeployingCallbackAnnotation(DeployAsync));
    }

    // ---------- PUBLISH PIPELINE ----------
    private static async Task PublishAsync(PublishingContext context)
    {
        var reporter = context.ActivityReporter;

        // STEP: Build container images
        await using (var buildStep = await reporter.CreateStepAsync(
                     "Build container images", context.CancellationToken))
        {
            var apiTask = await buildStep.CreateTaskAsync(
                "service-api image", context.CancellationToken);
            // dotnet publish -t:PublishContainer …
            await apiTask.SucceedAsync("service-api built", context.CancellationToken);

            var uiTask = await buildStep.CreateTaskAsync(
                "service-ui image", context.CancellationToken);
            await uiTask.SucceedAsync("service-ui built", context.CancellationToken);

            await buildStep.SucceedAsync(
                "All images produced", context.CancellationToken);
        }

        // STEP: Generate deployment manifests
        await using (var manifestStep = await reporter.CreateStepAsync(
                     "Generate manifests", context.CancellationToken))
        {
            var bicepTask = await manifestStep.CreateTaskAsync(
                "Write main.bicep", context.CancellationToken);
            // Write file to context.OutputPath …
            await bicepTask.SucceedAsync(
                $"main.bicep at {context.OutputPath}", context.CancellationToken);

            await manifestStep.SucceedAsync("Manifests ready", context.CancellationToken);
        }

        // Finalise publishing
        await reporter.CompletePublishAsync(
            completionMessage: "Publish pipeline completed",
            completionState: CompletionState.Completed,
            cancellationToken: context.CancellationToken);
    }

    // ---------- DEPLOY PIPELINE ----------
    private static async Task DeployAsync(DeployingContext context)
    {
        var reporter = context.ActivityReporter;

        await using (var deployStep = await reporter.CreateStepAsync(
                     "Apply Kubernetes resources", context.CancellationToken))
        {
            var apiTask = await deployStep.CreateTaskAsync(
                "kubectl apply api.yaml", context.CancellationToken);
            // kubectl apply -f api.yaml …
            await apiTask.SucceedAsync("api workload applied", context.CancellationToken);

            var uiTask = await deployStep.CreateTaskAsync(
                "kubectl apply ui.yaml", context.CancellationToken);
            await uiTask.SucceedAsync("ui workload applied", context.CancellationToken);

            await deployStep.SucceedAsync("Cluster updated", context.CancellationToken);
        }

        await reporter.CompletePublishAsync(
            completionMessage: "Deployment completed",
            completionState: CompletionState.Completed,
            isDeploy: true,
            cancellationToken: context.CancellationToken);
    }
}

Behavior summary

Aspect Description
Step hierarchy Steps form a strict tree; tasks belong to a single step. Nested steps are unsupported, ensuring linear top-level phases.
Ordering Creation and completion events preserve call order; the reporter serialises updates to avoid interleaving.
State machine Each step/task starts in Running and transitions exactly once to Completed, Warning, or Error. Attempting multiple transitions throws.
Cancellation All APIs accept CancellationToken and propagate OperationCanceledException to the CLI.
CLI integration The Aspire CLI subscribes to reporter events and renders status glyphs, elapsed time, and coloured output; failures set the CLI process exit code.
Concurrency Thread-safe implementation supports parallel task creation under the same step; completion ordering remains deterministic.
Disposal contract Awaiting disposal of an IPublishingActivityStep automatically completes the step if unfinished, preventing orphaned phases.

Implementation guidance

  • Always encapsulate long-running logical phases in a step; avoid emitting raw tasks without a parent step.
  • Keep titles concise (< 60 characters); the CLI truncates longer strings.
  • Call CompletePublishAsync exactly once per publishing or deployment operation.
  • Treat warnings as recoverable; allow subsequent steps to proceed. Treat errors as fatal; fail fast and surface diagnostics.
  • Prefer asynchronous, cancellation-aware IO to avoid blocking reporter event processing.

Relevant searches


"PublishingActivityProgressReporter API", "IPublishingActivityReporter CreateStepAsync",
"PublishingActivityProgressReporter CompletePublishAsync", "Aspire progress CLI output"


Associated WorkItem - 474248

Metadata

Metadata

Assignees

Labels

📌 seQUESTeredIdentifies that an issue has been imported into Quest.area-docs

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions