MCPInstall

csharp-wolverinefx

Build .NET applications with WolverineFX for messaging, HTTP services, and event sourcing. Use when implementing command handlers, message handlers, HTTP endpoints with WolverineFx.HTTP, transactional outbox patterns, event sourcing with Marten, CQRS architectures, cascading messages, batch message processing, or configuring transports like RabbitMQ, Azure Service Bus, or Amazon SQS.

Installs
570
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/wshaddix/dotnet-skills --skill csharp-wolverinefx
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads csharp-wolverinefx automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

WolverineFX for .NET

When to Use This Skill

Use this skill when:

  • Building message handlers or command handlers with Wolverine
  • Creating HTTP endpoints with WolverineFx.HTTP (alternative to Minimal API/MVC)
  • Implementing event sourcing with Marten and Wolverine
  • Setting up transactional outbox pattern for reliable messaging
  • Configuring message transports (RabbitMQ, Azure Service Bus, Amazon SQS, TCP)
  • Implementing CQRS with event sourcing
  • Processing messages in batches
  • Using cascading messages for testable, pure function handlers
  • Configuring error handling and retry policies
  • Pre-generating code for optimized cold starts

Related Skills

  • efcore-patterns - Entity Framework Core patterns for data access
  • csharp-coding-standards - Modern C# patterns (records, pattern matching)
  • http-client-resilience - Polly resilience patterns (complementary)
  • background-services - Hosted services and background job patterns
  • aspire-configuration - .NET Aspire orchestration

Core Principles

  1. Low Ceremony Code - Pure functions, method injection, minimal boilerplate
  2. Cascading Messages - Return messages from handlers instead of injecting IMessageBus
  3. Transactional Outbox - Guaranteed message delivery with database transactions
  4. Code Generation - Runtime or pre-generated code for optimal performance
  5. Vertical Slice Architecture - Organize code by feature, not technical layers
  6. Pure Functions for Business Logic - Isolate infrastructure from business logic

Required NuGet Packages

Core Messaging

<PackageReference Include="Wolverine" />
<PackageReference Include="WolverineFx.Http" />

Persistence Integration

<PackageReference Include="WolverineFx.Marten" />

Transports

<PackageReference Include="WolverineFx.RabbitMQ" />
<PackageReference Include="WolverineFx.AzureServiceBus" />
<PackageReference Include="WolverineFx.Kafka" />
<PackageReference Include="WolverineFx.AmazonSQS" />

Basic Setup

Program.cs (ASP.NET Core)

using JasperFx;
using Wolverine;
using Wolverine.Http;

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseWolverine(opts =>
{
    opts.Policies.AutoApplyTransactions();
    opts.Policies.UseDurableLocalQueues();
});

builder.Services.AddWolverineHttp();

var app = builder.Build();

app.MapWolverineEndpoints();

return await app.RunJasperFxCommands(args);

Message Handlers

Simple Message Handler

public record DebitAccount(long AccountId, decimal Amount);

public static class DebitAccountHandler
{
    public static void Handle(DebitAccount command, IAccountRepository repository)
    {
        repository.Debit(command.AccountId, command.Amount);
    }
}

Handler with Cascading Messages

public record CreateOrder(Guid OrderId, string[] Items);
public record OrderCreated(Guid OrderId);

public static class CreateOrderHandler
{
    public static (OrderCreated, ShipOrder) Handle(
        CreateOrder command,
        IDocumentSession session)
    {
        var order = new Order { Id = command.OrderId, Items = command.Items };
        session.Store(order);
        
        return (
            new OrderCreated(command.OrderId),
            new ShipOrder(command.OrderId)
        );
    }
}

Using OutgoingMessages for Multiple Messages

public static OutgoingMessages Handle(ProcessOrder command)
{
    var messages = new OutgoingMessages
    {
        new OrderProcessed(command.OrderId),
        new SendEmail(command.CustomerEmail, "Order processed"),
        new UpdateInventory(command.Items)
    };
    
    messages.Delay(new CleanupOrder(command.OrderId), 5.Minutes());
    
    return messages;
}

HTTP Endpoints (WolverineFx.HTTP)

Basic GET Endpoint

[WolverineGet("/users/{id}")]
public static Task<User?> GetUser(int id, IQuerySession session)
    => session.LoadAsync<User>(id);

POST with Message Publishing

[WolverinePost("/orders")]
public static async Task<IResult> CreateOrder(
    CreateOrderRequest request,
    IDocumentSession session,
    IMessageBus bus)
{
    var order = new Order { Id = Guid.NewGuid(), Items = request.Items };
    session.Store(order);
    
    await bus.PublishAsync(new OrderCreated(order.Id));
    
    return Results.Created($"/orders/{order.Id}", order);
}

Compound Handler (Load/Validate/Handle)

public static class UpdateOrderEndpoint
{
    public static async Task<(Order?, IResult)> LoadAsync(
        UpdateOrder command,
        IDocumentSession session)
    {
        var order = await session.LoadAsync<Order>(command.OrderId);
        return order != null
            ? (order, new WolverineContinue())
            : (order, Results.NotFound());
    }

    [WolverinePut("/orders")]
    public static void Handle(UpdateOrder command, Order order, IDocumentSession session)
    {
        order.Items = command.Items;
        session.Store(order);
    }
}

Event Sourcing with Marten

Aggregate Handler Workflow

public class Order
{
    public Guid Id { get; set; }
    public int Version { get; set; }
    public Dictionary<string, Item> Items { get; set; } = new();
    public DateTimeOffset? Shipped { get; private set; }

    public void Apply(ItemReady ready) => Items[ready.Name].Ready = true;
    public void Apply(IEvent<OrderShipped> shipped) => Shipped = shipped.Timestamp;

    public bool IsReadyToShip() => Shipped == null && Items.Values.All(x => x.Ready);
}

public record MarkItemReady(Guid OrderId, string ItemName, int Version);

[AggregateHandler]
public static IEnumerable<object> Handle(MarkItemReady command, Order order)
{
    if (order.Items.TryGetValue(command.ItemName, out var item))
    {
        item.Ready = true;
        yield return new ItemReady(command.ItemName);
    }
    
    if (order.IsReadyToShip())
    {
        yield return new OrderReady();
    }
}

Read Aggregate (Read-Only)

[WolverineGet("/orders/{id}")]
public static Order GetOrder([ReadAggregate] Order order) => order;

Write Aggregate with Validation

public static IEnumerable<object> Handle(
    MarkItemReady command,
    [WriteAggregate(Required = true, OnMissing = OnMissing.ProblemDetailsWith404)] Order order)
{
    order.Items[command.ItemName].Ready = true;
    yield return new ItemReady(command.ItemName);
}

Returning Updated Aggregate

[AggregateHandler]
public static (UpdatedAggregate, Events) Handle(
    MarkItemReady command,
    Order order)
{
    var events = new Events();
    events.Add(new ItemReady(command.ItemName));
    return (new UpdatedAggregate(), events);
}

Transactional Outbox

Marten Integration

builder.Services.AddMarten(opts =>
{
    opts.Connection(connectionString);
}).IntegrateWithWolverine();

builder.Host.UseWolverine(opts =>
{
    opts.Policies.AutoApplyTransactions();
});

Using Outbox in Controllers

[HttpPost("/orders")]
public async Task Post(
    [FromBody] CreateOrder command,
    [FromServices] IDocumentSession session,
    [FromServices] IMartenOutbox outbox)
{
    outbox.Enroll(session);
    
    var order = new Order { Id = command.OrderId };
    session.Store(order);
    
    await outbox.PublishAsync(new OrderCreated(command.OrderId));
    
    await session.SaveChangesAsync();
}

Transport Configuration

RabbitMQ

builder.Host.UseWolverine(opts =>
{
    opts.UseRabbitMq("host=localhost")
        .AutoProvision()
        .AutoPurgeOnStartup();
    
    opts.PublishAllMessages()
        .ToRabbitExchange("wolverine.events", exchange =>
        {
            exchange.ExchangeType = ExchangeType.Topic;
        });
});

Azure Service Bus

builder.Host.UseWolverine(opts =>
{
    opts.UseAzureServiceBus(asbConnectionString)
        .AutoProvision()
        .ConfigureQueue(q => q.MaxDeliveryCount = 5);
});

Amazon SQS

builder.Host.UseWolverine(opts =>
{
    opts.UseAmazonSqs(sqsConfig)
        .AutoProvision();
});

Batch Message Processing

Configure Batching

builder.Host.UseWolverine(opts =>
{
    opts.BatchMessagesOf<SubTaskCompleted>(batching =>
    {
        batching.BatchSize = 500;
        batching.TriggerTime = 1.Seconds();
    });
});

Batch Handler

public static class ItemBatchHandler
{
    public static void Handle(Item[] items, IRepository repository)
    {
        foreach (var item in items)
        {
            repository.Process(item);
        }
    }
}

Custom Batching Strategy

public record SubTaskCompleted(string TaskId, string SubTaskId);
public record SubTaskBatch(string TaskId, string[] SubTaskIds);

public class SubTaskBatcher : IMessageBatcher
{
    public IEnumerable<Envelope> Group(IReadOnlyList<Envelope> envelopes)
    {
        var groups = envelopes
            .GroupBy(x => x.Message!.As<SubTaskCompleted>().TaskId);
        
        foreach (var group in groups)
        {
            var subTaskIds = group
                .Select(x => x.Message)
                .OfType<SubTaskCompleted>()
                .Select(x => x.SubTaskId)
                .ToArray();
            
            yield return new Envelope(
                new SubTaskBatch(group.Key, subTaskIds),
                group);
        }
    }

    public Type BatchMessageType => typeof(SubTaskBatch);
}

Error Handling

Retry Policies

builder.Host.UseWolverine(opts =>
{
    opts.Policies.OnException<SqlException>()
        .RetryWithCooldown(50.Milliseconds(), 100.Milliseconds(), 250.Milliseconds());
    
    opts.Policies.OnException<TimeoutException>()
        .RetryTimes(3);
    
    opts.Policies.OnException<InvalidOperationException>()
        .Requeue();
    
    opts.Policies.OnAnyException()
        .MoveToErrorQueue();
});

Circuit Breaker

opts.ListenToRabbitQueue("incoming")
    .CircuitBreaker(cb =>
    {
        cb.PauseTime = 1.Minutes();
        cb.FailurePercentageThreshold = 50;
        cb.MinimumThreshold = 10;
    });

Scheduled Messages

Delayed Messages

public static IEnumerable<object> Handle(OrderCreated command)
{
    yield return new ProcessPayment(command.OrderId);
    yield return new ShipOrder(command.OrderId)
        .DelayedFor(30.Minutes());
}

Scheduled at Specific Time

yield return new GenerateReport()
    .ScheduledAt(DateTime.Today.AddDays(1));

Using OutgoingMessages

var messages = new OutgoingMessages();
messages.Delay(new Reminder(orderId), TimeSpan.FromHours(24));
messages.Schedule(new MonthlyReport(), DateTime.Today.AddMonths(1));

Request/Reply Pattern

Sending with Response Request

public async Task<OrderStatus> GetOrderStatus(IMessageBus bus, Guid orderId)
{
    return await bus.InvokeAsync<OrderStatus>(new GetOrder(orderId));
}

Handler with Response

public record GetOrder(Guid OrderId);
public record OrderStatus(Guid OrderId, string Status);

public static class GetOrderHandler
{
    public static OrderStatus Handle(GetOrder query, IQuerySession session)
    {
        var order = session.Load<Order>(query.OrderId);
        return new OrderStatus(query.OrderId, order?.Status ?? "NotFound");
    }
}

Middleware

Custom Middleware

public class LoggingMiddleware
{
    public void Before(Envelope envelope, ILogger logger)
    {
        logger.LogInformation("Processing {MessageType}", envelope.MessageType);
    }

    public void After(Envelope envelope, ILogger logger)
    {
        logger.LogInformation("Completed {MessageType}", envelope.MessageType);
    }
}

builder.Host.UseWolverine(opts =>
{
    opts.Handlers.AddMiddleware<LoggingMiddleware>();
});

Transaction Middleware

builder.Host.UseWolverine(opts =>
{
    opts.Policies.AutoApplyTransactions();
});

Code Generation

Pre-Generate Types

dotnet run -- codegen write

Generated code appears in ./Internal/Generated/WolverineHandlers/

Configure for AOT/Trimming

builder.Host.UseWolverine(opts =>
{
    opts.CodeGeneration.TypeLoadMode = TypeLoadMode.Auto;
});

Multi-Tenancy

Conjoined Tenancy

builder.Host.UseWolverine(opts =>
{
    opts.Policies.ConjoinedTenancy(x =>
    {
        x.TenantIdStyle = TenantIdStyle.PerRequest;
    });
});

Publishing to Specific Tenant

await bus.PublishAsync(new OrderCreated(orderId), 
    new DeliveryOptions { TenantId = "tenant-1" });

Marten Side Effects

public static IMartenOp Handle(CreateTodo command)
{
    var todo = new Todo { Name = command.Name };
    return MartenOps.Store(todo);
}

public static IMartenOp Handle(DeleteTodo command)
{
    return MartenOps.Delete<Todo>(command.Id);
}

Ancillary Stores (Modular Monolith)

public interface IPlayerStore : IDocumentStore;

builder.Host.UseWolverine(opts =>
{
    opts.Services.AddMartenStore<IPlayerStore>(m =>
    {
        m.Connection(connectionString);
        m.DatabaseSchemaName = "players";
    })
    .IntegrateWithWolverine();
});

[MartenStore(typeof(IPlayerStore))]
public static class PlayerMessageHandler
{
    public static IMartenOp Handle(PlayerMessage message)
    {
        return MartenOps.Store(new Player { Id = message.Id });
    }
}

Command Line Tools

Available Commands

dotnet run -- help           # List all commands
dotnet run -- describe       # Application description
dotnet run -- resources      # Resource management
dotnet run -- storage        # Message storage admin
dotnet run -- codegen write  # Pre-generate code

Best Practices

  1. Prefer pure functions - Business logic should be testable without mocks
  2. Use cascading messages - Return messages instead of injecting IMessageBus
  3. Keep call stacks short - Avoid deep service hierarchies
  4. Pre-generate code - Optimize cold starts in production
  5. Use compound handlers - Separate load/validate/handle logic
  6. Configure error handling - Let Wolverine handle retries and errors
  7. Use transactional outbox - Guarantee message delivery
  8. Batch when appropriate - Improve throughput for high-volume messages

Anti-Patterns to Avoid

  1. Injecting IMessageBus deep in call stack - Makes workflow hard to reason about
  2. Over-using constructor injection - Prefer method injection
  3. Ignoring transactional outbox - Can lose messages on failure
  4. Not pre-generating code - Slow cold starts in production
  5. Mixing too many concerns in one handler - Keep handlers focused
  6. Not configuring error handling - Messages end up in error queue unexpectedly

FAQ

What does the csharp-wolverinefx skill do?

Build .NET applications with WolverineFX for messaging, HTTP services, and event sourcing. Use when implementing command handlers, message handlers, HTTP endpoints with WolverineFx.HTTP, transactional outbox patterns, event sourcing with Marten, CQRS architectures, cascading messages, batch message processing, or configuring transports like RabbitMQ, Azure Service Bus, or Amazon SQS.

How do I install csharp-wolverinefx?

Run npx skills add https://github.com/wshaddix/dotnet-skills --skill csharp-wolverinefx in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/csharp-wolverinefx/ (personal) or .claude/skills/csharp-wolverinefx/ (project).

Which AI agents can use csharp-wolverinefx?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is csharp-wolverinefx safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains csharp-wolverinefx?

It is published by wshaddix in wshaddix/dotnet-skills. It has 570 recorded installs.

More from wshaddix/dotnet-skills

View all →

dotnet-xunit

wshaddix/dotnet-skills

Writing xUnit tests. v3 Fact/Theory, fixtures, parallelism, IAsyncLifetime, v2 compatibility.

Backend & APIs613 installs

dotnet-testing-strategy

wshaddix/dotnet-skills

Deciding how to test .NET code. Unit vs integration vs E2E decision tree, test doubles.

Testing & QA610 installs

modern-csharp-coding-standards

wshaddix/dotnet-skills

Write modern, high-performance C code using records, pattern matching, value objects, async/await, Span /Memory , and best-practice API design patterns. Emphasizes functional-style programming with C 12+ features. Use when writing new C code or refactoring existing code, designing public APIs for libraries or services, optimizing performance-critical code paths, or building async/await-heavy applications.

testcontainers

wshaddix/dotnet-skills

Patterns for using Testcontainers in .NET integration tests to spin up real dependencies like databases and message queues. Use when writing integration tests that require real databases, testing with message brokers like RabbitMQ or Kafka, or isolating test dependencies with Docker containers.

Testing & QA565 installs

Dependency injection patterns and best practices using Microsoft.Extensions.DependencyInjection for .NET applications. Use when configuring DI containers in .NET, choosing between service lifetimes (Singleton, Scoped, Transient), or implementing decorator patterns and service interception.

Security549 installs

logging-observability

wshaddix/dotnet-skills

Production-grade logging and observability patterns for ASP.NET Core Razor Pages. Covers structured logging with Serilog, correlation IDs, health checks, request logging, OpenTelemetry integration, and diagnostic best practices. Use when setting up structured logging in ASP.NET Core applications, implementing distributed tracing with OpenTelemetry, or configuring health checks and observability.

DevOps & Cloud542 installs

Popular backend & apis skills

View category →

find-skills

vercel-labs/skills

Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.

OfficialBackend & APIs4.1M installs

entra-app-registration

microsoft/azure-skills

Guides Microsoft Entra ID app registration, OAuth 2.0 authentication, and MSAL integration. USE FOR: create app registration, register Azure AD app, configure OAuth, set up authentication, add API permissions, generate service principal, MSAL example, console app auth, Entra ID setup, Azure AD authentication. DO NOT USE FOR: Key Vault secrets (use azure-keyvault-expiration-audit), general Azure resource security guidance.

OfficialBackend & APIs566.7K installs

This skill should be used when the user wants to "write agent code", "build an agent with ADK", "add a tool", "create a callback", "define an agent", "use state management" — in a project that needs ADK (Agent Development Kit) API patterns and code examples. It provides a quick reference for agent types, tool definitions, orchestration patterns, callbacks, state management, the graph Workflow API, and reference recipes to study. Do NOT use for scaffolding (use google-agents-cli-scaffold) or deployment (use google-agents-cli-deploy).

Backend & APIs514.8K installs

embedded-captions

heygen-com/hyperframes

Add captions or subtitles to an existing single-subject talking-head video without editing the footage. Use for plain verbatim captions, cinematic captions embedded behind the subject, VFX captions, “炸/特效/酷炫字幕,” or a named identity from the 35-style catalog. Route by visual identity, not by backend engine. The quiet anchor rail is the default; embed every word only when the user explicitly wants a fully cinematic treatment. The workflow runs locally end to end, including transcription and subject matting; split multi-shot footage before applying it.

Backend & APIs453.3K installs

entra-agent-id

microsoft/azure-skills

Provision Microsoft Entra Agent Identity Blueprints, BlueprintPrincipals, and per-instance Agent Identities via Microsoft Graph, and configure OAuth 2.0 token exchange (fmipath, OBO, cross-tenant) including the Microsoft Entra SDK for AgentID sidecar. USE FOR: Agent Identity Blueprint, BlueprintPrincipal, agent OAuth, fmipath token exchange, agent OBO, Workload Identity Federation for agents, polyglot agent auth, Microsoft.Identity.Web.AgentIdentities. DO NOT USE FOR: standard Entra app registration (use entra-app-registration), Microsoft Foundry agent authoring (use microsoft-foundry).

OfficialBackend & APIs336.6K installs

neon

neondatabase/agent-skills

Overview of Neon, a complete set of cloud backend primitives around Lakebase Postgres: Auth, Object Storage, Functions, the AI Gateway, and Realtime. Start here to choose Neon for undecided login, files, APIs, and LLM calls, set up the CLI or MCP server, and follow the branch-first workflow. Use when building an app or backend on Neon, or when "Neon" or "Lakebase Postgres" is mentioned. Child skill neon-postgres wins for an existing DATABASEURL, SQL, schema, inspect, or search. Child skill neon-auth wins for login, users, sessions, identity routing, and Managed Better Auth setup. Child skill neon-realtime wins for live queries in reactive apps and agents. Also use for object storage, S3, buckets, serverless functions, function triggers, cron, AI gateway, LLM calls, realtime, logs, Loki, Grafana, observability, postgres, database, backend, Claimable Neon, neon.new, or a no-signup database.

Backend & APIs237.1K installs