Claude-skill-registry add-aspire-worker-project
Create a new .NET Aspire worker project with Kafka, RavenDB, and MinIO integration (project)
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/add-aspire-worker-project" ~/.claude/skills/majiayu000-claude-skill-registry-add-aspire-worker-project && rm -rf "$T"
manifest:
skills/data/add-aspire-worker-project/SKILL.mdsafety · automated scan (low risk)
This is a pattern-based risk scan, not a security review. Our crawler flagged:
- references API keys
Always read a skill's source content before installing. Patterns alone don't mean the skill is malicious — but they warrant attention.
source content
Add Aspire Worker Project Skill
Create a new .NET Aspire worker project with messaging, database, and storage integration for NovaTune.
Project Context
- Workers location:
src/NovaTuneApp/NovaTuneApp.Workers.{Name}/ - AppHost:
src/NovaTuneApp/NovaTuneApp.AppHost/ - Solution:
src/NovaTuneApp/NovaTuneApp.sln - Naming convention:
NovaTuneApp.Workers.{PurposeName}
Steps
1. Create Project Directory
mkdir -p src/NovaTuneApp/NovaTuneApp.Workers.Lifecycle
2. Create Project File
Location:
src/NovaTuneApp/NovaTuneApp.Workers.Lifecycle/NovaTuneApp.Workers.Lifecycle.csproj
<Project Sdk="Microsoft.NET.Sdk.Worker"> <PropertyGroup> <TargetFramework>net9.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <UserSecretsId>dotnet-NovaTuneApp.Workers.Lifecycle</UserSecretsId> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\NovaTuneApp.ServiceDefaults\NovaTuneApp.ServiceDefaults.csproj" /> <ProjectReference Include="..\NovaTuneApp.ApiService\NovaTuneApp.ApiService.csproj" /> </ItemGroup> <ItemGroup> <!-- KafkaFlow for Kafka/Redpanda messaging --> <PackageReference Include="KafkaFlow" Version="3.1.0" /> <PackageReference Include="KafkaFlow.Microsoft.DependencyInjection" Version="3.1.0" /> <PackageReference Include="KafkaFlow.Serializer.JsonCore" Version="3.1.0" /> <PackageReference Include="KafkaFlow.Admin" Version="3.1.0" /> <!-- RavenDB --> <PackageReference Include="RavenDB.Client" Version="7.0.2" /> <!-- MinIO --> <PackageReference Include="Minio" Version="6.0.3" /> <!-- Health Checks --> <PackageReference Include="AspNetCore.HealthChecks.Kafka" Version="9.0.0" /> <PackageReference Include="AspNetCore.HealthChecks.RavenDB" Version="9.0.0" /> <!-- Serilog --> <PackageReference Include="Serilog.AspNetCore" Version="9.0.0" /> <PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" /> <PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" /> </ItemGroup> </Project>
3. Create Program.cs
Location:
src/NovaTuneApp/NovaTuneApp.Workers.Lifecycle/Program.cs
using Confluent.Kafka; using KafkaFlow; using KafkaFlow.Serializer; using Microsoft.Extensions.Options; using Minio; using NovaTuneApp.ApiService.Infrastructure.Configuration; using NovaTuneApp.Workers.Lifecycle.Handlers; using NovaTuneApp.Workers.Lifecycle.Services; using Raven.Client.Documents; using Serilog; using Serilog.Events; using Serilog.Formatting.Compact; // Bootstrap logging Log.Logger = new LoggerConfiguration() .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.Console(new RenderedCompactJsonFormatter()) .CreateBootstrapLogger(); try { var builder = Host.CreateApplicationBuilder(args); // Add service defaults (OpenTelemetry, health checks, etc.) builder.AddServiceDefaults(); // Serilog builder.Services.AddSerilog((services, configuration) => configuration .ReadFrom.Configuration(builder.Configuration) .ReadFrom.Services(services) .MinimumLevel.Information() .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("KafkaFlow", LogEventLevel.Information) .Enrich.FromLogContext() .Enrich.WithEnvironmentName() .Enrich.WithMachineName() .WriteTo.Console(new RenderedCompactJsonFormatter())); // Configuration builder.Services.Configure<NovaTuneOptions>( builder.Configuration.GetSection(NovaTuneOptions.SectionName)); builder.Services.Configure<LifecycleOptions>( builder.Configuration.GetSection(LifecycleOptions.SectionName)); var topicPrefix = builder.Configuration["NovaTune:TopicPrefix"] ?? "dev"; var bootstrapServers = builder.Configuration.GetConnectionString("messaging") ?? "localhost:9092"; // RavenDB var ravenConnectionString = builder.Configuration.GetConnectionString("novatune"); string ravenDbUrl; string ravenDbDatabase; if (ravenConnectionString != null && ravenConnectionString.Contains(';')) { var parts = ravenConnectionString.Split(';') .Select(p => p.Split('=', 2)) .Where(p => p.Length == 2) .ToDictionary(p => p[0], p => p[1]); ravenDbUrl = parts.GetValueOrDefault("URL") ?? "http://localhost:8080"; ravenDbDatabase = parts.GetValueOrDefault("Database") ?? "NovaTune"; } else { ravenDbUrl = ravenConnectionString ?? "http://localhost:8080"; ravenDbDatabase = builder.Configuration["RavenDb:Database"] ?? "NovaTune"; } builder.Services.AddSingleton<IDocumentStore>(sp => { var store = new DocumentStore { Urls = [ravenDbUrl], Database = ravenDbDatabase }; store.Initialize(); return store; }); // MinIO var minioEndpoint = builder.Configuration.GetConnectionString("storage") ?? "http://localhost:9000"; var minioAccessKey = builder.Configuration["MinIO:AccessKey"] ?? "minioadmin"; var minioSecretKey = builder.Configuration["MinIO:SecretKey"] ?? "minioadmin"; var minioHost = minioEndpoint.Replace("http://", "").Replace("https://", ""); var useSSL = minioEndpoint.StartsWith("https://"); builder.Services.AddSingleton<IMinioClient>(_ => new MinioClient() .WithEndpoint(minioHost) .WithCredentials(minioAccessKey, minioSecretKey) .WithSSL(useSSL) .Build()); // Health Checks builder.Services.AddHealthChecks() .AddRavenDB( setup => setup.Urls = [ravenDbUrl], name: "ravendb", timeout: TimeSpan.FromSeconds(5)) .AddKafka( new ProducerConfig { BootstrapServers = bootstrapServers }, name: "kafka", timeout: TimeSpan.FromSeconds(5)) .AddUrlGroup( new Uri($"{minioEndpoint}/minio/health/live"), name: "minio", timeout: TimeSpan.FromSeconds(5)); // KafkaFlow Consumer (track deletions) builder.Services.AddKafka(kafka => kafka .UseMicrosoftLog() .AddCluster(cluster => { cluster.WithBrokers([bootstrapServers]); cluster.AddConsumer(consumer => consumer .Topic($"{topicPrefix}-track-deletions") .WithGroupId($"{topicPrefix}-lifecycle-worker") .WithBufferSize(100) .WithWorkersCount(2) .WithAutoOffsetReset(KafkaFlow.AutoOffsetReset.Earliest) .AddMiddlewares(m => m .AddDeserializer<JsonCoreDeserializer>() .AddTypedHandlers(h => h.AddHandler<TrackDeletedHandler>()) ) ); }) ); // Services builder.Services.AddTransient<TrackDeletedHandler>(); builder.Services.AddScoped<IPhysicalDeletionService, PhysicalDeletionService>(); // Background Services builder.Services.AddHostedService<KafkaFlowHostedService>(); builder.Services.AddHostedService<PhysicalDeletionBackgroundService>(); var host = builder.Build(); await host.RunAsync(); } catch (Exception ex) { Log.Fatal(ex, "Lifecycle worker terminated unexpectedly"); } finally { Log.CloseAndFlush(); }
4. Add to Solution
cd src/NovaTuneApp dotnet sln add NovaTuneApp.Workers.Lifecycle/NovaTuneApp.Workers.Lifecycle.csproj
5. Register in AppHost
Location:
src/NovaTuneApp/NovaTuneApp.AppHost/AppHost.cs
Add project reference to
NovaTuneApp.AppHost.csproj:
<ProjectReference Include="..\NovaTuneApp.Workers.Lifecycle\NovaTuneApp.Workers.Lifecycle.csproj"/>
Add to AppHost.cs (in the non-testing block):
// Lifecycle Worker - handles physical deletion of soft-deleted tracks builder.AddProject<Projects.NovaTuneApp_Workers_Lifecycle>("lifecycle-worker") .WithReference(messaging) .WaitFor(messaging) .WithReference(database) .WaitFor(database) .WithReference(storage.GetEndpoint("api")) .WaitFor(storage) .WithEnvironment("NovaTune__TopicPrefix", "dev");
6. Create Configuration Class
Location:
src/NovaTuneApp/NovaTuneApp.Workers.Lifecycle/Configuration/LifecycleOptions.cs
namespace NovaTuneApp.Workers.Lifecycle.Configuration; public class LifecycleOptions { public const string SectionName = "Lifecycle"; /// <summary> /// Interval between physical deletion polling. /// Default: 5 minutes. /// </summary> public TimeSpan PollingInterval { get; set; } = TimeSpan.FromMinutes(5); /// <summary> /// Maximum tracks to process per cycle. /// Default: 50. /// </summary> public int BatchSize { get; set; } = 50; /// <summary> /// Whether physical deletion is enabled. /// Default: true. /// </summary> public bool Enabled { get; set; } = true; }
7. Create appsettings.json
Location:
src/NovaTuneApp/NovaTuneApp.Workers.Lifecycle/appsettings.json
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.Hosting.Lifetime": "Information" } }, "NovaTune": { "TopicPrefix": "dev" }, "Lifecycle": { "PollingInterval": "00:05:00", "BatchSize": 50, "Enabled": true } }
Project Structure
NovaTuneApp.Workers.Lifecycle/ ├── Configuration/ │ └── LifecycleOptions.cs ├── Handlers/ │ └── TrackDeletedHandler.cs ├── Services/ │ ├── IPhysicalDeletionService.cs │ └── PhysicalDeletionService.cs ├── Program.cs ├── appsettings.json ├── appsettings.Development.json └── NovaTuneApp.Workers.Lifecycle.csproj
Dependencies Pattern
Workers typically depend on:
| Dependency | Purpose |
|---|---|
| OpenTelemetry, health checks, service discovery |
| Shared models, configuration, services |
| Kafka/Redpanda messaging |
| Document database |
| Object storage |
| Structured logging |
Verification
After creating the project:
# Build solution dotnet build src/NovaTuneApp/NovaTuneApp.sln # Run the worker standalone dotnet run --project src/NovaTuneApp/NovaTuneApp.Workers.Lifecycle # Run with Aspire orchestration dotnet run --project src/NovaTuneApp/NovaTuneApp.AppHost