Console and worker apps
Questi contenuti non sono ancora disponibili nella tua lingua.
Console applications and background workers can participate in an Aspire application regardless of the language they’re written in. Add them to your AppHost to manage their lifecycle alongside your other resources, provide connection strings and service endpoints, and view their console output in the dashboard.
Adding a console app to the AppHost
Section titled “Adding a console app to the AppHost”Choose the resource type that matches how your app runs. Use an executable resource for a command-line program, a Python app resource for a script or module, or a JavaScript app resource for a Node.js app. For a .NET console app or Worker Service project, use a project resource. None of these apps needs to expose an HTTP endpoint to be orchestrated by Aspire.
For more information on executable, Python, and JavaScript resources, see Executable resources, Python integration, and JavaScript integration.
The following examples show how to add a .NET or Node.js console app, connect it to other resources, and run it as a background worker. The examples use three sets of tabs, which you choose independently:
- Aspire CLI, apphost.cs (C# file-based app), and PackageReference(*.csproj) tabs select an installation method for Aspire integrations. If you’re using a TypeScript AppHost, only the Aspire CLI tab is applicable.
- .NET and Node.js tabs select the language of the console app or executable that the AppHost orchestrates.
- C# and TypeScript tabs select the language of the AppHost itself.
For example, to orchestrate a Node.js console app from a C# AppHost, choose any installation method, then Node.js, and then C#. Your selections stay the same as you move through the page.
These examples assume an existing AppHost.
- .NET: The examples also assume a shared service defaults project. Create the named console, worker, and API projects before referencing them. In a C# AppHost, add project references so the corresponding
Projects.*types are generated; in a TypeScript AppHost, use each project’s actual path. - Node.js: Create each app folder with a package.json file before referencing it. The AppHost resolves the app folder path relative to the AppHost directory, and the script path relative to the app folder.
For the Redis examples, install the hosting integration in your AppHost. Running Redis also requires a supported container runtime.
aspire add redisLa CLI Aspire è interattiva; seleziona il risultato corretto quando richiesto:
Select an integration to add:
> redis (Aspire.Hosting.Redis)> Other results listed as selectable options...aspire add redisLa CLI Aspire è interattiva; seleziona il risultato corretto quando richiesto:
Select an integration to add:
> redis (Aspire.Hosting.Redis)> Other results listed as selectable options...#:package Aspire.Hosting.Redis@*<PackageReference Include="Aspire.Hosting.Redis" Version="*" />Add the console app project to your AppHost:
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const cache = await builder.addRedis('cache');
// Add a console app project to the AppHostawait builder .addProject('processor', '../MyConsoleApp/MyConsoleApp.csproj') .withReference(cache);
await builder.build().run();var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache");
// Add a console app project to the AppHostvar processor = builder.AddProject<Projects.MyConsoleApp>("processor") .WithReference(cache);
builder.Build().Run();The processor project receives the ConnectionStrings__cache environment variable at runtime, just like any other project type.
Install the JavaScript hosting integration in your AppHost. It provides AddNodeApp / addNodeApp, which runs a JavaScript file with Node.js:
aspire add javascriptLa CLI Aspire è interattiva; seleziona il risultato corretto quando richiesto:
Select an integration to add:
> javascript (Aspire.Hosting.JavaScript)> Other results listed as selectable options...aspire add javascriptLa CLI Aspire è interattiva; seleziona il risultato corretto quando richiesto:
Select an integration to add:
> javascript (Aspire.Hosting.JavaScript)> Other results listed as selectable options...#:package Aspire.Hosting.JavaScript@*<PackageReference Include="Aspire.Hosting.JavaScript" Version="*" />Then add the console app to your AppHost:
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const cache = await builder.addRedis('cache');
// Add a Node.js console app to the AppHostawait builder .addNodeApp('processor', '../processor', 'index.js') .withReference(cache);
await builder.build().run();var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache");
// Add a Node.js console app to the AppHostvar processor = builder.AddNodeApp("processor", "../processor", "index.js") .WithReference(cache);
builder.Build().Run();The console app uses the ioredis client. Aspire installs the dependencies listed in package.json before starting the app:
{ "name": "processor", "private": true, "type": "module", "dependencies": { "ioredis": "^5.4.0" }}import Redis from 'ioredis';
const redis = new Redis(process.env.CACHE_URI);
await redis.set('processor:last-run', new Date().toISOString());console.log('Last run:', await redis.get('processor:last-run'));
await redis.quit();The processor app receives the CACHE_URI, CACHE_HOST, CACHE_PORT, and CACHE_PASSWORD environment variables at runtime, along with ConnectionStrings__cache. For more information, see Connect to Redis.
Service discovery in console apps
Section titled “Service discovery in console apps”Console apps can call HTTP services that the AppHost manages. The following walkthrough assumes a CatalogService API project that exposes an HTTP endpoint at /, and a console app named processor that calls it.
Console apps using the .NET Generic Host can use the same service discovery infrastructure as web apps. This walkthrough uses a MyConsoleApp console project.
-
From the console project directory, install the hosting package:
.NET CLI — Add Microsoft.Extensions.Hosting package dotnet add package Microsoft.Extensions.HostingProgram.cs #:package Microsoft.Extensions.Hosting@*XML — Add Microsoft.Extensions.Hosting package reference <PackageReference Include="Microsoft.Extensions.Hosting" Version="*" />Add a reference to your existing service defaults project in the console project’s MyConsoleApp.csproj. Replace the example path with your project’s actual path:
MyConsoleApp.csproj <ItemGroup><ProjectReference Include="..\MyApp.ServiceDefaults\MyApp.ServiceDefaults.csproj" /></ItemGroup> -
Add a typed HTTP client to the console project:
CatalogClient.cs using System.Net.Http;public sealed class CatalogClient(HttpClient httpClient){public Task<string> GetAsync(CancellationToken cancellationToken = default)=> httpClient.GetStringAsync("/", cancellationToken);} -
Configure the host in your console app’s Program.cs. This example starts the host, calls the catalog API, and then stops:
Program.cs using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Hosting;var builder = Host.CreateApplicationBuilder(args);// Add service defaults (telemetry, health checks, service discovery)builder.AddServiceDefaults();// Register your servicesbuilder.Services.AddHttpClient<CatalogClient>(client => client.BaseAddress = new Uri("https+http://catalog"));using var app = builder.Build();await app.StartAsync();var catalogClient = app.Services.GetRequiredService<CatalogClient>();Console.WriteLine(await catalogClient.GetAsync());await app.StopAsync(); -
In the AppHost, reference the services this app depends on:
apphost.mts import { createBuilder } from './.aspire/modules/aspire.mjs';const builder = await createBuilder();const catalog = await builder.addProject('catalog','../CatalogService/CatalogService.csproj');const processor = await builder.addProject('processor','../MyConsoleApp/MyConsoleApp.csproj');await processor.withReference(catalog);await processor.waitFor(catalog);await builder.build().run();AppHost.cs var builder = DistributedApplication.CreateBuilder(args);var catalog = builder.AddProject<Projects.CatalogService>("catalog");var processor = builder.AddProject<Projects.MyConsoleApp>("processor").WithReference(catalog).WaitFor(catalog);builder.Build().Run();
The WithReference(catalog) call injects service discovery configuration so processor can resolve https+http://catalog to the running catalog service endpoint.
Node.js apps don’t use the .NET service discovery resolver. Instead, they read the catalog service’s endpoint URLs from environment variables that Aspire injects.
-
Create a processor folder next to your AppHost with a package.json file. This example uses the built-in
fetchAPI, so it has no dependencies:processor/package.json {"name": "processor","private": true,"type": "module"} -
Add an index.js file that calls the catalog API and then exits. It prefers the HTTPS endpoint and falls back to HTTP:
processor/index.js const catalogUrl = process.env.CATALOG_HTTPS ?? process.env.CATALOG_HTTP;if (!catalogUrl) {throw new Error('The catalog endpoint is not configured. Run the app through Aspire.');}const response = await fetch(new URL('/', catalogUrl));console.log(await response.text()); -
In the AppHost, reference the services this app depends on:
apphost.mts import { createBuilder } from './.aspire/modules/aspire.mjs';const builder = await createBuilder();const catalog = await builder.addProject('catalog','../CatalogService/CatalogService.csproj');const processor = await builder.addNodeApp('processor','../processor','index.js');await processor.withReference(catalog);await processor.waitFor(catalog);await builder.build().run();AppHost.cs var builder = DistributedApplication.CreateBuilder(args);var catalog = builder.AddProject<Projects.CatalogService>("catalog");var processor = builder.AddNodeApp("processor", "../processor", "index.js").WithReference(catalog).WaitFor(catalog);builder.Build().Run();
The withReference(catalog) call injects the catalog service’s endpoint URLs into processor as CATALOG_HTTPS and CATALOG_HTTP. Aspire also injects the same URLs in the services__catalog__https__0 format. For more information, see Service discovery.
To send traces, metrics, and logs from a Node.js app to the dashboard, configure the OpenTelemetry Node.js SDK in the app. For more information, see Add telemetry configuration.
Background worker services
Section titled “Background worker services”Background workers run for as long as the AppHost runs and stop when it shuts down. In .NET, Worker Service projects are the recommended pattern for long-running background tasks with Aspire. In Node.js, use a script that loops until it receives a shutdown signal.
Creating a worker
Section titled “Creating a worker”Worker Service projects use IHostedService (or BackgroundService) and integrate naturally with the .NET Generic Host.
Create a DataWorker project with the Worker Service template, then add a reference to your shared service defaults project as shown in the console walkthrough:
dotnet new worker --name DataWorker --output DataWorkerFrom the DataWorker directory, install the Redis client integration. This package supplies AddRedisClient; the AppHost’s hosting package doesn’t register a client in the worker:
dotnet add package Aspire.StackExchange.Redis#:package Aspire.StackExchange.Redis@*<PackageReference Include="Aspire.StackExchange.Redis" Version="*" />Replace the generated worker class with DataProcessor.cs. Its DoWorkAsync method is a placeholder for your application’s processing logic; registering the Redis client doesn’t itself perform Redis operations.
using Microsoft.Extensions.Hosting;using Microsoft.Extensions.Logging;
public class DataProcessor : BackgroundService{ private readonly ILogger<DataProcessor> _logger;
public DataProcessor(ILogger<DataProcessor> logger) { _logger = logger; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation("Processing data at: {Time}", DateTimeOffset.UtcNow);
await DoWorkAsync(stoppingToken);
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); } }
private Task DoWorkAsync(CancellationToken stoppingToken) { // Replace with your background work. return Task.CompletedTask; }}using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.AddServiceDefaults();
// Register resource connectionsbuilder.AddRedisClient("cache");
// Register the background workerbuilder.Services.AddHostedService<DataProcessor>();
var app = builder.Build();await app.RunAsync();Create a data-worker folder next to your AppHost with a package.json file that includes the ioredis client:
{ "name": "data-worker", "private": true, "type": "module", "dependencies": { "ioredis": "^5.4.0" }}Add a worker.js file. The worker runs every 10 seconds until Aspire stops the process, then closes its Redis connection. Its doWork function is a placeholder for your application’s processing logic.
import { setTimeout as delay } from 'node:timers/promises';import Redis from 'ioredis';
const redis = new Redis(process.env.CACHE_URI);
// Stop the loop when Aspire stops the processconst controller = new AbortController();for (const signal of ['SIGINT', 'SIGTERM']) { process.once(signal, () => controller.abort());}
async function doWork(redis, signal) { // Replace with your background work.}
try { while (!controller.signal.aborted) { console.log(`Processing data at: ${new Date().toISOString()}`);
await doWork(redis, controller.signal);
await delay(10_000, undefined, { signal: controller.signal }); }} catch (error) { if (error.name !== 'AbortError') { throw error; }} finally { await redis.quit();}Adding to the AppHost
Section titled “Adding to the AppHost”The following model also provides a PostgreSQL database. Install its hosting integration in the AppHost before using this example:
aspire add postgresqlLa CLI Aspire è interattiva; seleziona il risultato corretto quando richiesto:
Select an integration to add:
> postgresql (Aspire.Hosting.PostgreSQL)> Other results listed as selectable options...aspire add postgresqlLa CLI Aspire è interattiva; seleziona il risultato corretto quando richiesto:
Select an integration to add:
> postgresql (Aspire.Hosting.PostgreSQL)> Other results listed as selectable options...#:package Aspire.Hosting.PostgreSQL@*<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="*" />The database reference supplies connection information, but the worker above doesn’t yet use it. To add database operations, configure a client as described in Connect to PostgreSQL.
Add the Worker Service project to your AppHost:
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const cache = await builder.addRedis('cache');const postgres = await builder.addPostgres('postgres');const db = await postgres.addDatabase('app-db');
const worker = await builder.addProject( 'data-worker', '../DataWorker/DataWorker.csproj');await worker.withReference(cache);await worker.withReference(db);await worker.waitFor(db);
await builder.build().run();var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache");var db = builder.AddPostgres("postgres").AddDatabase("app-db");
var worker = builder.AddProject<Projects.DataWorker>("data-worker") .WithReference(cache) .WithReference(db) .WaitFor(db);
builder.Build().Run();Add the Node.js worker to your AppHost:
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const cache = await builder.addRedis('cache');const postgres = await builder.addPostgres('postgres');const db = await postgres.addDatabase('app-db');
const worker = await builder.addNodeApp( 'data-worker', '../data-worker', 'worker.js');await worker.withReference(cache);await worker.withReference(db);await worker.waitFor(db);
await builder.build().run();var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache");var db = builder.AddPostgres("postgres").AddDatabase("app-db");
var worker = builder.AddNodeApp("data-worker", "../data-worker", "worker.js") .WithReference(cache) .WithReference(db) .WaitFor(db);
builder.Build().Run();The worker receives the database connection properties with an APP_DB_ prefix, such as APP_DB_URI. Aspire replaces the dash in the resource name with an underscore in these variable names. For more information, see Resource naming conventions.
For more information on the Redis and PostgreSQL hosting integrations, see Redis integration and PostgreSQL integration.
Explicit start and console interaction
Section titled “Explicit start and console interaction”By default, all resources start automatically when the AppHost runs. For console apps that should only run on demand, use WithExplicitStart.
In your AppHost, add the console app project with WithExplicitStart:
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const tool = await builder.addProject( 'tool', '../MyInteractiveTool/MyInteractiveTool.csproj');await tool.withExplicitStart();
await builder.build().run();var builder = DistributedApplication.CreateBuilder(args);
var tool = builder.AddProject<Projects.MyInteractiveTool>("tool") .WithExplicitStart();
builder.Build().Run();In your AppHost, add the Node.js app with WithExplicitStart:
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const tool = await builder.addNodeApp('tool', '../tool', 'index.js');await tool.withExplicitStart();
await builder.build().run();var builder = DistributedApplication.CreateBuilder(args);
var tool = builder.AddNodeApp("tool", "../tool", "index.js") .WithExplicitStart();
builder.Build().Run();With WithExplicitStart, the resource appears in the Aspire dashboard but doesn’t start automatically. You can start it manually from the dashboard or programmatically.
Aspire builds that include the experimental WithTerminal API support interactive terminal user interfaces (TUIs) and shell-based experiences that need a live, bidirectional session, separately from the dashboard’s console log viewer.
Viewing console output
Section titled “Viewing console output”Aspire captures stdout and stderr from all managed resources and displays them in the dashboard’s Console logs page. Console apps output is fully visible, including colored text (ANSI escape codes are rendered).
For more information, see Console logs page.
For structured logging in console apps, use ILogger instead of Console.Write. The following fragment assumes an existing logger and item identifier:
// Prefer structured logginglogger.LogInformation("Processing item {ItemId}", itemId);
// Instead ofConsole.WriteLine($"Processing item {itemId}");With console logging and OpenTelemetry logging/export configured, such as through the service defaults setup above, logs appear in both the Console logs and Structured logs views of the dashboard. Calling ILogger alone doesn’t export structured logs to the dashboard.
Output from console.log and console.error appears in the Console logs view. To also see logs in the Structured logs view, emit them through the OpenTelemetry Logs API. The following fragment assumes the OpenTelemetry Node.js SDK is configured with an OTLP log exporter, and an existing item identifier:
import { logs, SeverityNumber } from '@opentelemetry/api-logs';
const logger = logs.getLogger('processor');
// Prefer structured logginglogger.emit({ severityNumber: SeverityNumber.INFO, severityText: 'Information', body: 'Processing item', attributes: { itemId },});
// Instead ofconsole.log(`Processing item ${itemId}`);Logs emitted without a configured log exporter aren’t sent to the dashboard. For more information on setting up the SDK, see Add telemetry configuration.
Using connection strings without service defaults
Section titled “Using connection strings without service defaults”Apps that don’t use the service defaults project, such as a minimal .NET console app without a host or any Node.js app, can read connection information directly from environment variables.
For the configuration API example, install the environment variables configuration provider in your console project:
dotnet add package Microsoft.Extensions.Configuration.EnvironmentVariables#:package Microsoft.Extensions.Configuration.EnvironmentVariables@*<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="*" />// For .NET apps, read from configurationusing Microsoft.Extensions.Configuration;
var config = new ConfigurationBuilder() .AddEnvironmentVariables() .Build();
var redisConnection = config.GetConnectionString("cache");// Or equivalently:var redisConnection2 = Environment.GetEnvironmentVariable("ConnectionStrings__cache");Read the values from process.env. Node.js doesn’t map the : and __ separators, so use the exact environment variable name:
// Prefer the connection properties for the resourceconst redisUri = process.env.CACHE_URI;
// The full connection string is also available:const redisConnection = process.env.ConnectionStrings__cache;For Redis, ConnectionStrings__cache uses the StackExchange.Redis connection string format rather than a URI, so pass CACHE_URI to Node.js clients such as ioredis. For the properties each resource provides, see that integration’s connect page, such as Connect to Redis.