# Getting Started with .NET Aspire (/integrations/aspire/tutorials/getting-started)



The KubeMQ .NET Aspire integration ships two NuGet packages that follow the standard Aspire two-package model: `KubeMQ.Aspire.Hosting` provisions a KubeMQ container as a resource in your AppHost, and `KubeMQ.Aspire.Client` wires an `IKubeMQClient` into a service project with health checks, OpenTelemetry, and keyed dependency injection. This guide takes you from an empty Aspire solution to publishing your first event and queue message.

## Setup walkthrough [#setup-walkthrough]

<Steps>
  <Step>
    ### Prerequisites [#prerequisites]

    You need the following installed before you begin:

    | Requirement          | Version                                    |
    | -------------------- | ------------------------------------------ |
    | .NET SDK             | 8.0 or 9.0                                 |
    | .NET Aspire workload | 9.0+                                       |
    | Docker               | Running (for local container provisioning) |
    | KubeMQ license key   | Set via `WithLicenseKey()`                 |

    Aspire provisions the KubeMQ broker as a local Docker container, so Docker must be running. The license key is supplied at deploy time as a secret parameter — you will add it in the AppHost step.
  </Step>

  <Step>
    ### Install the Packages [#install-the-packages]

    Add the hosting package to your **AppHost** project:

    ```bash
    dotnet add package KubeMQ.Aspire.Hosting
    ```

    Add the client package to each **service** project that talks to KubeMQ:

    ```bash
    dotnet add package KubeMQ.Aspire.Client
    ```

    These packages build on `Aspire.Hosting` `9.0.0` and the `KubeMQ.SDK.CSharp` `3.0.1` client. If your solution uses central package management (`Directory.Packages.props`), pin those versions there.
  </Step>

  <Step>
    ### Configure the AppHost [#configure-the-apphost]

    In the AppHost `Program.cs`, declare the license key as a secret parameter, provision the broker, and reference it from your service project. The `WaitFor` call ensures the service does not start until the broker is healthy.

    ```csharp title="AppHost/Program.cs"
    var builder = DistributedApplication.CreateBuilder(args);

    var kubemqKey = builder.AddParameter("kubemq-key", secret: true);

    var messaging = builder.AddKubeMQ("messaging")
        .WithLicenseKey(kubemqKey)
        .WithDataVolume();

    builder.AddProject<Projects.KubeMQ_Aspire_Sample_WebApi>("webapi")
        .WithReference(messaging)
        .WaitFor(messaging);

    builder.Build().Run();
    ```

    `AddKubeMQ("messaging")` names the resource `messaging` — remember this name, because the service project uses it to resolve the connection. `WithLicenseKey` sets the `KUBEMQ_TOKEN` environment variable on the container from the secret parameter, and `WithDataVolume` binds a persistent volume to `/store` so messages survive container restarts.

    <Callout type="info">
      The `kubemq-key` parameter value is read from configuration. For local development, set it once with `dotnet user-secrets set Parameters:kubemq-key <your-license-key>` in the AppHost project, or add it to `appsettings.json` under the `Parameters` section.
    </Callout>
  </Step>

  <Step>
    ### What `AddKubeMQ` Does [#what-addkubemq-does]

    Behind the scenes, `AddKubeMQ` registers a container resource and configures it for you:

    * Pulls the `europe-docker.pkg.dev/kubemq/images/kubemq:2.5.0` image.
    * Exposes three endpoints — gRPC on target port `50000`, REST on `9090`, and the Dashboard on `8080`.
    * Uses a **persistent** container lifetime (`ContainerLifetime.Persistent`), so the container — and the data volume bound at `/store` — survives AppHost restarts.

    This means the broker keeps running between debug sessions and your queued messages are not lost when you stop and restart the AppHost.
  </Step>

  <Step>
    ### Configure the Service Project [#configure-the-service-project]

    In your service project `Program.cs`, register the KubeMQ client. The connection name passed to `AddKubeMQClient` **must match** the resource name from the AppHost (`messaging`). Aspire injects the `host:port` connection string automatically — you never hardcode an address.

    ```csharp title="WebApi/Program.cs"
    var builder = WebApplication.CreateBuilder(args);
    builder.AddKubeMQClient("messaging");
    builder.Services.AddControllers();

    var app = builder.Build();
    app.MapControllers();
    app.Run();
    ```

    `AddKubeMQClient` registers an `IKubeMQClient` singleton, binds settings from the `Aspire:KubeMQ:Client` configuration section, and enables health checks (tagged `ready` and `live`), OpenTelemetry tracing (`AddSource("KubeMQ.Sdk")`), and metrics (`AddMeter("KubeMQ.Sdk")`) by default.

    <Callout type="info">
      For multiple KubeMQ instances in one service, use `AddKeyedKubeMQClient("name")` for each connection and resolve them with `[FromKeyedServices("name")]`. See the [Multiple KubeMQ Instances](/integrations/aspire/how-to/keyed-multi-instance) guide for details.
    </Callout>
  </Step>

  <Step>
    ### Send Your First Message [#send-your-first-message]

    Inject `IKubeMQClient` into a controller and publish an event. Events are fire-and-forget — no response is expected. This controller builds an `EventMessage` on the `events.example` channel and calls `SendEventAsync`.

    ```csharp title="WebApi/Controllers/EventsController.cs"
    using System.Text;
    using KubeMQ.Sdk.Client;
    using KubeMQ.Sdk.Events;
    using Microsoft.AspNetCore.Mvc;

    namespace KubeMQ.Aspire.Sample.WebApi.Controllers;

    [ApiController]
    [Route("api/[controller]")]
    public sealed class EventsController : ControllerBase
    {
        private readonly IKubeMQClient _client;

        public EventsController(IKubeMQClient client) => _client = client;

        [HttpPost]
        public async Task<IActionResult> PublishEvent([FromBody] string body)
        {
            var message = new EventMessage
            {
                Channel = "events.example",
                Body = Encoding.UTF8.GetBytes(body),
                Tags = new Dictionary<string, string> { ["source"] = "aspire-sample" },
            };

            await _client.SendEventAsync(message);
            return Ok(new { Status = "published" });
        }
    }
    ```

    POST a JSON string body to `/api/events` and the message is published to the `events.example` channel.
  </Step>

  <Step>
    ### Try a Queue (Send and Receive) [#try-a-queue-send-and-receive]

    Events are one pattern; queues are another. Queue messages are persisted and consumed at-least-once, which makes them a good fit for work distribution. The same `IKubeMQClient` sends with `SendQueueMessageAsync` and pulls with `ReceiveQueueMessagesAsync`. The poll request below requests up to 5 messages, waits up to 5 seconds, and auto-acknowledges on receipt.

    ```csharp title="WebApi/Controllers/QueuesController.cs"
    using System.Text;
    using KubeMQ.Sdk.Client;
    using KubeMQ.Sdk.Queues;
    using Microsoft.AspNetCore.Mvc;

    namespace KubeMQ.Aspire.Sample.WebApi.Controllers;

    [ApiController]
    [Route("api/[controller]")]
    public sealed class QueuesController : ControllerBase
    {
        private readonly IKubeMQClient _client;

        public QueuesController(IKubeMQClient client) => _client = client;

        [HttpPost]
        public async Task<IActionResult> SendMessage([FromBody] string body)
        {
            var message = new QueueMessage
            {
                Channel = "queues.example",
                Body = Encoding.UTF8.GetBytes(body),
            };

            var result = await _client.SendQueueMessageAsync(message);
            return Ok(new { result.MessageId, result.IsError, result.Error });
        }

        [HttpGet]
        public async Task<IActionResult> ReceiveMessages()
        {
            var request = new QueuePollRequest
            {
                Channel = "queues.example",
                MaxMessages = 5,
                WaitTimeoutSeconds = 5,
                AutoAck = true,
            };

            var response = await _client.ReceiveQueueMessagesAsync(request);
            var messages = response.Messages.Select(m => new
            {
                m.MessageId,
                Body = Encoding.UTF8.GetString(m.Body.Span),
            }).ToList();

            return Ok(new { Count = messages.Count, Messages = messages });
        }
    }
    ```

    POST to `/api/queues` to enqueue a message, then GET `/api/queues` to drain up to five messages from the `queues.example` queue.
  </Step>

  <Step>
    ### Run It [#run-it]

    Start the whole distributed application from the AppHost project:

    ```bash
    dotnet run
    ```

    Aspire launches the dashboard in your browser. There you can confirm that the `messaging` container and the `webapi` resource are running and **healthy**. The sample service project calls `MapDefaultEndpoints` in its `ServiceDefaults`, which maps two health endpoints:

    | Endpoint  | Reports                                                |
    | --------- | ------------------------------------------------------ |
    | `/health` | Checks tagged `ready` (the broker connection is ready) |
    | `/alive`  | Checks tagged `live` (the process is live)             |

    A green health indicator on `webapi` means the KubeMQ readiness check passed and the client is connected to the broker.
  </Step>

  <Step>
    ### Worker-Style Services [#worker-style-services]

    Not every service is a web app. In a background worker or console service, use `Host.CreateApplicationBuilder(args)` instead of `WebApplication.CreateBuilder(args)`. The client registration is identical — resolve `IKubeMQClient` from the host and call `ConnectAsync` before you publish or subscribe.

    ```csharp title="Worker/Program.cs"
    using System.Text;
    using KubeMQ.Sdk.Client;
    using KubeMQ.Sdk.Events;

    var builder = Host.CreateApplicationBuilder(args);
    builder.AddServiceDefaults();
    builder.AddKubeMQClient("messaging");

    var host = builder.Build();
    var client = host.Services.GetRequiredService<IKubeMQClient>();
    var logger = host.Services.GetRequiredService<ILogger<Program>>();

    await client.ConnectAsync();

    await client.SendEventAsync(new EventMessage
    {
        Channel = "events.fanout",
        Body = Encoding.UTF8.GetBytes("Hello from a worker"),
    });

    await host.RunAsync();
    ```
  </Step>

  <Step>
    ### Next Steps [#next-steps]

    <Cards>
      <Card title="Pub/Sub" href="/integrations/aspire/how-to/events" description="Publish and subscribe to events with IKubeMQClient — fan-out, ordering, and subscription patterns." />

      <Card title="Queues" href="/integrations/aspire/how-to/queues" description="At-least-once queue messaging — send, poll, acknowledge, and tune QueuePollRequest." />

      <Card title="Configuration & TLS" href="/integrations/aspire/how-to/configuration-and-tls" description="Settings, keyed clients, health checks, OpenTelemetry, and securing the connection with TLS." />
    </Cards>
  </Step>
</Steps>
