# client csharp (AsyncAPI)

Source: https://apigenie.pl/techdocs/reference/cli-generate_client_csharp_asyncapi

Generate a C# Kafka client library for the Kafka channels of an AsyncAPI document.

## Usage

```sh
apigenie-generator client csharp [...OPTIONS] INPUT_FILE
```

## Description

`apigenie-generator client csharp` generates from OpenAPI documents (see
[apigenie-generator client csharp](/techdocs/reference/cli-generate_client_csharp)) and from AsyncAPI 3.x
documents. It tells them apart by their `openapi` or `asyncapi` field. Documents that reference
other files are bundled first.

From an AsyncAPI document it generates a .NET 8 class library (`Microsoft.NET.Sdk`) on the Kafka
client [Confluent.Kafka](https://github.com/confluentinc/confluent-kafka-dotnet) (Apache-2.0, pinned
at 2.15.1). It also references `Microsoft.Extensions.Hosting.Abstractions` and
`Microsoft.Extensions.Options.ConfigurationExtensions`.

By default the client implements the counterpart of the application the document describes: it
sends the messages of the application's `receive` operations, and receives the messages of its
`send` operations. Set the `perspective` option to `application` to generate the application's
side instead. The two sides talk to each other, and to the application of
[apigenie-generator server aspnet (AsyncAPI)](/techdocs/reference/cli-generate_server_aspnet_asyncapi).

The generator writes the Kafka channels:
- the channels of servers with the `kafka` or `kafka-secure` protocol;
- in documents without servers, the channels with a Kafka binding.

Channels of other protocols are skipped and reported as info.

```csharp
// the application's side of the Carpet Express Event Platform
builder.Services.AddCarpetExpressKafkaClient(builder.Configuration);

public sealed class Shipping(CarpetExpressKafkaClient kafka)
{
    public async Task DeliveredAsync(ShipmentDelivered message, CancellationToken cancellationToken)
    {
        await kafka.ShipmentEvents.SendShipmentDeliveredAsync(message, cancellationToken);
    }

    public async Task<Uri?> LabelAsync(RenderLabel request, CancellationToken cancellationToken)
    {
        // sends the request with a new correlation id and waits for its reply
        var reply = await kafka.LabelRenderRequests.RequestRenderLabelAsync(request, TimeSpan.FromSeconds(10), cancellationToken);
        return reply is LabelRendered rendered ? rendered.Payload.Url : null;
    }
}
```

The project's root namespace (and assembly name) is `root_namespace`, else `--package-name`, else
the document's title in PascalCase followed by `.Client`. The class names start with the
namespace's stem: its last segment, or the segment before a last `Client`, `Clients` or `Sdk`. For
example, `CarpetExpress.Client` gives `CarpetExpressKafkaClient`.

| File | Contents |
| ---- | -------- |
| `Models/*.cs`, `Serialization/*.cs`, `Messages/*.cs` | the payload types, the JSON context and the message records, as [apigenie-generator server aspnet (AsyncAPI)](/techdocs/reference/cli-generate_server_aspnet_asyncapi) writes them |
| `Kafka/<Name>KafkaClient.cs` | the client: a producer per channel it sends to |
| `Kafka/<Channel>Producer.cs` | the `Send…Async` and `Request…Async` methods of a channel |
| `Kafka/*Handler.cs`, `Kafka/*Consumer.cs` | the handler interfaces and consumers of the received operations |
| `Kafka/<Name>KafkaClientOptions.cs` | the options, with the topics and ids |
| `Kafka/<Name>KafkaClientServiceCollectionExtensions.cs`, `Kafka/<Name>KafkaClientBuilder.cs` | `Add<Name>KafkaClient` and the registration of handlers |
| `Infrastructure/*.cs` | the Kafka runtime (producer, consumer service, reply consumer, headers) and the validation of messages |
| `<RootNamespace>.csproj`, `README.md` | the project |

- **Sent operations.** The client has a producer per channel, e.g. `client.ShipmentEvents`. A
  producer's `Send<Message>Async` validates a message and completes once the brokers have it. It
  writes the constant headers (e.g. `ce_type`), the key and the topic from the bindings.
- **Request/reply.** A request/reply operation gets `Request<Message>Async(message,
  cancellationToken)` and an overload with a timeout.
  - It sends the request with its correlation id, or with a new Guid when the request has none.
  - It returns the reply: the reply message, or an interface of the replies
    (`IRequestLabelRenderReply`) when there are several.
  - It throws `TimeoutException` when no reply comes within the timeout (by default
    `ReplyTimeout`), and `OperationCanceledException` when the token is cancelled.

  One consumer per client reads the replies of every request. It has no consumer group, and starts
  with the first request reading from a minute before, so no reply is missed.
- **Received operations.** They have handler interfaces and consumers, as in
  [apigenie-generator server aspnet (AsyncAPI)](/techdocs/reference/cli-generate_server_aspnet_asyncapi):
  - the dispatch by a payload property or a header, with `OnUnknownAsync` for other records;
  - validation;
  - commits once the handler returns, and retries after `RetryDelay` when it throws;
  - replies sent with the request's correlation id.

  Register a handler with the builder: its consumer runs as a hosted service of the host.

```csharp
// the counterpart's side: consume the shipment events and answer label requests
builder.Services.AddCarpetExpressKafkaClient(builder.Configuration)
    .AddPublishShipmentEventsHandler<ShipmentEventsHandler>()
    .AddRequestLabelRenderHandler<LabelRenderer>();
```

`Add<Name>KafkaClient(configuration, configure)` does the following:
- binds the options to the `AsyncApi:Kafka` section of `configuration` when it is given, then
  applies `configure`;
- registers the client and its producers as singletons;
- returns the builder.

Without dependency injection, `new CarpetExpressKafkaClient(options)` sends and requests; dispose
it when done.

### Settings

The options are those of [apigenie-generator server aspnet (AsyncAPI)](/techdocs/reference/cli-generate_server_aspnet_asyncapi#settings):
`BootstrapServers`, `ClientId`, `ReplyTimeout`, `RetryDelay`, `Topics`, `Operations` (the group
and client ids of received operations), `Consumer` and `Producer`. A client that sends requests
also has `ReplyClientId`, the client id of its reply consumer. It defaults to the client id of the
first request/reply operation's Kafka binding. Without a value in the binding, the default is
`<namespace stem in kebab case>-<operation>`.

Not supported, and reported as warnings:
- Avro payloads, and payloads of other non-JSON schema formats;
- reply addresses given by runtime expressions;
- Kafka topics with parameters.

Security (SASL/SCRAM, mTLS) is not configured: set the librdkafka properties in `Consumer` and
`Producer`.

## Extensions

| Extension | Effect |
| --------- | ------ |
| `x-apigenie.codegen.disabled` | `true` on a channel or an operation leaves it out (with the operations of a channel left out) |
| `x-enum-varnames`, `x-enum-names` | names of the members of an enum, in PascalCase |
| `x-enum-descriptions`, `x-enumDescriptions` | documentation comments of the members of an enum |

The extensions can live in an overlay for this target instead of in the document, e.g.
`csharp.overlay.yaml`:

```yaml
overlay: 1.0.0
info:
  title: C# names
  version: 1.0.0
actions:
  - target: $.components.schemas.OrderStatus
    update:
      x-enum-varnames: [Created, Paid, Shipped]
  - target: $.operations.publishAudit
    update:
      x-apigenie:
        codegen:
          disabled: true
```

```sh
apigenie-generator client csharp asyncapi.yaml --overlay csharp.overlay.yaml -o out
```

## Options

### -c, --config

Path to the configuration file to use. See the "Configuration" section for more information.

### --overlay OVERLAY

Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the
extensions of the "Extensions" section. Can be repeated: the overlays apply in order, after those
listed under `commands.generate.client_csharp.overlay` in the configuration file (with paths
relative to it). Warnings and errors about a value an overlay set point to the overlay. The overlays
apply to the AsyncAPI document bundled with the files it references.

### --strict

Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated)
instead of a warning (`OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.`).

### -o, --output

Directory where the project is generated.

### --package-name

Root namespace of the generated code, e.g. `Acme.Events.Client`. Defaults to the title of the API
in PascalCase, followed by `.Client`.

### --package-version

Version of the project. Defaults to the version of the API when it is a .NET version, else 1.0.0.

### -p, --property

Applies to OpenAPI documents only. Using it with an AsyncAPI document is an error.

## Configuration

The options are read from the `asyncapi` section of the configuration of `apigenie-generator client csharp`.
Its other keys configure the generation from OpenAPI documents.

```yaml
version: '1.0'
commands:
  generate:
    client_csharp:
      asyncapi:
        root_namespace: Acme.Events.Client
        validation: true
        perspective: counterpart
```

| Option | Description |
| ------ | ----------- |
| `root_namespace` | Root namespace and assembly name of the project (default: `--package-name`, or the title followed by `.Client`) |
| `validation` | `true` (default): validate messages by their DataAnnotations before sending and after receiving them |
| `perspective` | `counterpart` (default): the counterpart of the described application; `application`: the application itself |
