diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 52af6c216..f0a7e163a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -27,6 +27,8 @@ jobs: with: dotnet-version: '8.x' + - run: dotnet build + - run: dotnet tool update -g docfx - run: docfx docs/docfx.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d4ed5a0f1..b10419d1e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -132,6 +132,14 @@ jobs: ./NATS.Client.CheckNativeAot + - name: Check Documentation Examples + run: | + killall nats-server 2> /dev/null | echo -n + nats-server -v + nats-server -js & + cd tests/NATS.Net.DocsExamples + dotnet run + windows_test: name: Windows strategy: diff --git a/NATS.Client.sln b/NATS.Net.sln similarity index 100% rename from NATS.Client.sln rename to NATS.Net.sln diff --git a/README.md b/README.md index 36fe7423e..4b5300ede 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,3 @@ -### Looking for NATS .NET v1? [Click here](https://github.com/nats-io/nats.net.v1) - -Please update your references to the new name. You can also read the [announcement](REPO_RENAME.md) for more information. - ---- - [![License Apache 2.0](https://img.shields.io/badge/License-Apache2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![NuGet](https://img.shields.io/nuget/v/NATS.Net.svg?cacheSeconds=3600)](https://www.nuget.org/packages/NATS.Net) @@ -16,6 +10,16 @@ async enumerables and channels, and leverages advanced .NET memory, buffer and I Check out [NATS .NET client library documentation](https://nats-io.github.io/nats.net/) for guides and examples. +> [!NOTE] +> **Don't confuse NuGet packages!** +> NATS .NET package on NuGet is called [NATS.Net](https://www.nuget.org/packages/NATS.Net). +> There is another package called `NATS.Client` which is the older version of the client library +> and will be deprecated eventually. + +> [!TIP] +> NATS .NET now supports **.NET Standard** 2.0 and 2.1 along with .NET 6.0 and 8.0, +> which means you can also use it with **.NET Framework** 4.6.2+ and **Unity** 2018.1+. + ### What is NATS? NATS is a high-performance, secure, distributed messaging system. @@ -32,26 +36,26 @@ Basic messaging: ```csharp // NATS core M:N messaging example -await using var nats = new NatsConnection(); +await using var nc = new NatsClient(); // Subscribe on one terminal -await foreach (var msg in nats.SubscribeAsync(subject: "foo")) +await foreach (var msg in nc.SubscribeAsync(subject: "foo")) { Console.WriteLine($"Received: {msg.Data}"); } // Start publishing to the same subject on a second terminal -await nats.PublishAsync(subject: "foo", data: "Hello, World!"); +await nc.PublishAsync(subject: "foo", data: "Hello, World!"); ``` Persistance with JetStream: ```csharp -await using var nats = new NatsConnection(); -var js = new NatsJSContext(nats); +await using var nc = new NatsClient(); +var js = nc.CreateJetStreamContext(); // Create a stream to store the messages -await js.CreateStreamAsync(new StreamConfig(name: "orders", subjects: new[] { "orders.*" })); +await js.CreateStreamAsync(new StreamConfig(name: "ORDERS", subjects: new[] { "orders.*" })); // Publish a message to the stream. The message will be stored in the stream // because the published subject matches one of the the stream's subjects. @@ -59,7 +63,7 @@ var ack = await js.PublishAsync(subject: "orders.new", data: "order 1"); ack.EnsureSuccess(); // Create a consumer on a stream to receive the messages -var consumer = await js.CreateOrUpdateConsumerAsync("orders", new ConsumerConfig("order_processor")); +var consumer = await js.CreateOrUpdateConsumerAsync("ORDERS", new ConsumerConfig("order_processor")); await foreach (var jsMsg in consumer.ConsumeAsync()) { @@ -79,21 +83,46 @@ See more details, including how to download and start NATS server and JetStream ## Packages -- **NATS.Net**: Meta package that includes all other packages (except serialization) +- **NATS.Net**: Meta package that includes all other packages except extensions - **NATS.Client.Core**: [Core NATS](https://docs.nats.io/nats-concepts/core-nats) - **NATS.Client.JetStream**: [JetStream](https://docs.nats.io/nats-concepts/jetstream) - **NATS.Client.KeyValueStore**: [Key/Value Store](https://docs.nats.io/nats-concepts/jetstream/key-value-store) - **NATS.Client.ObjectStore**: [Object Store](https://docs.nats.io/nats-concepts/jetstream/obj_store) - **NATS.Client.Services**: [Services](https://docs.nats.io/using-nats/developer/services) -- **NATS.Extensions.Microsoft.DependencyInjection**: extension to configure DI container +- **NATS.Client.Simplified**: simplify common use cases especially for beginners - **NATS.Client.Serializers.Json**: JSON serializer for ad-hoc types +- **NATS.Extensions.Microsoft.DependencyInjection**: extension to configure DI container ## Contributing -- Run `dotnet format` at root directory of project in order to clear warnings that can be auto-formatted -- Run `dotnet build` at root directory and make sure there are no errors or warnings +You are welcome to contribute to this project. Here are some steps to get you started: -Find us on [slack.nats.io dotnet channel](https://natsio.slack.com/channels/dotnet) +### Reporting Bugs and Feature Requests + +You can report bugs and request features +by opening an [issue on GitHub](https://github.com/nats-io/nats.net/issues/new/choose). + +### Join the Community + +You can join the community asking questions, sharing ideas, and helping others: + +- Join the [NATS Slack](https://slack.nats.io) and find us on the `#dotnet` channel +- Join the discussion on [GitHub Discussions](https://github.com/nats-io/nats.net/discussions) +- Follow us on X [@nats_io](https://x.com/nats_io) + +### Contributing Code + +- Read the [Contributor Guide](CONTRIBUTING.md) +- Fork the repository and create a branch +- Open `NATS.Net.sln` solution in Visual Studio, Rider or VS Code (or any other editor of your choice) +- Make changes and write tests +- Run tests against a locally installed NATS server in your PATH +- Note that some tests are still not reliable locally, so CI will run all tests +- For a quick check, run `NATS.Client.Platform.Windows.Tests` which is a subset of tests that should pass on Windows +- You can also locally run `NATS.Client.CoreUnit.Tests` and `NATS.Client.Core2.Tests` which are more stable +- Run `dotnet format` at root directory of project to clear warnings that can be auto-formatted +- Run `dotnet build` at root directory and make sure there are no errors or warnings +- Submit a pull request Please also check out the [Contributor Guide](CONTRIBUTING.md) and [Code of Conduct](CODE-OF-CONDUCT.md). diff --git a/docs/api/index.md b/docs/api/index.md index 7557a3375..93da0b77d 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -1,5 +1,5 @@ # API Documentation -You can browse the latest NATS.Net API Documentation[^1] [here](NATS.html). +You can browse the latest NATS.Net API Documentation[^1] [here](NATS.yml). [^1]: API Documentation is auto-generated using [DocFX](https://dotnet.github.io/docfx/). diff --git a/docs/documentation/advanced/aot.md b/docs/documentation/advanced/aot.md new file mode 100644 index 000000000..52a12b1e2 --- /dev/null +++ b/docs/documentation/advanced/aot.md @@ -0,0 +1,15 @@ +# Native Ahead-of-Time Deployments + +For [Native Ahead-of-Time (AOT) deployments](https://learn.microsoft.com/dotnet/core/deploying/native-aot), +you need to use the `NatsConnection` class directly. +This is because the `NatsClient` class uses reflection to set up the ad hoc JSON serializers, which is not supported in AOT scenarios. + +If you started with the `NatsClient` class and need to switch to `NatsConnection`, you can do so without any changes to your code +because both classes implement the `INatsClient` interface. + +NuGet packages that are compatible with AOT publishing are: +- [NATS.Client.Core](https://www.nuget.org/packages/NATS.Client.Core) +- [NATS.Client.JetStream](https://www.nuget.org/packages/NATS.Client.JetStream) +- [NATS.Client.KeyValueStore](https://www.nuget.org/packages/NATS.Client.KeyValueStore) +- [NATS.Client.ObjectStore](https://www.nuget.org/packages/NATS.Client.ObjectStore) +- [NATS.Client.Services](https://www.nuget.org/packages/NATS.Client.Services) diff --git a/docs/documentation/advanced/intro.md b/docs/documentation/advanced/intro.md new file mode 100644 index 000000000..b670c86fe --- /dev/null +++ b/docs/documentation/advanced/intro.md @@ -0,0 +1,97 @@ +# Advanced Options + +For more advanced configuration, you can use the [`NatsOpts`](xref:NATS.Client.Core.NatsOpts) +class to configure the connection to the NATS server. + +For example, you can hook your logger to `NatsClient` to make sure all is working as expected or +to get help diagnosing any issues you might have: + +(For this example, you need to add [Microsoft.Extensions.Logging.Console](https://www.nuget.org/packages/Microsoft.Extensions.Logging.Console) from Nuget.) + +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/IntroPage.cs#logging)] + +## NatsClient vs NatsConnection + +[`NatsClient`](xref:NATS.Net.NatsClient) is a high-level API that wraps [`NatsConnection`](xref:NATS.Client.Core.NatsConnection) +and provides a more user-friendly interface to interact with the NATS server. +It is the recommended way to interact with the NATS server for beginners. +However, if you need to access the underlying `NatsConnection` instance, +you can do so by using the `Connection` property of `NatsClient` or by creating a new instance of `NatsConnection`. + +**So, What's the Difference?** + +`NatsClient` implements `INatsClient` and provides a high-level APIs, and also +sets up the serializers to use the expected formats for message types like `int`, +`string`, `byte[]`, and data classes for ad hoc JSON serialization. + +`NatsConnection` is the underlying class that manages the connection to the NATS server. +It provides more advanced APIs and allows you to configure the connection in more detail. +`NatsConnection`implements `INatsConnection` which extends `INatsClient` with advanced APIs, +so you can use it as a `NatsClient` instance without any changes to your code. When you +instantiate a `NatsConnection` with default options, you would only have basic serialization +for `int`, `string`, and `byte[]` types, and you would need to set up the serializers for your data classes +if you want to use e.g., JSON serialization. + +The other difference is that `NatsClient` sets `SubPendingChannelFullMode` internal channel option to +`BoundedChannelFullMode.Wait` to avoid dropping messages when the subscriber's internal channel is full. +This is a good default for most cases, but you can change it by setting the `SubPendingChannelFullMode` option +in `NatsClient` constructor. + +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/IntroPage.cs#opts)] + +You can also use the `NatsConnection` class directly. + +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/IntroPage.cs#opts2)] + +**Which One Should I Use?** + +If you are new to NATS, you should use `NatsClient` as it provides a more user-friendly interface +with sensible defaults especially for serialization. +If you need more control over the connection options, AOT deployments, or custom serializers, +you should use `NatsConnection`. + +See also [serialization](serialization.md) for more information on how to set up custom serializers. + +> [!NOTE] +> Every [`NatsClient`](xref:NATS.Net.NatsClient) (and the underlying [`NatsConnection`](xref:NATS.Client.Core.NatsConnection)) +> instance is a TCP connection to a NATS server. +> Typically an application will only need one +> connection, and many subscriptions and publishers would share that same connection. Connections are relatively +> heavyweight and expensive to create while +> subscriptions and publishers are lightweight internal NATS protocol handlers. +> NATS.Net should be able to handle large numbers of subscriptions +> and publishers per connection. + +## Subscriptions with Lower Level Control + +The +[`SubscribeAsync()`](xref:NATS.Client.Core.INatsClient.SubscribeAsync``1(System.String,System.String,NATS.Client.Core.INatsDeserialize{``0},NATS.Client.Core.NatsSubOpts,System.Threading.CancellationToken)) +method is a convenient way to subscribe to a subject and receive messages without much effort. +If you need more control over how subscription is handled, you can use the +[`SubscribeCoreAsync()`](xref:NATS.Client.Core.INatsConnection.SubscribeCoreAsync``1(System.String,System.String,NATS.Client.Core.INatsDeserialize{``0},NATS.Client.Core.NatsSubOpts,System.Threading.CancellationToken)) +method instead. + +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/IntroPage.cs#lowlevel-sub)] + +## Round Trip Time (RTT) + +[`PingAsync()`](xref:NATS.Client.Core.INatsClient.PingAsync(System.Threading.CancellationToken)) is somewhat a +special method in all NATS clients, mostly referred to as `rtt`. It is used to send a ping to the server and +receive a pong back while measuring the round trip time. Since it waits for the server to respond, as a side effect, +it also flushes the outgoing buffers. + +Remember that every [`NatsConnection`](xref:NATS.Client.Core.NatsConnection) instance is a single TCP connection +and all the calls sent to the server are +essentially sent back to back after they're picked up from internal queues and buffers. + +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/IntroPage.cs#ping)] + +> [!NOTE] +> [`NatsConnection`](xref:NATS.Client.Core.NatsConnection) establishes the first server connection when the first call to subscribe or publish is made. +> You can also call the `ConnectAsync()` method explicitly to establish the connection before any other calls are made. + +## What's Next + +- [Serialization](serialization.md) is the process of converting an object into a format that can be stored or transmitted. +- [Security](security.md) is an important aspect of any distributed system. NATS provides a number of security features to help you secure your applications. +- [AOT Deployment](aot.md) is a way to deploy your applications as native platform executables, which produces faster startup times and better performance in most cases. diff --git a/docs/documentation/advanced/security.md b/docs/documentation/advanced/security.md new file mode 100644 index 000000000..366380074 --- /dev/null +++ b/docs/documentation/advanced/security.md @@ -0,0 +1,36 @@ +# Security + +NATS has a lot of [security features](https://docs.nats.io/nats-concepts/security) and .NET V2 client supports them all. +All you need to do is to pass your credentials to the connection. + +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/SecurityPage.cs#user-pass)] + +See also [user authentication tests](https://github.com/nats-io/nats.net.v2/blob/main/tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs) for more examples. + +## Implicit TLS Connections + +As of NATS server version 2.10.4 and later, the server supports implicit TLS connections. +This means that the client can connect to the server using the default port of 4222 and the server will automatically upgrade the connection to TLS. +This is useful for environments where TLS is required by default. + +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/SecurityPage.cs#tls-implicit)] + +## Mutual TLS Connections + +The [server can require TLS certificates from a client](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/auth_intro/tls_mutual_auth) to validate +the client certificate matches a known or trusted CA and to provide authentication. + +You can set the TLS options to use your client certificates when connecting to a server which requires TLS Mutual authentication. + +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/SecurityPage.cs#tls-mutual)] + +> [!TIP] +> #### Intermediate CA Certificates +> +> When connecting using intermediate CA certificates, it might not be possible to validate the client certificate and +> TLS handshake may fail. +> +> Unfortunately, for .NET client applications it isn't possible to pass additional intermediate certificates and the +> only solution is to add the certificates to the certificate store manually. +> +> See also .NET documentation on [Troubleshooting SslStream authentication issues](https://learn.microsoft.com/en-us/dotnet/core/extensions/sslstream-troubleshooting#intermediate-certificates-are-not-sent) diff --git a/docs/documentation/advanced/serialization.md b/docs/documentation/advanced/serialization.md new file mode 100644 index 000000000..47f05699f --- /dev/null +++ b/docs/documentation/advanced/serialization.md @@ -0,0 +1,116 @@ +# Serialization + +NATS.Net supports serialization of messages using a simple interface [`INatsSerializer`](xref:NATS.Client.Core.INatsSerializer`1). + +By default, the client uses the [`NatsClientDefaultSerializer`](xref:NATS.Net.NatsClientDefaultSerializer`1) +which can handle binary data, UTF8 strings, numbers, and ad hoc JSON serialization. You can provide your own +serializer by implementing the [`INatsSerializer`](xref:NATS.Client.Core.INatsSerializer`1) interface or using the +[`NatsJsonContextSerializer`](xref:NATS.Client.Core.NatsJsonContextSerializer`1) for generated +JSON serialization. Serializers can also be chained together to provide multiple serialization formats typically +depending on the types being used. + +## Serializer Registries + +There are two default serializer registries that can be used to provide serializers for specific types. +For any other serializers, you can implement your own serializer registry. + +### NatsClientDefaultSerializer + +This is the default serializer for [`NatsClient`](xref:NATS.Net.NatsClient) that +is used when no serializer registry is provided as an option. + +- Can serialize what `NatsDefaultSerializerRegistry` can (see below). +- Additionally, it can serialize data classes using ad hoc JSON serialization. +- Uses reflection to generate serialization code at runtime so it's not AOT friendly. + +The default client serializer is designed to be used by developers +who want to have an out-of-the-box experience for basic use cases like sending and receiving UTF8 strings, +or JSON messages. + +### NatsDefaultSerializerRegistry + +This is the default serializer for [`NatsConnection`](xref:NATS.Client.Core.NatsConnection) that +is used when no serializer registry is provided as an option. +See also [the differences between NatsClient vs NatsConnection](intro.md#natsclient-vs-natsconnection) + +- AOT friendly +- If the data is a byte array, [`Memory`](https://learn.microsoft.com/dotnet/api/system.memory-1), [`IMemoryOwner`](https://learn.microsoft.com/dotnet/api/system.buffers.imemoryowner-1) or similar it is treated as binary data. +- If the data is a string or similar it is treated as UTF8 string. +- If the data is a primitive (for example `DateTime`, `int` or `double`. See also [`NatsUtf8PrimitivesSerializer`](xref:NATS.Client.Core.NatsUtf8PrimitivesSerializer`1)) it is treated as the primitive encoded as a UTF8 string. +- For any other type, the serializer will throw an exception. + +The default connection serializer is designed to be AOT friendly and mostly suitable for binary data. + +### Using Custom Serializer Registries + +Serialising custom data formats can be done by implementing the serializer registry interface +[`INatsSerializerRegistry`](xref:NATS.Client.Core.INatsSerializerRegistry) +that can be used to provide a custom serializer instances for specific types. + +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs#default)] + +## Using JSON Serializer Context + +The [`NatsJsonContextSerializer`](xref:NATS.Client.Core.NatsJsonContextSerializer`1) +uses the [`System.Text.Json`](https://learn.microsoft.com/dotnet/api/system.text.json) +serializer to serialize and deserialize messages. It relies +on the [`System.Text.Json` source generator](https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator/) +to generate the serialization code at compile time. This is the recommended JSON serializer for most use cases and it's +required for [Native AOT deployments](https://learn.microsoft.com/dotnet/core/deploying/native-aot). + +First you need to define your JSON classes and a context to generate the serialization code: +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs#my-data)] + +Then you can use the [`NatsJsonContextSerializer`](xref:NATS.Client.Core.NatsJsonContextSerializer`1) to serialize and deserialize messages +by providing the registry ([`NatsJsonContextSerializerRegistry`](xref:NATS.Client.Core.NatsJsonContextSerializerRegistry)) with the connection options: + +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs#my-data-usage)] + +You can also set the serializer for a specific subscription or publish call: + +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs#my-data-publish)] + +## Using Custom Serializer + +You can also provide your own serializer by implementing the [`INatsSerializer`](xref:NATS.Client.Core.INatsSerializer`1) interface. This is useful if you need to +support a custom serialization format or if you need to support multiple serialization formats. + +Here is an example of a custom serializer that uses the Google ProtoBuf serializer to serialize and deserialize: + +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs#custom-serializer)] + +You can then use the custom serializer as the default for the connection: + +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs#custom)] + +## Using Multiple Serializers (chaining) + +You can also chain multiple serializers together to support multiple serialization formats. The first serializer in the +chain that can handle the data will be used. This is useful if you need to support multiple serialization formats and +reuse them. + +Note that chaining serializers is implemented by convention and not enforced by the [`INatsSerializer`](xref:NATS.Client.Core.INatsSerializer`1) +interface since the next serializer would not be exposed to external users of the interface. + +Here is an example of a serializer that uses the Google ProtoBuf serializer and the [`NatsJsonContextSerializer`](xref:NATS.Client.Core.NatsJsonContextSerializer`1) to +serialize and deserialize messages based on the type: + +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs#mixed)] + +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs#chain)] + +## Dealing with Binary Data and Buffers + +The default serializer can handle binary data and buffers. This is typically archived by using [`IMemoryOwner`](https://learn.microsoft.com/dotnet/api/system.buffers.imemoryowner-1) +implementations. NATS .NET Client provides a [`NatsMemoryOwner`](xref:NATS.Client.Core.NatsMemoryOwner`1) implementation that can be used to allocate buffers. +The [`NatsMemoryOwner`](xref:NATS.Client.Core.NatsMemoryOwner`1) and [`NatsBufferWriter`](xref:NATS.Client.Core.NatsBufferWriter`1) (adapted from [.NET Community Toolkit](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/high-performance/memoryowner)) +are [`IMemoryOwner`](https://learn.microsoft.com/dotnet/api/system.buffers.imemoryowner-1) and [`IBufferWriter`](https://learn.microsoft.com/dotnet/api/system.buffers.ibufferwriter-1) implementations that use the [`ArrayPool`](https://learn.microsoft.com/dotnet/api/system.buffers.arraypool-1) +to allocate buffers. They can be used with the default serializer. + +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs#buffers)] + +Advantage of using [`NatsMemoryOwner`](xref:NATS.Client.Core.NatsMemoryOwner`1) and [`NatsBufferWriter`](xref:NATS.Client.Core.NatsBufferWriter`1) is that they can be used with the default serializer and +they can be used to allocate buffers from the [`ArrayPool`](https://learn.microsoft.com/dotnet/api/system.buffers.arraypool-1) which can be reused. This is useful if you need to allocate +buffers for binary data and you want to avoid allocating buffers on for every operation (e.g. `new byte[]`) reducing +garbage collection pressure. They may also be useful for example, if your subscription may receive messages with +different formats and the only way to determine the format is by reading the message. diff --git a/docs/documentation/core/intro.md b/docs/documentation/core/intro.md index 7792eb87d..48755983c 100644 --- a/docs/documentation/core/intro.md +++ b/docs/documentation/core/intro.md @@ -9,14 +9,17 @@ pub/sub functionality and the concept of [Subject-Based Messaging](https://docs. ## Quick Start [Download the latest](https://nats.io/download/) `nats-server` for your platform and run it without any arguments. `nats-server` will listen -on its default TCP port 4222. +on its default TCP port 4222. You can also use a containerized version of the NATS server: ```shell $ nats-server ``` +or +```shell +$ docker run nats +``` -Install [NATS.Net](https://www.nuget.org/packages/NATS.Net) -and [NATS.Client.Serializers.Json](https://www.nuget.org/packages/NATS.Client.Serializers.Json) from Nuget. +Install [NATS.Net](https://www.nuget.org/packages/NATS.Net) from Nuget. Given that we have a plain class `Bar`, we can publish and subscribe to our `nats-server` sending and receiving `Bar` objects: @@ -30,14 +33,6 @@ Subscribe to all `bar` [related subjects](https://docs.nats.io/nats-concepts/sub Publish `Bar` objects to related `bar` [subjects](https://docs.nats.io/nats-concepts/subjects): [!code-csharp[](../../../tests/NATS.Net.DocsExamples/Core/IntroPage.cs#pub)] -## Logging - -You should also hook your logger to `NatsConnection` to make sure all is working as expected or -to get help diagnosing any issues you might have: - -(For this example you need to add [Microsoft.Extensions.Logging.Console](https://www.nuget.org/packages/Microsoft.Extensions.Logging.Console) from Nuget.) -[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Core/IntroPage.cs#logging)] - ## What's Next [Publish-Subscribe](pub-sub.md) is the message distribution model for one-to-many communication. diff --git a/docs/documentation/core/pub-sub.md b/docs/documentation/core/pub-sub.md index 7670a2664..064c2a9cb 100644 --- a/docs/documentation/core/pub-sub.md +++ b/docs/documentation/core/pub-sub.md @@ -6,30 +6,6 @@ receives the message. [!code-csharp[](../../../tests/NATS.Net.DocsExamples/Core/PubSubPage.cs#pubsub)] -## Subscriptions with Lower Level Control - -The -[`SubscribeAsync()`](xref:NATS.Client.Core.INatsConnection.SubscribeAsync``1(System.String,System.String,NATS.Client.Core.INatsDeserialize{``0},NATS.Client.Core.NatsSubOpts,System.Threading.CancellationToken)) -method is a convenient way to subscribe to a subject and receive messages without much effort. -If you need more control over how subscription is handled, you can use the -[`SubscribeCoreAsync()`](xref:NATS.Client.Core.INatsConnection.SubscribeCoreAsync``1(System.String,System.String,NATS.Client.Core.INatsDeserialize{``0},NATS.Client.Core.NatsSubOpts,System.Threading.CancellationToken)) -method instead. - -[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Core/PubSubPage.cs#lowlevel)] - -> [!NOTE] -> [`NatsConnection`](xref:NATS.Client.Core.NatsConnection) establishes the first server connection when the first call to subscribe or publish is made. -> This is why we call the `ConnectAsync()` method explicitly before subscribe or publishing any messages in the example above, -> making sure the subscription request is received by the server before any publish requests, avoiding potential race conditions -> of subscribe and publish method establishing the first connection. - - -> [!NOTE] -> [`PingAsync()`](xref:NATS.Client.Core.INatsConnection.PingAsync(System.Threading.CancellationToken)) is somewhat a -> special method in all NATS clients. It is used to send a ping to the server and -> receive a pong back while measuring the round trip time. Since it waits for the server to respond, as a side effect -> it also flushes the outgoing buffers. -> -> Remember that every [`NatsConnection`](xref:NATS.Client.Core.NatsConnection) instance is a single TCP connection -> and all the calls sent to the server are -> essentially serialized back to back after they're picked up from internal queues and buffers. +You can run multiple subscribers to the same subject, and each subscriber will receive a copy of the message. +At the same time, you can have multiple publishers sending messages to the same subject. +This is a powerful feature of NATS that enables many messaging patterns. diff --git a/docs/documentation/core/queue.md b/docs/documentation/core/queue.md index 593ca23d8..96cc92cd4 100644 --- a/docs/documentation/core/queue.md +++ b/docs/documentation/core/queue.md @@ -38,3 +38,12 @@ Stopping... [1] Done All done ``` + +In the example above, three subscribers are created, each of which is part of the same queue group. +The publisher sends 10 messages, which are randomly distributed among the subscribers. +Each subscriber processes the message and sends a response back to the publisher. + +The queue group feature is useful when you want to distribute messages among a group of subscribers +in a load-balanced manner. +Combined with other messaging patterns, such as scatter-gather, +queue groups can be used to create highly scalable and fault-tolerant systems. diff --git a/docs/documentation/core/req-rep.md b/docs/documentation/core/req-rep.md index 079777a70..f7ab96c94 100644 --- a/docs/documentation/core/req-rep.md +++ b/docs/documentation/core/req-rep.md @@ -10,3 +10,6 @@ Create a service that will be responding to requests: Reply to a request is asynchronously received using an _inbox_ subscription behind the scenes: [!code-csharp[](../../../tests/NATS.Net.DocsExamples/Core/ReqRepPage.cs#reqrep)] + +Using request-reply you can build services that are resilient to failures. +See also [Services](../services/intro.md) for a more structured approach to building services. diff --git a/docs/documentation/intro.md b/docs/documentation/intro.md index 081c0a473..02d281cfe 100644 --- a/docs/documentation/intro.md +++ b/docs/documentation/intro.md @@ -1,13 +1,23 @@ # Introduction -NATS.Net is a .NET client for the open source [Connective Technology for Adaptive Edge & Distributed Systems - NATS](https://nats.io/)! -It's build on top of the modern .NET platform, taking advantage of all the high performance features and +NATS .NET is a .NET client for the open source [NATS](https://nats.io/) messaging system. +It's built on top of the modern .NET platform, taking advantage of the high performance features and asynchronous programming model. -NATS.Net, just like NATS, is open source as is this documentation. -Please [let us know](https://natsio.slack.com/channels/dotnet) if you have updates or suggestions for +NATS .NET, just like NATS, is open source as is this documentation. +Please [let us know](https://slack.nats.io) if you have updates or suggestions for these docs. You can also create a Pull Request in GitHub using the _Edit this page_ link on each page. +> [!NOTE] +> **Don't confuse NuGet packages!** +> NATS .NET package on NuGet is called [NATS.Net](https://www.nuget.org/packages/NATS.Net). +> There is another package called `NATS.Client` which is the older version of the client library +> and will be deprecated eventually. + +> [!TIP] +> NATS .NET now supports **.NET Standard** 2.0 and 2.1 along with .NET 6.0 and 8.0, +> which means you can also use it with **.NET Framework** 4.6.2+ and **Unity** 2018.1+. + ## Quick Start You can [download the latest](https://nats.io/download/) `nats-server` for your platform and run it without any arguments. @@ -22,27 +32,31 @@ also enable [JetStream](https://docs.nats.io/nats-concepts/jetstream) by passing persistence and other advanced features. If you prefer using containers, you can also run the latest -[NATS server image](https://docs.nats.io/running-a-nats-service/nats_docker) using Docker: +[NATS server image](https://docs.nats.io/running-a-nats-service/nats_docker) using Docker or Podman, for example: ```shell $ docker run nats ``` -Here are some quick examples to get you started with NATS.Net: +Here are quick examples to get you started with NATS .NET: # [Core NATS](#tab/core-nats) Core NATS is the basic messaging functionality. Messages can be published to a subject and received by one or more -subscribers listening to the same subject only when they are running. There is no persistence and messages are -not stored anywhere. +subscribers listening to the same subject only when they are running. +Messages are not stored anywhere. Start NATS server with default options: ```shell $ nats-server ``` +or +```shell +$ docker run nats +``` -Reference [NATS.Net](https://www.nuget.org/packages/NATS.Net) NuGet package in your project: +Reference [NATS.Net NuGet package](https://www.nuget.org/packages/NATS.Net) in your project: [!code-csharp[](../../tests/NATS.Net.DocsExamples/IntroPage.cs#core-nats)] @@ -56,6 +70,10 @@ Start NATS server with JetStream enabled: ```shell $ nats-server -js ``` +or +```shell +$ docker run nats -js +``` Reference [NATS.Net NuGet package](https://www.nuget.org/packages/NATS.Net/) in your project: @@ -63,16 +81,7 @@ Reference [NATS.Net NuGet package](https://www.nuget.org/packages/NATS.Net/) in --- -> [!NOTE] -> Every [`NatsConnection`](xref:NATS.Client.Core.NatsConnection) instance is a TCP connection to a NATS server. -> Typically an application will only need one -> connection and many subscriptions and publishers would share that same connection. Connections are relatively -> heavyweight and expensive to create while -> subscriptions and publishers are lightweight internal NATS protocol handlers. -> NATS.Net should be able to handle large numbers of subscriptions -> and publishers per connection. - -Now you should be able to run NATS server on your machine and use the above code samples to see the basics of +Now you should be able to run the NATS server on your machine and use the above code samples to see the basics of NATS messaging and persistence. ## What's Next @@ -85,4 +94,4 @@ NATS messaging and persistence. [Object Store](object-store/intro.md) is the built-in distributed persistent objects of arbitrary size built on top of JetStream. -[Services](services/intro.md) is the services protocol built on top of core NATS enabling discovery and monitoring of services you develop. +[Services](services/intro.md) is the Service Protocol built on top of core NATS enabling discovery and monitoring of services you develop. diff --git a/docs/documentation/jetstream/consume.md b/docs/documentation/jetstream/consume.md index 387dc1bc0..b51bb186b 100644 --- a/docs/documentation/jetstream/consume.md +++ b/docs/documentation/jetstream/consume.md @@ -5,13 +5,8 @@ You can access these methods from the consumer object created using JetStream co Install [NATS.Net](https://www.nuget.org/packages/NATS.Net) from Nuget. -[!code-csharp[](../../../tests/NATS.Net.DocsExamples/JetStream/IntroPage.cs#serializer)] - [!code-csharp[](../../../tests/NATS.Net.DocsExamples/JetStream/ConsumePage.cs#js)] -> [!NOTE] -> See also [Serialization](../serialization.md) section for more information about different serialization options. - ## Next Method Next method is the simplest way of retrieving messages from a stream. Every time you call the next method, you get @@ -57,7 +52,3 @@ will be created eventually: Depending on your application you should configure streams and consumers with appropriate settings so that the messages are processed and stored based on your requirements. - -> [!NOTE] -> This example used generated JSON serializer suitable for native AOT deployments. -> See also [Serialization](../serialization.md) section for more details. diff --git a/docs/documentation/jetstream/intro.md b/docs/documentation/jetstream/intro.md index 0a8f4797c..5584dd8f9 100644 --- a/docs/documentation/jetstream/intro.md +++ b/docs/documentation/jetstream/intro.md @@ -33,6 +33,10 @@ stream and will keep track of which messages were delivered and acknowledged by ```shell $ nats-server -js ``` +or +```shell +$ docker run nats -js +``` Install [NATS.Net](https://www.nuget.org/packages/NATS.Net) from Nuget. @@ -50,11 +54,9 @@ our case, meaning any subject prefixed with `orders.` e.g. `orders.new.123`. Hav Given that we have a record `Order`, we can publish and consume stream of `Order` objects: -[!code-csharp[](../../../tests/NATS.Net.DocsExamples/JetStream/IntroPage.cs#serializer)] +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/JetStream/IntroPage.cs#order-class)] -[!code-csharp[](../../../tests/NATS.Net.DocsExamples/JetStream/IntroPage.cs#js-serializer)] - -We can publish to the `shop_orders` stream and receive a confirmation that our message is persisted: +We can publish to `SHOP_ORDERS` stream and receive a confirmation that our message is persisted: [!code-csharp[](../../../tests/NATS.Net.DocsExamples/JetStream/IntroPage.cs#js-publish)] @@ -68,24 +70,22 @@ $ nats stream ls ├─────────────┬─────────────┬─────────────────────┬──────────┬───────┬──────────────┤ │ Name │ Description │ Created │ Messages │ Size │ Last Message │ ├─────────────┼─────────────┼─────────────────────┼──────────┼───────┼──────────────┤ -│ shop_orders │ │ 2023-09-12 10:25:52 │ 10 │ 600 B │ 10.41s │ +│ SHOP_ORDERS │ │ 2023-09-12 10:25:52 │ 10 │ 600 B │ 10.41s │ ╰─────────────┴─────────────┴─────────────────────┴──────────┴───────┴──────────────╯ ``` We need one more JetStream construct before we can start consuming our messages: a *consumer*: -```csharp -var consumer = await js.CreateConsumerAsync(stream: "shop_orders", consumer: "order_processor"); -``` +[!code-csharp[](../../../tests/NATS.Net.DocsExamples/JetStream/IntroPage.cs#js-consumer)] In JetStream, consumers are stored on the server. Clients don't need to worry about maintaining state separately. You can think of JetStream consumers as pointers to messages in streams stored on the NATS JetStream server. Let's see what our consumer's state is: ```shell -$ nats consumer report shop_orders +$ nats consumer report SHOP_ORDERS ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ Consumer report for shop_orders with 1 consumers │ +│ Consumer report for SHOP_ORDERS with 1 consumers │ ├─────────────────┬──────┬────────────┬──────────┬─────────────┬─────────────┬─────────────┬───────────┬─────────┤ │ Consumer │ Mode │ Ack Policy │ Ack Wait │ Ack Pending │ Redelivered │ Unprocessed │ Ack Floor │ Cluster │ ├─────────────────┼──────┼────────────┼──────────┼─────────────┼─────────────┼─────────────┼───────────┼─────────┤ @@ -95,18 +95,10 @@ $ nats consumer report shop_orders Check out [JetStream documentation](https://docs.nats.io/nats-concepts/jetstream) for more information on streams and consumers. -Finally, we're ready to consume the messages we persisted in `shop_orders` stream: +Finally, we're ready to consume the messages we persisted in `SHOP_ORDERS` stream: [!code-csharp[](../../../tests/NATS.Net.DocsExamples/JetStream/IntroPage.cs#consumer-consume)] -## Logging - -You should also hook your logger to `NatsConnection` to make sure all is working as expected or -to get help diagnosing any issues you might have: - -(For this example you need to add [Microsoft.Extensions.Logging.Console](https://www.nuget.org/packages/Microsoft.Extensions.Logging.Console) from Nuget.) -[!code-csharp[](../../../tests/NATS.Net.DocsExamples/Core/IntroPage.cs#logging)] - ## What's Next [Managing JetStream](manage.md) covers how to create, update, get, list and delete streams and consumers. diff --git a/docs/documentation/jetstream/manage.md b/docs/documentation/jetstream/manage.md index ed38413fe..7ba90c7ec 100644 --- a/docs/documentation/jetstream/manage.md +++ b/docs/documentation/jetstream/manage.md @@ -24,7 +24,7 @@ However, in practice streams are usually managed separately from the application line client](https://github.com/nats-io/natscli) you can create a stream interactively: ```shell -$ nats stream create my_events --subjects 'events.*' +$ nats stream create ORDERS --subjects 'orders.>' ? Storage [Use arrows to move, type to filter, ? for more help] > file memory diff --git a/docs/documentation/jetstream/publish.md b/docs/documentation/jetstream/publish.md index b375d428a..f1d7f585f 100644 --- a/docs/documentation/jetstream/publish.md +++ b/docs/documentation/jetstream/publish.md @@ -10,7 +10,7 @@ The subject must be configured on a stream to be persisted: or using the nats cli: ```shell -$ nats stream create orders --subjects 'orders.>' +$ nats stream create ORDERS --subjects 'orders.>' ``` Then you can publish to subjects captured by the stream: @@ -27,6 +27,3 @@ by ignoring duplicate messages as indicated by the message ID. Message ID is not as metadata, part of the message headers. [!code-csharp[](../../../tests/NATS.Net.DocsExamples/JetStream/PublishPage.cs#publish-duplicate)] - -> [!NOTE] -> See also [Serialization](../serialization.md) section for more information about different serialization options. diff --git a/docs/documentation/key-value-store/intro.md b/docs/documentation/key-value-store/intro.md index 8f2e588c9..821000f03 100644 --- a/docs/documentation/key-value-store/intro.md +++ b/docs/documentation/key-value-store/intro.md @@ -16,11 +16,7 @@ To be able to use KV you need to enable JetStream by running the server with `-j $ nats-server -js ``` -Install [NATS.Net](https://www.nuget.org/packages/NATS.Net) -and [NATS.Client.Serializers.Json](https://www.nuget.org/packages/NATS.Client.Serializers.Json) from Nuget. - -> [!NOTE] -> See also [Serialization](../serialization.md) section for more information about different serialization options. +Install [NATS.Net](https://www.nuget.org/packages/NATS.Net) from Nuget. Before we can do anything, we need a Key/Value Store context: @@ -40,7 +36,7 @@ $ nats kv ls ├─────────────┬─────────────┬─────────────────────┬──────┬────────┬─────────────┤ │ Bucket │ Description │ Created │ Size │ Values │ Last Update │ ├─────────────┼─────────────┼─────────────────────┼──────┼────────┼─────────────┤ -│ shop_orders │ │ 2023-10-12 15:29:40 │ 0 B │ 0 │ never │ +│ SHOP_ORDERS │ │ 2023-10-12 15:29:40 │ 0 B │ 0 │ never │ ╰─────────────┴─────────────┴─────────────────────┴──────┴────────┴─────────────╯ ``` @@ -53,8 +49,8 @@ saved value by its key: We can also confirm that our value is persisted by using the NATS command line: ```shell -$ nats kv get shop_orders order-1 -shop_orders > order-1 created @ 12 Oct 23 15:31 UTC +$ nats kv get SHOP_ORDERS order-1 +SHOP_ORDERS > order-1 created @ 12 Oct 23 15:31 UTC {"Id":1} ``` diff --git a/docs/documentation/security.md b/docs/documentation/security.md index b4900bed3..61a3496ae 100644 --- a/docs/documentation/security.md +++ b/docs/documentation/security.md @@ -1,36 +1,3 @@ -# Security - -NATS has a lot of [security features](https://docs.nats.io/nats-concepts/security) and .NET V2 client supports them all. -All you need to do is to pass your credentials to the connection. - -[!code-csharp[](../../tests/NATS.Net.DocsExamples/SecurityPage.cs#user-pass)] - -See also [user authentication tests](https://github.com/nats-io/nats.net.v2/blob/main/tests/NATS.Client.Core.Tests/NatsConnectionTest.Auth.cs) for more examples. - -## Implicit TLS Connections - -As of NATS server version 2.10.4 and later, the server supports implicit TLS connections. -This means that the client can connect to the server using the default port of 4222 and the server will automatically upgrade the connection to TLS. -This is useful for environments where TLS is required by default. - -[!code-csharp[](../../tests/NATS.Net.DocsExamples/SecurityPage.cs#tls-implicit)] - -## Mutual TLS Connections - -The [server can require TLS certificates from a client](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/auth_intro/tls_mutual_auth) to validate -the client certificate matches a known or trusted CA and to provide authentication. - -You can set the TLS options to use your client certificates when connecting to a server which requires TLS Mutual authentication. - -[!code-csharp[](../../tests/NATS.Net.DocsExamples/SecurityPage.cs#tls-mutual)] - -> [!TIP] -> #### Intermediate CA Certificates -> -> When connecting using intermediate CA certificates, it might not be possible to validate the client certificate and -> TLS handshake may fail. -> -> Unfortunately, for .NET client applications it isn't possible to pass additional intermediate certificates and the -> only solution is to add the certificates to the certificate store manually. -> -> See also .NET documentation on [Troubleshooting SslStream authentication issues](https://learn.microsoft.com/en-us/dotnet/core/extensions/sslstream-troubleshooting#intermediate-certificates-are-not-sent) +--- +redirect_url: advanced/security.html +--- diff --git a/docs/documentation/serialization.md b/docs/documentation/serialization.md index 1c0eabc96..3114e412a 100644 --- a/docs/documentation/serialization.md +++ b/docs/documentation/serialization.md @@ -1,118 +1,3 @@ -# Serialization - -NATS.Net supports serialization of messages using a simple interface [`INatsSerializer`](xref:NATS.Client.Core.INatsSerializer`1). - -By default, the client uses the [`NatsDefaultSerializer`](xref:NATS.Client.Core.NatsDefaultSerializer`1) -which can handle binary data, UTF8 strings and numbers. You can provide your own -serializer by implementing the [`INatsSerializer`](xref:NATS.Client.Core.INatsSerializer`1) interface or using the -[`NatsJsonContextSerializer`](xref:NATS.Client.Core.NatsJsonContextSerializer`1) for generated -JSON serialization. Serializers can also be chained together to provide multiple serialization formats typically -depending on the types being used. - -## Default Serializer Registry - -Default serializer is used when no serializer is provided to the connection options. It can handle binary data, UTF8 -strings and numbers. It uses the following rules to determine the type of the data: - -- If the data is a byte array, [`Memory`](https://learn.microsoft.com/dotnet/api/system.memory-1), [`IMemoryOwner`](https://learn.microsoft.com/dotnet/api/system.buffers.imemoryowner-1) or similar it is treated as binary data. -- If the data is a string or similar it is treated as UTF8 string. -- If the data is a primitive (for example `DateTime`, `int` or `double`. See also [`NatsUtf8PrimitivesSerializer`](xref:NATS.Client.Core.NatsUtf8PrimitivesSerializer`1)) it is treated as the primitive encoded as a UTF8 string. -- For any other type, the serializer will throw an exception. - -Serialising custom data formats can be done by implementing the serializer registry interface -[`INatsSerializerRegistry`](xref:NATS.Client.Core.INatsSerializerRegistry) -that can be used to provide a custom serializer instances for specific types. - -You would be using the default serializer by not specifying a serializer registry in connection options -or by setting it to the default explicitly: - -[!code-csharp[](../../tests/NATS.Net.DocsExamples/SerializationPage.cs#default)] - -The default serializer is designed to be used by developers who want to only work with binary data, and provide an out -of the box experience for basic use cases like sending and receiving UTF8 strings. - -## Using JSON Serializer Context - -The [`NatsJsonContextSerializer`](xref:NATS.Client.Core.NatsJsonContextSerializer`1) uses the [`System.Text.Json`](https://learn.microsoft.com/dotnet/api/system.text.json) serializer to serialize and deserialize messages. It relies -on the [`System.Text.Json` source generator](https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator/) -to generate the serialization code at compile time. This is the recommended JSON serializer for most use cases and it's -required for [Native AOT deployments](https://learn.microsoft.com/dotnet/core/deploying/native-aot). - -First you need to define your JSON classes and a context to generate the serialization code: -[!code-csharp[](../../tests/NATS.Net.DocsExamples/SerializationPage.cs#my-data)] - -Then you can use the [`NatsJsonContextSerializer`](xref:NATS.Client.Core.NatsJsonContextSerializer`1) to serialize and deserialize messages -by providing the registry ([`NatsJsonContextSerializerRegistry`](xref:NATS.Client.Core.NatsJsonContextSerializerRegistry)) with the connection options: - -[!code-csharp[](../../tests/NATS.Net.DocsExamples/SerializationPage.cs#my-data-usage)] - -You can also set the serializer for a specific subscription or publish call: - -[!code-csharp[](../../tests/NATS.Net.DocsExamples/SerializationPage.cs#my-data-publish)] - -## Using Custom Serializer - -You can also provide your own serializer by implementing the [`INatsSerializer`](xref:NATS.Client.Core.INatsSerializer`1) interface. This is useful if you need to -support a custom serialization format or if you need to support multiple serialization formats. - -Here is an example of a custom serializer that uses the Google ProtoBuf serializer to serialize and deserialize: - -[!code-csharp[](../../tests/NATS.Net.DocsExamples/SerializationPage.cs#custom-serializer)] - -You can then use the custom serializer as the default for the connection: - -[!code-csharp[](../../tests/NATS.Net.DocsExamples/SerializationPage.cs#custom)] - -## Using Multiple Serializers (chaining) - -You can also chain multiple serializers together to support multiple serialization formats. The first serializer in the -chain that can handle the data will be used. This is useful if you need to support multiple serialization formats and -reuse them. - -Note that chaining serializers is implemented by convention and not enforced by the [`INatsSerializer`](xref:NATS.Client.Core.INatsSerializer`1) -interface since the next serializer would not be exposed to external users of the interface. - -Here is an example of a serializer that uses the Google ProtoBuf serializer and the [`NatsJsonContextSerializer`](xref:NATS.Client.Core.NatsJsonContextSerializer`1) to -serialize and deserialize messages based on the type: - -[!code-csharp[](../../tests/NATS.Net.DocsExamples/SerializationPage.cs#mixed)] -[!code-csharp[](../../tests/NATS.Net.DocsExamples/SerializationPage.cs#chain)] - -## Dealing with Binary Data and Buffers - -The default serializer can handle binary data and buffers. This is typically archived by using [`IMemoryOwner`](https://learn.microsoft.com/dotnet/api/system.buffers.imemoryowner-1) -implementations. NATS .NET Client provides a [`NatsMemoryOwner`](xref:NATS.Client.Core.NatsMemoryOwner`1) implementation that can be used to allocate buffers. -The [`NatsMemoryOwner`](xref:NATS.Client.Core.NatsMemoryOwner`1) and [`NatsBufferWriter`](xref:NATS.Client.Core.NatsBufferWriter`1) (adapted from [.NET Community Toolkit](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/high-performance/memoryowner)) -are [`IMemoryOwner`](https://learn.microsoft.com/dotnet/api/system.buffers.imemoryowner-1) and [`IBufferWriter`](https://learn.microsoft.com/dotnet/api/system.buffers.ibufferwriter-1) implementations that use the [`ArrayPool`](https://learn.microsoft.com/dotnet/api/system.buffers.arraypool-1) -to allocate buffers. They can be used with the default serializer. - -[!code-csharp[](../../tests/NATS.Net.DocsExamples/SerializationPage.cs#buffers)] - -Advantage of using [`NatsMemoryOwner`](xref:NATS.Client.Core.NatsMemoryOwner`1) and [`NatsBufferWriter`](xref:NATS.Client.Core.NatsBufferWriter`1) is that they can be used with the default serializer and -they can be used to allocate buffers from the [`ArrayPool`](https://learn.microsoft.com/dotnet/api/system.buffers.arraypool-1) which can be reused. This is useful if you need to allocate -buffers for binary data and you want to avoid allocating buffers on for every operation (e.g. `new byte[]`) reducing -garbage collection pressure. They may also be useful for example, if your subscription may receive messages with -different formats and the only way to determine the format is by reading the message. - -## Using JSON Serialization with Reflection - -If you're not using [Native AOT deployments](https://learn.microsoft.com/dotnet/core/deploying/native-aot) you can use -the [`NatsJsonSerializer`](xref:NATS.Client.Serializers.Json.NatsJsonSerializer`1) to serialize and deserialize -messages. [`NatsJsonSerializer`](xref:NATS.Client.Serializers.Json.NatsJsonSerializer`1) uses [`System.Text.Json`](https://learn.microsoft.com/dotnet/api/system.text.json) -APIs that can work with types that are not registered to generate serialization code. - -Using this serializer is most useful for use cases where you want to send and receive JSON messages and you don't want to -worry about registering types. It's also useful for prototyping and testing. To use the serializer you need to install -the [`NATS.Client.Serializers.Json`](https://www.nuget.org/packages/NATS.Client.Serializers.Json) Nuget package. - -```shell -$ dotnet add package NATS.Client.Serializers.Json -``` - -Then set the serializer as the default for the connection: - -[!code-csharp[](../../tests/NATS.Net.DocsExamples/SerializationPage.cs#using-json)] -[!code-csharp[](../../tests/NATS.Net.DocsExamples/SerializationPage.cs#json)] - -Now you can use any type without worrying about registering it. [`System.Text.Json`](https://learn.microsoft.com/dotnet/api/system.text.json) -serializer will use reflection to serialize and deserialize the messages. +--- +redirect_url: advanced/serialization.html +--- diff --git a/docs/documentation/services/intro.md b/docs/documentation/services/intro.md index 71e2e417a..820452cfc 100644 --- a/docs/documentation/services/intro.md +++ b/docs/documentation/services/intro.md @@ -29,7 +29,7 @@ line client](https://github.com/nats-io/natscli) (make sure you have at least v0 ```shell $ nats --version -0.1.1 +0.1.4 ``` ```shell @@ -77,7 +77,3 @@ as well as an optional common subject prefix for all endpoints. You can group your endpoints optionally in different [queue groups](https://docs.nats.io/nats-concepts/core-nats/queue): [!code-csharp[](../../../tests/NATS.Net.DocsExamples/Services/IntroPage.cs#grp)] - -> [!NOTE] -> Examples in this page uses primitive types for simplicity. See [Serialization](../serialization.md) section for more -> information about different serialization options. diff --git a/docs/documentation/toc.yml b/docs/documentation/toc.yml index 4499ced6b..077cbf182 100644 --- a/docs/documentation/toc.yml +++ b/docs/documentation/toc.yml @@ -30,11 +30,15 @@ - name: Services href: services/intro.md -- name: Serialization - href: serialization.md - -- name: Security - href: security.md +- name: Advanced Options + href: advanced/intro.md + items: + - name: Serialization + href: advanced/serialization.md + - name: Security + href: advanced/security.md + - name: AOT Deployments + href: advanced/aot.md - name: Updating Documentation href: update-docs.md diff --git a/docs/documentation/update-docs.md b/docs/documentation/update-docs.md index 1b3ccea3e..4541ccbdd 100644 --- a/docs/documentation/update-docs.md +++ b/docs/documentation/update-docs.md @@ -14,7 +14,11 @@ dotnet tool update -g docfx Generate API documentation and run local server: ``` -$ git clone https://github.com/nats-io/nats.net.git -$ cd nats.net/docs -$ docfx docfx.json --serve +git clone https://github.com/nats-io/nats.net.git +cd nats.net/docs +docfx docfx.json --serve ``` + +You might not be a .NET developer, but still want to contribute to the documentation. +You can install the .NET SDK and use the `docfx` tool to generate the documentation. +Download the .NET SDK from [dot.net](https://dot.net). diff --git a/docs/index.md b/docs/index.md index c61f529d2..db48c3cd9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,13 +7,71 @@ a pleasant developer experience. [NATS](https://nats.io) is a lightweight and high-performance messaging system designed for asynchronous communication among different software components, with modern clustering, security and persistence streaming support out of the box, in a [single compact binary with no dependencies](https://nats.io/download/), available for any modern -platform, enabling vast variety of deployment options from edge, IoT, Kubernetes to bare-metal. +platform, enabling a vast variety of deployment options from edge, IoT, Kubernetes to bare-metal. -NATS.Net brings the power of NATS to the .NET platform, enabling developers to build distributed, cloud native, modern +NATS .NET brings the power of NATS to the .NET platform, enabling developers to build distributed, cloud native, modern applications using the tools and languages they already know and love. +> [!NOTE] +> **Don't confuse NuGet packages!** +> NATS .NET package on NuGet is called [NATS.Net](https://www.nuget.org/packages/NATS.Net). +> There is another package called `NATS.Client` which is the older version of the client library +> and will be deprecated eventually. + +> [!TIP] +> NATS .NET now supports **.NET Standard** 2.0 and 2.1 along with .NET 6.0 and 8.0, +> which means you can also use it with **.NET Framework** 4.6.2+ and **Unity** 2018.1+. + +## Hello, World! + +NATS team maintains a demo server you can reach at `demo.nats.io` globally. +You can use this server to quickly write your first NATS .NET application without setting up a server. + +> [!NOTE] +> If you're behind a firewall, you might not be able to reach the demo server. +> Check out the [introduction page](documentation/intro.md) for instructions on how to run your own server easily. + +Create two console applications, one for subscribing and one for publishing messages. + +### The Receiver + +```shell +mkdir HelloNats.Receiver +cd HelloNats.Receiver +dotnet new console +dotnet add package NATS.Net +``` + +[!code-csharp[](../tests/NATS.Net.DocsExamples/IndexPageSub.cs#demo)] + +```shell +dotnet run +``` + +### The Sender + +```shell +mkdir HelloNats.Sender +cd HelloNats.Sender +dotnet new console +dotnet add package NATS.Net +``` + +[!code-csharp[](../tests/NATS.Net.DocsExamples/IndexPagePub.cs#demo)] + +```shell +dotnet run +``` + +Try running the sender from more than one terminal to have some fun. Happy chatting! + +The receiver will listen to messages on the `hello.my_room.>` subject and your sender application +will send messages to the matching subjects. +This [subject has a wildcard](https://docs.nats.io/nats-concepts/subjects) `>` at the end, which means it will match +any subject starting with `hello.my_room.`. + ## What's Next -[Documentation](documentation/intro.md) can help you start writing code in no time. Just follow our quick start guides. +[Documentation](documentation/intro.md) can help you start creating your application in no time. Follow our quick start guides. [API](api/index.md) is the generated reference documentation. diff --git a/src/NATS.Client.Simplified/NatsClient.cs b/src/NATS.Client.Simplified/NatsClient.cs index eb489d2b3..87de0d6c0 100644 --- a/src/NATS.Client.Simplified/NatsClient.cs +++ b/src/NATS.Client.Simplified/NatsClient.cs @@ -31,6 +31,31 @@ public NatsClient( Connection = new NatsConnection(opts); } + /// + /// Initializes a new instance of the class. + /// + /// NATS client options. + /// Sets `SubPendingChannelFullMode` option. (default: wait) + /// + /// By default, the will be merged with the default options + /// overriding SerializationRegistry with + /// and SubPendingChannelFullMode with . + /// + public NatsClient(NatsOpts opts, BoundedChannelFullMode pending = BoundedChannelFullMode.Wait) + { + if (ReferenceEquals(opts.SerializerRegistry, NatsOpts.Default.SerializerRegistry)) + { + opts = opts with + { + SerializerRegistry = NatsClientDefaultSerializerRegistry.Default, + }; + } + + opts = opts with { SubPendingChannelFullMode = pending }; + + Connection = new NatsConnection(opts); + } + /// public INatsConnection Connection { get; } diff --git a/tests/NATS.Client.Simplified.Tests/ClientTest.cs b/tests/NATS.Client.Simplified.Tests/ClientTest.cs index 6d1a2e478..39aad7465 100644 --- a/tests/NATS.Client.Simplified.Tests/ClientTest.cs +++ b/tests/NATS.Client.Simplified.Tests/ClientTest.cs @@ -1,7 +1,10 @@ using System.Text; +using System.Threading.Channels; +using NATS.Client.Core; using NATS.Client.Core.Tests; using NATS.Client.JetStream; using NATS.Client.JetStream.Models; +using NATS.Client.Serializers.Json; using NATS.Net; // ReSharper disable AccessToDisposedClosure @@ -208,5 +211,34 @@ await Retry.Until( await Task.WhenAll(task1, task2, task3, task4, task5, task6); } + [Fact] + public void Client_opts_default_regitry() + { + var client = new NatsClient(new NatsOpts()); + Assert.Equal(NatsClientDefaultSerializerRegistry.Default, client.Connection.Opts.SerializerRegistry); + } + + [Fact] + public void Client_opts_custom_registry() + { + var registry = new NatsJsonSerializerRegistry(); + var client = new NatsClient(new NatsOpts { SerializerRegistry = registry }); + Assert.Equal(registry, client.Connection.Opts.SerializerRegistry); + } + + [Fact] + public void Client_opts_default_pending() + { + var client = new NatsClient(new NatsOpts()); + Assert.Equal(BoundedChannelFullMode.Wait, client.Connection.Opts.SubPendingChannelFullMode); + } + + [Fact] + public void Client_opts_set_pending() + { + var client = new NatsClient(new NatsOpts(), pending: BoundedChannelFullMode.DropNewest); + Assert.Equal(BoundedChannelFullMode.DropNewest, client.Connection.Opts.SubPendingChannelFullMode); + } + private record MyData(int Id, string Name); } diff --git a/tests/NATS.Net.DocsExamples/Advanced/IntroPage.cs b/tests/NATS.Net.DocsExamples/Advanced/IntroPage.cs new file mode 100644 index 000000000..9158a601d --- /dev/null +++ b/tests/NATS.Net.DocsExamples/Advanced/IntroPage.cs @@ -0,0 +1,108 @@ +// ReSharper disable RedundantTypeArgumentsOfMethod +// ReSharper disable SuggestVarOrType_SimpleTypes +// ReSharper disable SuggestVarOrType_Elsewhere +#pragma warning disable SA1123 +#pragma warning disable SA1124 +#pragma warning disable SA1509 +#pragma warning disable IDE0007 +#pragma warning disable IDE0008 + +using System.Threading.Channels; +using Microsoft.Extensions.Logging; +using NATS.Client.Core; + +namespace NATS.Net.DocsExamples.Advanced; + +public class IntroPage +{ + public async Task Run() + { + Console.WriteLine("____________________________________________________________"); + Console.WriteLine("NATS.Net.DocsExamples.Advanced.IntroPage"); + + { + #region lowlevel-sub + await using var nc = new NatsConnection(); + + // Connections are lazy, so we need to connect explicitly + // to avoid any races between subscription and publishing. + await nc.ConnectAsync(); + + await using var sub = await nc.SubscribeCoreAsync("foo"); + + for (var i = 0; i < 10; i++) + { + Console.WriteLine($" Publishing {i}..."); + await nc.PublishAsync("foo", i); + } + + // Signal subscription to stop + await nc.PublishAsync("foo", -1); + + // Messages have been collected in the subscription internal channel + // now we can drain them + await foreach (var msg in sub.Msgs.ReadAllAsync()) + { + Console.WriteLine($"Received {msg.Subject}: {msg.Data}\n"); + if (msg.Data == -1) + break; + } + + // We can unsubscribe from the subscription explicitly + // (otherwise dispose will do it for us) + await sub.UnsubscribeAsync(); + #endregion + } + + { + #region ping + await using var nc = new NatsClient(); + + TimeSpan rtt = await nc.PingAsync(); + + Console.WriteLine($"RTT to server: {rtt}"); + #endregion + } + + { + #region logging + using var loggerFactory = LoggerFactory.Create(configure: builder => builder.AddConsole()); + + var opts = new NatsOpts { LoggerFactory = loggerFactory }; + + await using var nc = new NatsClient(opts); + #endregion + } + + { + #region opts + + var opts = new NatsOpts + { + // You need to set pending in the constructor and not use + // the option here, as it will be ignored. + SubPendingChannelFullMode = BoundedChannelFullMode.DropOldest, + + // Your custom options + SerializerRegistry = new MyProtoBufSerializerRegistry(), + + // ... + }; + + await using var nc = new NatsClient(opts, pending: BoundedChannelFullMode.DropNewest); + #endregion + } + + { + #region opts2 + + var opts = new NatsOpts + { + // Your custom options + }; + + await using var nc = new NatsConnection(opts); + #endregion + } + } +} diff --git a/tests/NATS.Net.DocsExamples/SecurityPage.cs b/tests/NATS.Net.DocsExamples/Advanced/SecurityPage.cs similarity index 73% rename from tests/NATS.Net.DocsExamples/SecurityPage.cs rename to tests/NATS.Net.DocsExamples/Advanced/SecurityPage.cs index 7944429ec..7bc549986 100644 --- a/tests/NATS.Net.DocsExamples/SecurityPage.cs +++ b/tests/NATS.Net.DocsExamples/Advanced/SecurityPage.cs @@ -3,10 +3,8 @@ #pragma warning disable SA1509 using NATS.Client.Core; -using NATS.Client.JetStream; -using NATS.Client.JetStream.Models; -namespace NATS.Net.DocsExamples; +namespace NATS.Net.DocsExamples.Advanced; public class SecurityPage { @@ -17,7 +15,7 @@ public async Task Run() { #region user-pass - var opts = NatsOpts.Default with + var opts = new NatsOpts { AuthOpts = NatsAuthOpts.Default with { @@ -26,13 +24,13 @@ public async Task Run() }, }; - await using var nats = new NatsConnection(opts); + await using var nats = new NatsClient(opts); #endregion } { #region tls-implicit - var opts = NatsOpts.Default with + var opts = new NatsOpts { TlsOpts = new NatsTlsOpts { @@ -40,13 +38,13 @@ public async Task Run() }, }; - await using var nats = new NatsConnection(opts); + await using var nats = new NatsClient(opts); #endregion } { #region tls-mutual - var opts = NatsOpts.Default with + var opts = new NatsOpts { TlsOpts = new NatsTlsOpts { @@ -56,7 +54,7 @@ public async Task Run() }, }; - await using var nats = new NatsConnection(opts); + await using var nats = new NatsClient(opts); #endregion } } diff --git a/tests/NATS.Net.DocsExamples/SerializationPage.cs b/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs similarity index 67% rename from tests/NATS.Net.DocsExamples/SerializationPage.cs rename to tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs index f4e3dbcd2..8f88b0e0e 100644 --- a/tests/NATS.Net.DocsExamples/SerializationPage.cs +++ b/tests/NATS.Net.DocsExamples/Advanced/SerializationPage.cs @@ -17,11 +17,7 @@ using Google.Protobuf.Reflection; using NATS.Client.Core; -#region using-json -using NATS.Client.Serializers.Json; -#endregion - -namespace NATS.Net.DocsExamples; +namespace NATS.Net.DocsExamples.Advanced; public class SerializationPage { @@ -34,45 +30,10 @@ public async Task Run() Console.WriteLine(" #region default"); #region default - // Same as not specifying a serializer. - var natsOpts = NatsOpts.Default with { SerializerRegistry = NatsDefaultSerializerRegistry.Default }; - - await using var nats = new NatsConnection(natsOpts); - - var subscriber = Task.Run(async () => - { - // Default serializer knows how to deal with UTF8 strings, numbers and binary data. - await foreach (var msg in nats.SubscribeAsync("foo")) - { - // Check for the end of messages. - if (msg.Data == null) - break; + // Set your custom serializer registry as the default for the connection. + var opts = NatsOpts.Default with { SerializerRegistry = new MyProtoBufSerializerRegistry() }; - // Outputs 'Hello World' - Console.WriteLine(msg.Data); - } - }); - - // Give subscriber a chance to connect. - await Task.Delay(1000); - - // Default serializer knows how to deal with UTF8 strings, numbers and binary data. - await nats.PublishAsync(subject: "foo", data: "Hello World"); - - // Signal the end of messages by sending an empty payload. - await nats.PublishAsync(subject: "foo"); - - await subscriber; - #endregion - } - - { - Console.WriteLine(" #region json"); - - #region json - var natsOpts = NatsOpts.Default with { SerializerRegistry = NatsJsonSerializerRegistry.Default }; - - await using var nats = new NatsConnection(natsOpts); + await using var nc = new NatsClient(opts); #endregion } @@ -83,13 +44,13 @@ public async Task Run() // Set the custom serializer registry as the default for the connection. var myRegistry = new NatsJsonContextSerializerRegistry(MyJsonContext.Default, OtherJsonContext.Default); - var natsOpts = NatsOpts.Default with { SerializerRegistry = myRegistry }; + var opts = new NatsOpts { SerializerRegistry = myRegistry }; - await using var nats = new NatsConnection(natsOpts); + await using var nc = new NatsClient(opts); var subscriber = Task.Run(async () => { - await foreach (var msg in nats.SubscribeAsync("foo")) + await foreach (var msg in nc.SubscribeAsync("foo")) { // Outputs 'MyData { Id = 1, Name = bar }' Console.WriteLine(msg.Data); @@ -100,7 +61,7 @@ public async Task Run() // Give subscriber a chance to connect. await Task.Delay(1000); - await nats.PublishAsync(subject: "foo", data: new MyData { Id = 1, Name = "bar" }); + await nc.PublishAsync(subject: "foo", data: new MyData { Id = 1, Name = "bar" }); await subscriber; #endregion @@ -110,13 +71,13 @@ public async Task Run() Console.WriteLine(" #region my-data-publish"); #region my-data-publish - await using var nats = new NatsConnection(); + await using var nc = new NatsClient(); var serializer = new NatsJsonContextSerializer(MyJsonContext.Default); var subscriber = Task.Run(async () => { - await foreach (var msg in nats.SubscribeAsync("foo", serializer: serializer)) + await foreach (var msg in nc.SubscribeAsync("foo", serializer: serializer)) { // Outputs 'MyData { Id = 1, Name = bar }' Console.WriteLine(msg.Data); @@ -127,7 +88,7 @@ public async Task Run() // Give subscriber a chance to connect. await Task.Delay(1000); - await nats.PublishAsync(subject: "foo", data: new MyData { Id = 1, Name = "bar" }, serializer: serializer); + await nc.PublishAsync(subject: "foo", data: new MyData { Id = 1, Name = "bar" }, serializer: serializer); await subscriber; #endregion @@ -137,13 +98,13 @@ public async Task Run() Console.WriteLine(" #region custom"); #region custom - var natsOpts = NatsOpts.Default with { SerializerRegistry = new MyProtoBufSerializerRegistry() }; + var opts = new NatsOpts { SerializerRegistry = new MyProtoBufSerializerRegistry() }; - await using var nats = new NatsConnection(natsOpts); + await using var nc = new NatsClient(opts); var subscriber = Task.Run(async () => { - await foreach (var msg in nats.SubscribeAsync("foo")) + await foreach (var msg in nc.SubscribeAsync("foo")) { // Outputs '{ "id": 42, "name": "Marvin" }' Console.WriteLine(msg.Data); @@ -154,7 +115,7 @@ public async Task Run() // Give subscriber a chance to connect. await Task.Delay(1000); - await nats.PublishAsync(subject: "foo", data: new Greeting { Id = 42, Name = "Marvin" }); + await nc.PublishAsync(subject: "foo", data: new Greeting { Id = 42, Name = "Marvin" }); await subscriber; #endregion @@ -164,13 +125,13 @@ public async Task Run() Console.WriteLine(" #region chain"); #region chain - var natsOpts = NatsOpts.Default with { SerializerRegistry = new MixedSerializerRegistry() }; + var opts = new NatsOpts { SerializerRegistry = new MixedSerializerRegistry() }; - await using var nats = new NatsConnection(natsOpts); + await using var nc = new NatsClient(opts); var subscriber1 = Task.Run(async () => { - await foreach (var msg in nats.SubscribeAsync("greet")) + await foreach (var msg in nc.SubscribeAsync("greet")) { // Outputs '{ "id": 42, "name": "Marvin" }' Console.WriteLine(msg.Data); @@ -180,7 +141,7 @@ public async Task Run() var subscriber2 = Task.Run(async () => { - await foreach (var msg in nats.SubscribeAsync("data")) + await foreach (var msg in nc.SubscribeAsync("data")) { // Outputs 'MyData { Id = 1, Name = bar }' Console.WriteLine(msg.Data); @@ -191,8 +152,8 @@ public async Task Run() // Give subscribers a chance to connect. await Task.Delay(1000); - await nats.PublishAsync(subject: "greet", data: new Greeting { Id = 42, Name = "Marvin" }); - await nats.PublishAsync(subject: "data", data: new MyData { Id = 1, Name = "Bob" }); + await nc.PublishAsync(subject: "greet", data: new Greeting { Id = 42, Name = "Marvin" }); + await nc.PublishAsync(subject: "data", data: new MyData { Id = 1, Name = "Bob" }); await Task.WhenAll(subscriber1, subscriber2); @@ -203,15 +164,14 @@ public async Task Run() Console.WriteLine(" #region buffers"); #region buffers - // Same as not specifying a serializer. - var natsOpts = NatsOpts.Default with { SerializerRegistry = NatsDefaultSerializerRegistry.Default }; - - await using var nats = new NatsConnection(natsOpts); + // The default serializer knows how to deal with binary data types like NatsMemoryOwner. + // So, you can use it without specifying a serializer. + await using var nc = new NatsClient(); var subscriber = Task.Run(async () => { - // Default serializer knows how to deal with binary data types like NatsMemoryOwner. - await foreach (var msg in nats.SubscribeAsync>("foo")) + // The default serializer knows how to deal with binary data types like NatsMemoryOwner. + await foreach (var msg in nc.SubscribeAsync>("foo")) { // Check for the end of messages. if (msg.Data.Length == 0) @@ -229,7 +189,7 @@ public async Task Run() await Task.Delay(1000); // Don't reuse NatsBufferWriter, it's disposed and returned to the pool - // by the publisher after being written to network. + // by the publisher after being written to the network. var bw = new NatsBufferWriter(); var memory = bw.GetMemory(2); memory.Span[0] = (byte)'H'; @@ -237,10 +197,10 @@ public async Task Run() bw.Advance(2); // Default serializer knows how to deal with binary data types like NatsBufferWriter. - await nats.PublishAsync(subject: "foo", data: bw); + await nc.PublishAsync(subject: "foo", data: bw); // Signal the end of messages by sending an empty payload. - await nats.PublishAsync(subject: "foo"); + await nc.PublishAsync(subject: "foo"); await subscriber; #endregion @@ -319,7 +279,7 @@ public class MixedSerializerRegistry : INatsSerializerRegistry #endregion // Fake protobuf message. -// Normally this would be generated using protobuf compiler. +// Normally, this would be generated using protobuf compiler. public class Greeting : IBufferMessage { public int Id { get; set; } diff --git a/tests/NATS.Net.DocsExamples/Core/IntroPage.cs b/tests/NATS.Net.DocsExamples/Core/IntroPage.cs index 008b4ef04..d49d3a798 100644 --- a/tests/NATS.Net.DocsExamples/Core/IntroPage.cs +++ b/tests/NATS.Net.DocsExamples/Core/IntroPage.cs @@ -20,12 +20,9 @@ public async Task Run() subscription = Task.Run(async () => { #region sub - // required to serialize ad-hoc types - var opts = new NatsOpts { SerializerRegistry = NatsJsonSerializerRegistry.Default }; + await using var nc = new NatsClient(); - await using var nats = new NatsConnection(opts); - - await foreach (var msg in nats.SubscribeAsync("bar.>")) + await foreach (var msg in nc.SubscribeAsync("bar.>")) { if (msg.Subject == "bar.exit") break; @@ -40,30 +37,25 @@ public async Task Run() { #region pub - var opts = new NatsOpts { SerializerRegistry = NatsJsonSerializerRegistry.Default }; - await using var nats = new NatsConnection(opts); + await using var nc = new NatsClient(); for (var i = 0; i < 10; i++) { Console.WriteLine($" Publishing {i}..."); - await nats.PublishAsync($"bar.baz.{i}", new Bar(Id: i, Name: "Baz")); + await nc.PublishAsync($"bar.baz.{i}", new Bar(Id: i, Name: "Baz")); } - await nats.PublishAsync("bar.exit"); + await nc.PublishAsync("bar.exit"); #endregion + + for (var i = 0; i < 3; i++) + { + await Task.Delay(250); + await nc.PublishAsync("bar.exit"); + } } await subscription; - - { - #region logging - using var loggerFactory = LoggerFactory.Create(configure: builder => builder.AddConsole()); - - var opts = new NatsOpts { LoggerFactory = loggerFactory }; - - await using var nats = new NatsConnection(opts); - #endregion - } } } diff --git a/tests/NATS.Net.DocsExamples/Core/PubSubPage.cs b/tests/NATS.Net.DocsExamples/Core/PubSubPage.cs index bb367fc0d..0b31d365b 100644 --- a/tests/NATS.Net.DocsExamples/Core/PubSubPage.cs +++ b/tests/NATS.Net.DocsExamples/Core/PubSubPage.cs @@ -16,11 +16,11 @@ public async Task Run() { #region pubsub - await using var nats = new NatsConnection(); + await using var nc = new NatsClient(); var subscription = Task.Run(async () => { - await foreach (var msg in nats.SubscribeAsync("foo")) + await foreach (var msg in nc.SubscribeAsync("foo")) { Console.WriteLine($"Received {msg.Subject}: {msg.Data}\n"); @@ -35,49 +35,15 @@ public async Task Run() for (var i = 0; i < 10; i++) { Console.WriteLine($" Publishing {i}..."); - await nats.PublishAsync("foo", i); + await nc.PublishAsync("foo", i); } // Signal subscription to stop - await nats.PublishAsync("foo", -1); + await nc.PublishAsync("foo", -1); // Make sure subscription completes cleanly await subscription; #endregion } - - { - #region lowlevel - await using var nats = new NatsConnection(); - - // Connections are lazy, so we need to connect explicitly - // to avoid any races between subscription and publishing. - await nats.ConnectAsync(); - - await using var sub = await nats.SubscribeCoreAsync("foo"); - - for (var i = 0; i < 10; i++) - { - Console.WriteLine($" Publishing {i}..."); - await nats.PublishAsync("foo", i); - } - - // Signal subscription to stop - await nats.PublishAsync("foo", -1); - - // Messages have been collected in the subscription internal channel - // now we can drain them - await foreach (var msg in sub.Msgs.ReadAllAsync()) - { - Console.WriteLine($"Received {msg.Subject}: {msg.Data}\n"); - if (msg.Data == -1) - break; - } - - // We can unsubscribe from the subscription explicitly - // (otherwise dispose will do it for us) - await sub.UnsubscribeAsync(); - #endregion - } } } diff --git a/tests/NATS.Net.DocsExamples/Core/QueuePage.cs b/tests/NATS.Net.DocsExamples/Core/QueuePage.cs index 389457abb..eba85bdda 100644 --- a/tests/NATS.Net.DocsExamples/Core/QueuePage.cs +++ b/tests/NATS.Net.DocsExamples/Core/QueuePage.cs @@ -1,7 +1,11 @@ +// ReSharper disable MethodSupportsCancellation +// ReSharper disable AccessToDisposedClosure // ReSharper disable SuggestVarOrType_Elsewhere #pragma warning disable SA1123 #pragma warning disable SA1124 #pragma warning disable SA1509 +#pragma warning disable IDE0007 +#pragma warning disable IDE0008 using NATS.Client.Core; @@ -15,10 +19,12 @@ public async Task Run() Console.WriteLine("NATS.Net.DocsExamples.Core.QueuePage"); #region queue - await using var nats = new NatsConnection(); + await using var nc = new NatsClient(); + + // Create a cancellation token source to stop the subscriptions + using var cts = new CancellationTokenSource(); var replyTasks = new List(); - var cts = new CancellationTokenSource(); for (var i = 0; i < 3; i++) { @@ -28,7 +34,7 @@ public async Task Run() replyTasks.Add(Task.Run(async () => { // Retrieve messages until unsubscribed - await foreach (var msg in nats.SubscribeAsync("math.double", queueGroup: "maths-service", cancellationToken: cts.Token)) + await foreach (var msg in nc.SubscribeAsync("math.double", queueGroup: "maths-service", cancellationToken: cts.Token)) { Console.WriteLine($"[{replyTaskId}] Received request: {msg.Data}"); await msg.ReplyAsync($"Answer is: {2 * msg.Data}"); @@ -44,14 +50,14 @@ public async Task Run() // Send a few requests for (var i = 0; i < 10; i++) { - var reply = await nats.RequestAsync("math.double", i); + NatsMsg reply = await nc.RequestAsync("math.double", i); Console.WriteLine($"Reply: '{reply.Data}'"); } Console.WriteLine("Stopping..."); // Cancellation token will unsubscribe and complete the message loops - cts.Cancel(); + await cts.CancelAsync(); // Make sure all tasks finished cleanly await Task.WhenAll(replyTasks); diff --git a/tests/NATS.Net.DocsExamples/Core/ReqRepPage.cs b/tests/NATS.Net.DocsExamples/Core/ReqRepPage.cs index e8da71627..f49b41fe6 100644 --- a/tests/NATS.Net.DocsExamples/Core/ReqRepPage.cs +++ b/tests/NATS.Net.DocsExamples/Core/ReqRepPage.cs @@ -1,8 +1,13 @@ +// ReSharper disable ArrangeConstructorOrDestructorBody +// ReSharper disable ConvertToPrimaryConstructor // ReSharper disable SuggestVarOrType_Elsewhere #pragma warning disable SA1123 #pragma warning disable SA1124 #pragma warning disable SA1509 +#pragma warning disable IDE0007 +#pragma warning disable IDE0008 +using Microsoft.Extensions.Hosting; using NATS.Client.Core; namespace NATS.Net.DocsExamples.Core; @@ -14,36 +19,46 @@ public async Task Run() Console.WriteLine("____________________________________________________________"); Console.WriteLine("NATS.Net.DocsExamples.Core.ReqRepPage"); - var cts = new CancellationTokenSource(); - - var sub = Task.Run(async () => - { - #region sub - await using var nats = new NatsConnection(); - - await foreach (var msg in nats.SubscribeAsync("math.double").WithCancellation(cts.Token)) - { - Console.WriteLine($"Received request: {msg.Data}"); - - await msg.ReplyAsync($"Answer is: {2 * msg.Data}"); - } - #endregion - }); + await using var nc1 = new NatsClient(); + var myMathService = new MyMathService(nc1); + await myMathService.StartAsync(CancellationToken.None); await Task.Delay(1000); { #region reqrep - await using var nats = new NatsConnection(); + await using var nc = new NatsClient(); - var reply = await nats.RequestAsync("math.double", 2); + NatsMsg reply = await nc.RequestAsync("math.double", 2); Console.WriteLine($"Received reply: {reply.Data}"); #endregion } - cts.Cancel(); + await myMathService.StopAsync(CancellationToken.None); + } +} + +#region sub +public class MyMathService : BackgroundService +{ + private readonly INatsClient _natsClient; - await sub; + public MyMathService(INatsClient natsClient) + { + _natsClient = natsClient; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await foreach (var msg in _natsClient.SubscribeAsync("math.double", cancellationToken: stoppingToken)) + { + Console.WriteLine($"Received request: {msg.Data}"); + + var result = 2 * msg.Data; + + await msg.ReplyAsync(result, cancellationToken: stoppingToken); + } } } +#endregion diff --git a/tests/NATS.Net.DocsExamples/IndexPagePub.cs b/tests/NATS.Net.DocsExamples/IndexPagePub.cs new file mode 100644 index 000000000..08505d307 --- /dev/null +++ b/tests/NATS.Net.DocsExamples/IndexPagePub.cs @@ -0,0 +1,32 @@ +#pragma warning disable SA1123 +#pragma warning disable SA1124 +#pragma warning disable SA1509 + +namespace NATS.Net.DocsExamples; + +public class IndexPagePub +{ + public async Task Run() + { + Console.WriteLine("____________________________________________________________"); + Console.WriteLine("NATS.Net.DocsExamples.IndexPagePub"); + { + #region demo + await using var nc = new NatsClient("demo.nats.io"); + + Console.Write("Enter your room: "); + var room = Console.ReadLine(); + + Console.Write("Enter your name: "); + var name = Console.ReadLine(); + + while (true) + { + Console.Write("Enter a message to publish: "); + var message = Console.ReadLine(); + await nc.PublishAsync(subject: $"hello.{room}.{name}", data: message); + } + #endregion + } + } +} diff --git a/tests/NATS.Net.DocsExamples/IndexPageSub.cs b/tests/NATS.Net.DocsExamples/IndexPageSub.cs new file mode 100644 index 000000000..6c230c658 --- /dev/null +++ b/tests/NATS.Net.DocsExamples/IndexPageSub.cs @@ -0,0 +1,29 @@ +#pragma warning disable SA1123 +#pragma warning disable SA1124 +#pragma warning disable SA1509 + +namespace NATS.Net.DocsExamples; + +public class IndexPageSub +{ + public async Task Run() + { + Console.WriteLine("____________________________________________________________"); + Console.WriteLine("NATS.Net.DocsExamples.IndexPageSub"); + { + #region demo + await using var nc = new NatsClient("demo.nats.io"); + + Console.Write("Enter your room: "); + var room = Console.ReadLine(); + + Console.WriteLine($"Listening for messages on 'hello.{room}.>'"); + + await foreach (var msg in nc.SubscribeAsync(subject: $"hello.{room}.>")) + { + Console.WriteLine($"Received: {msg.Subject}: {msg.Data}"); + } + #endregion + } + } +} diff --git a/tests/NATS.Net.DocsExamples/IntroPage.cs b/tests/NATS.Net.DocsExamples/IntroPage.cs index c62f24937..7cd0c22e4 100644 --- a/tests/NATS.Net.DocsExamples/IntroPage.cs +++ b/tests/NATS.Net.DocsExamples/IntroPage.cs @@ -17,15 +17,16 @@ public async Task Run() { #region core-nats - await using var nats = new NatsConnection(); + await using var nc = new NatsClient(); - var cts = new CancellationTokenSource(); + // We will use a cancellation token to stop the subscription + using var cts = new CancellationTokenSource(); var subscription = Task.Run(async () => { - await foreach (var msg in nats.SubscribeAsync(subject: "foo").WithCancellation(cts.Token)) + await foreach (var msg in nc.SubscribeAsync(subject: "greet.*", cancellationToken: cts.Token)) { - Console.WriteLine($"Received: {msg.Data}"); + Console.WriteLine($"Received: {msg.Subject}: {msg.Data}"); } }); @@ -34,14 +35,14 @@ public async Task Run() for (var i = 0; i < 10; i++) { - await nats.PublishAsync(subject: "foo", data: $"Hello, World! {i}"); + await nc.PublishAsync(subject: $"greet.{i}", data: $"Hello, World! {i}"); } - // Give subscription time to receive messages + // Give subscription task time to receive messages await Task.Delay(1000); // Unsubscribe - cts.Cancel(); + await cts.CancelAsync(); await subscription; #endregion @@ -62,7 +63,7 @@ public async Task Run() { await using var nats = new NatsConnection(); var js = new NatsJSContext(nats); - await js.DeleteStreamAsync("orders"); + await js.DeleteStreamAsync("ORDERS"); await Task.Delay(1000); } catch (NatsJSApiException) @@ -71,34 +72,35 @@ public async Task Run() { #region jetstream - await using var nats = new NatsConnection(); - var js = new NatsJSContext(nats); + await using var nc = new NatsClient(); + var js = nc.CreateJetStreamContext(); - // Create a stream to store the messages - await js.CreateStreamAsync(new StreamConfig(name: "orders", subjects: new[] { "orders.*" })); + // Create a stream to store the messages those subjects start with "orders." + await js.CreateStreamAsync(new StreamConfig(name: "ORDERS", subjects: ["orders.>"])); for (var i = 0; i < 10; i++) { // Publish a message to the stream. The message will be stored in the stream // because the published subject matches one of the the stream's subjects. - var ack = await js.PublishAsync(subject: "orders.new", data: $"order {i}"); + var ack = await js.PublishAsync(subject: $"orders.new.{i}", data: $"order {i}"); + + // Ensure the message is stored in the stream. + // Returned ack makes the JetStream publish different from the core publish. ack.EnsureSuccess(); } // Create a consumer to receive the messages - var consumer = await js.CreateOrUpdateConsumerAsync("orders", new ConsumerConfig("order_processor")); + var consumer = await js.CreateOrUpdateConsumerAsync("ORDERS", new ConsumerConfig("order_processor")); - await foreach (var jsMsg in consumer.ConsumeAsync()) + // We will use a cancellation token to stop the consume loop + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)); + + await foreach (var jsMsg in consumer.ConsumeAsync(cancellationToken: cts.Token)) { - Console.WriteLine($"Processed: {jsMsg.Data}"); - await jsMsg.AckAsync(); + Console.WriteLine($"Processed: {jsMsg.Subject}: {jsMsg.Data} ({jsMsg.Metadata?.Sequence.Stream}/{jsMsg.Metadata?.NumPending})"); - // Process only 10 messages - // (message order might be different in different scenarios) - if (jsMsg.Data == "order 9") - { - break; - } + // Acknowledge the message is processed and the consumer can move to the next message + await jsMsg.AckAsync(cancellationToken: cts.Token); } #endregion } diff --git a/tests/NATS.Net.DocsExamples/JetStream/ConsumePage.cs b/tests/NATS.Net.DocsExamples/JetStream/ConsumePage.cs index 0905a7546..a8de763f0 100644 --- a/tests/NATS.Net.DocsExamples/JetStream/ConsumePage.cs +++ b/tests/NATS.Net.DocsExamples/JetStream/ConsumePage.cs @@ -25,7 +25,7 @@ public async Task Run() { await using var nats1 = new NatsConnection(); var js1 = new NatsJSContext(nats1); - await js1.DeleteStreamAsync("orders"); + await js1.DeleteStreamAsync("ORDERS"); await Task.Delay(1000); } catch (NatsJSApiException) @@ -36,7 +36,7 @@ public async Task Run() { await using var nats1 = new NatsConnection(); var js1 = new NatsJSContext(nats1); - await js1.DeleteStreamAsync("shop_orders"); + await js1.DeleteStreamAsync("SHOP_ORDERS"); await Task.Delay(1000); } catch (NatsJSApiException) @@ -44,27 +44,30 @@ public async Task Run() } #region js - await using var nats = new NatsConnection(); - var js = new NatsJSContext(nats); + await using var nc = new NatsClient(); + var js = nc.CreateJetStreamContext(); - await js.CreateStreamAsync(new StreamConfig(name: "orders", subjects: new[] { "orders.>" })); + await js.CreateStreamAsync(new StreamConfig(name: "ORDERS", subjects: ["orders.>"])); - var consumer = await js.CreateOrUpdateConsumerAsync(stream: "orders", new ConsumerConfig("order_processor")); - - // Use generated JSON serializer - var orderSerializer = new NatsJsonContextSerializer(OrderJsonSerializerContext.Default); + var consumer = await js.CreateOrUpdateConsumerAsync(stream: "ORDERS", new ConsumerConfig("order_processor")); // Publish new order messages - await nats.PublishAsync(subject: "orders.new.1", data: new Order(OrderId: 1), serializer: orderSerializer); + var ack = await js.PublishAsync(subject: "orders.new.1", data: new Order { Id = 1 }); + + // If you want exceptions to be thrown, you can use EnsureSuccess() method instead + if (!ack.IsSuccess()) + { + // handle error + } #endregion { #region consumer-next - var next = await consumer.NextAsync(serializer: orderSerializer); + var next = await consumer.NextAsync(); if (next is { } msg) { - Console.WriteLine($"Processing {msg.Subject}: {msg.Data.OrderId}..."); + Console.WriteLine($"Processing {msg.Subject}: {msg.Data.Id}..."); await msg.AckAsync(); } #endregion @@ -74,7 +77,7 @@ public async Task Run() var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1)); var cancellationToken = cts.Token; #region consumer-fetch - await foreach (var msg in consumer.FetchAsync(new NatsJSFetchOpts { MaxMsgs = 1000 }, serializer: orderSerializer).WithCancellation(cancellationToken)) + await foreach (var msg in consumer.FetchAsync(new NatsJSFetchOpts { MaxMsgs = 1000 }).WithCancellation(cancellationToken)) { // Process message await msg.AckAsync(); @@ -89,7 +92,7 @@ public async Task Run() var cancellationToken = cts.Token; #region consumer-consume // Continuously consume a batch of messages (1000 by default) - await foreach (var msg in consumer.ConsumeAsync(serializer: orderSerializer).WithCancellation(cancellationToken)) + await foreach (var msg in consumer.ConsumeAsync().WithCancellation(cancellationToken)) { // Process message await msg.AckAsync(); @@ -102,6 +105,7 @@ public async Task Run() { var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1)); var cancellationToken = cts.Token; + #region consumer-consume-error while (!cancellationToken.IsCancellationRequested) { @@ -109,7 +113,7 @@ public async Task Run() { await consumer.RefreshAsync(cancellationToken); // or try to recreate consumer - await foreach (var msg in consumer.ConsumeAsync(serializer: orderSerializer).WithCancellation(cancellationToken)) + await foreach (var msg in consumer.ConsumeAsync().WithCancellation(cancellationToken)) { // Process message await msg.AckAsync(cancellationToken: cancellationToken); diff --git a/tests/NATS.Net.DocsExamples/JetStream/IntroPage.cs b/tests/NATS.Net.DocsExamples/JetStream/IntroPage.cs index 9583bbcf4..6fa108227 100644 --- a/tests/NATS.Net.DocsExamples/JetStream/IntroPage.cs +++ b/tests/NATS.Net.DocsExamples/JetStream/IntroPage.cs @@ -1,6 +1,5 @@ // ReSharper disable SuggestVarOrType_Elsewhere -using System.Text.Json.Serialization; using NATS.Client.Core; using NATS.Client.JetStream; using NATS.Client.JetStream.Models; @@ -23,7 +22,7 @@ public async Task Run() { await using var nats1 = new NatsConnection(); var js1 = new NatsJSContext(nats1); - await js1.DeleteStreamAsync("shop_orders"); + await js1.DeleteStreamAsync("SHOP_ORDERS"); await Task.Delay(1000); } catch (NatsJSApiException) @@ -34,7 +33,7 @@ public async Task Run() { await using var nats1 = new NatsConnection(); var js1 = new NatsJSContext(nats1); - await js1.DeleteStreamAsync("orders"); + await js1.DeleteStreamAsync("ORDERS"); await Task.Delay(1000); } catch (NatsJSApiException) @@ -42,17 +41,12 @@ public async Task Run() } #region js-connection - await using var nats = new NatsConnection(); - var js = new NatsJSContext(nats); + await using var nc = new NatsClient(); + var js = nc.CreateJetStreamContext(); #endregion #region js-stream - await js.CreateStreamAsync(new StreamConfig(name: "shop_orders", subjects: new[] { "orders.>" })); - #endregion - - #region js-serializer - // Use generated JSON serializer - var orderSerializer = new NatsJsonContextSerializer(OrderJsonSerializerContext.Default); + await js.CreateStreamAsync(new StreamConfig(name: "SHOP_ORDERS", subjects: ["orders.>"])); #endregion #region js-publish @@ -60,20 +54,20 @@ public async Task Run() for (var i = 0; i < 10; i++) { // Notice we're using JetStream context to publish and receive ACKs - var ack = await js.PublishAsync($"orders.new.{i}", new Order(i), serializer: orderSerializer); + var ack = await js.PublishAsync($"orders.new.{i}", new Order { Id = i }); ack.EnsureSuccess(); } #endregion #region js-consumer - var consumer = await js.CreateOrUpdateConsumerAsync(stream: "shop_orders", new ConsumerConfig("order_processor")); + var consumer = await js.CreateOrUpdateConsumerAsync(stream: "SHOP_ORDERS", new ConsumerConfig("order_processor")); #endregion var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1)); var cancellationToken = cts.Token; #region consumer-consume - await foreach (var msg in consumer.ConsumeAsync(serializer: orderSerializer).WithCancellation(cancellationToken)) + await foreach (var msg in consumer.ConsumeAsync().WithCancellation(cancellationToken)) { var order = msg.Data; Console.WriteLine($"Processing {msg.Subject} {order}..."); @@ -84,16 +78,9 @@ public async Task Run() } } -#region serializer -// Generate serializer context at compile time, ready for native AOT deployments -[JsonSerializable(typeof(Order))] -public partial class OrderJsonSerializerContext : JsonSerializerContext -{ -} - -public record Order(int OrderId) +#region order-class +public record Order { - public int OrderId { get; set; } = OrderId; + public int Id { get; init; } } - #endregion diff --git a/tests/NATS.Net.DocsExamples/JetStream/ManagingPage.cs b/tests/NATS.Net.DocsExamples/JetStream/ManagingPage.cs index 121da28b6..25a91f357 100644 --- a/tests/NATS.Net.DocsExamples/JetStream/ManagingPage.cs +++ b/tests/NATS.Net.DocsExamples/JetStream/ManagingPage.cs @@ -1,7 +1,6 @@ // ReSharper disable RedundantAssignment // ReSharper disable SuggestVarOrType_Elsewhere -using NATS.Client.Core; using NATS.Client.JetStream; using NATS.Client.JetStream.Models; @@ -19,14 +18,14 @@ public async Task Run() Console.WriteLine("NATS.Net.DocsExamples.JetStream.ManagingPage"); #region js - await using var nats = new NatsConnection(); + await using var nc = new NatsClient(); - var js = new NatsJSContext(nats); + var js = nc.CreateJetStreamContext(); #endregion try { - await js.DeleteStreamAsync("shop_orders"); + await js.DeleteStreamAsync("SHOP_ORDERS"); await Task.Delay(1000); } catch (NatsJSApiException) @@ -35,7 +34,7 @@ public async Task Run() try { - await js.DeleteStreamAsync("orders"); + await js.DeleteStreamAsync("ORDERS"); await Task.Delay(1000); } catch (NatsJSApiException) @@ -43,20 +42,20 @@ public async Task Run() } #region stream - await js.CreateStreamAsync(new StreamConfig(name: "orders", subjects: new[] { "orders.>" })); + await js.CreateStreamAsync(new StreamConfig(name: "ORDERS", subjects: new[] { "orders.>" })); #endregion { #region consumer-create // Create or get a consumer - var consumer = await js.CreateOrUpdateConsumerAsync(stream: "orders", new ConsumerConfig("order_processor")); + var consumer = await js.CreateOrUpdateConsumerAsync(stream: "ORDERS", new ConsumerConfig("order_processor")); #endregion } { #region consumer-get // Get an existing consumer - var consumer = await js.GetConsumerAsync(stream: "orders", consumer: "order_processor"); + var consumer = await js.GetConsumerAsync(stream: "ORDERS", consumer: "order_processor"); #endregion } @@ -72,7 +71,7 @@ public async Task Run() DurableName = "durable_processor", }; - var consumer = await js.CreateOrUpdateConsumerAsync(stream: "orders", durableConfig); + var consumer = await js.CreateOrUpdateConsumerAsync(stream: "ORDERS", durableConfig); Console.WriteLine($"Consumer Name: {consumer.Info.Name}"); // durable_processor Console.WriteLine($"Consumer DurableName: {consumer.Info.Config.DurableName}"); // durable_processor @@ -84,7 +83,7 @@ public async Task Run() // Create an ephemeral consumer by not setting durable name var ephemeralConfig = new ConsumerConfig(); - var consumer = await js.CreateOrUpdateConsumerAsync(stream: "orders", ephemeralConfig); + var consumer = await js.CreateOrUpdateConsumerAsync(stream: "ORDERS", ephemeralConfig); Console.WriteLine($"Consumer Name: {consumer.Info.Name}"); // e.g. Z8YlwrP9 (server assigned random name) #endregion diff --git a/tests/NATS.Net.DocsExamples/JetStream/PublishPage.cs b/tests/NATS.Net.DocsExamples/JetStream/PublishPage.cs index 696658e27..d1917915d 100644 --- a/tests/NATS.Net.DocsExamples/JetStream/PublishPage.cs +++ b/tests/NATS.Net.DocsExamples/JetStream/PublishPage.cs @@ -1,6 +1,5 @@ // ReSharper disable SuggestVarOrType_Elsewhere -using System.Text.Json.Serialization; using NATS.Client.Core; using NATS.Client.JetStream; using NATS.Client.JetStream.Models; @@ -23,7 +22,7 @@ public async Task Run() { await using var nats1 = new NatsConnection(); var js1 = new NatsJSContext(nats1); - await js1.DeleteStreamAsync("shop_orders"); + await js1.DeleteStreamAsync("SHOP_ORDERS"); await Task.Delay(1000); } catch (NatsJSApiException) @@ -34,7 +33,7 @@ public async Task Run() { await using var nats1 = new NatsConnection(); var js1 = new NatsJSContext(nats1); - await js1.DeleteStreamAsync("orders"); + await js1.DeleteStreamAsync("ORDERS"); await Task.Delay(1000); } catch (NatsJSApiException) @@ -43,40 +42,34 @@ public async Task Run() { #region js - await using var nats = new NatsConnection(); - var js = new NatsJSContext(nats); + await using var nc = new NatsClient(); + var js = nc.CreateJetStreamContext(); - await js.CreateStreamAsync(new StreamConfig(name: "orders", subjects: new[] { "orders.>" })); + await js.CreateStreamAsync(new StreamConfig(name: "ORDERS", subjects: new[] { "orders.>" })); #endregion } { #region publish - await using var nats = new NatsConnection(); - var js = new NatsJSContext(nats); + await using var nc = new NatsClient(); + var js = nc.CreateJetStreamContext(); - var order = new Order(OrderId: 1); + var order = new Order { Id = 1 }; - // Use generated JSON serializer - var orderSerializer = new NatsJsonContextSerializer(OrderJsonSerializerContext.Default); - - var ack = await js.PublishAsync("orders.new.1", order, serializer: orderSerializer); + var ack = await js.PublishAsync("orders.new.1", order); ack.EnsureSuccess(); #endregion } { - await using var nats = new NatsConnection(); - var js = new NatsJSContext(nats); - - var order = new Order(OrderId: 1); + #region publish-duplicate + await using var nc = new NatsClient(); + var js = nc.CreateJetStreamContext(); - // Use generated JSON serializer - var orderSerializer = new NatsJsonContextSerializer(OrderJsonSerializerContext.Default); + var order = new Order { Id = 1 }; - #region publish-duplicate - var ack = await js.PublishAsync(subject: "orders.new.1", data: order, opts: new NatsJSPubOpts { MsgId = "1" }, serializer: orderSerializer); + var ack = await js.PublishAsync(subject: "orders.new.1", data: order, opts: new NatsJSPubOpts { MsgId = "1" }); if (ack.Duplicate) { // A message with the same ID was published before diff --git a/tests/NATS.Net.DocsExamples/KeyValueStore/IntroPage.cs b/tests/NATS.Net.DocsExamples/KeyValueStore/IntroPage.cs index fbd618536..8bbf2b13c 100644 --- a/tests/NATS.Net.DocsExamples/KeyValueStore/IntroPage.cs +++ b/tests/NATS.Net.DocsExamples/KeyValueStore/IntroPage.cs @@ -22,18 +22,13 @@ public async Task Run() Console.WriteLine("NATS.Net.DocsExamples.KeyValueStore.IntroPage"); #region kv - // required to serialize ad-hoc types - var opts = new NatsOpts { SerializerRegistry = NatsJsonSerializerRegistry.Default }; - - await using var nats = new NatsConnection(opts); - - var js = new NatsJSContext(nats); - var kv = new NatsKVContext(js); + await using var nc = new NatsClient(); + var kv = nc.CreateKeyValueStoreContext(); #endregion try { - await kv.DeleteStoreAsync("shop_orders"); + await kv.DeleteStoreAsync("SHOP_ORDERS"); await Task.Delay(1000); } catch (NatsJSApiException) @@ -41,7 +36,7 @@ public async Task Run() } #region store - var store = await kv.CreateStoreAsync("shop_orders"); + var store = await kv.CreateStoreAsync("SHOP_ORDERS"); #endregion { diff --git a/tests/NATS.Net.DocsExamples/NATS.Net.DocsExamples.csproj b/tests/NATS.Net.DocsExamples/NATS.Net.DocsExamples.csproj index 2147070a2..fd0c25fff 100644 --- a/tests/NATS.Net.DocsExamples/NATS.Net.DocsExamples.csproj +++ b/tests/NATS.Net.DocsExamples/NATS.Net.DocsExamples.csproj @@ -2,14 +2,13 @@ Exe - net6.0 + net8.0 enable enable false - diff --git a/tests/NATS.Net.DocsExamples/ObjectStore/IntroPage.cs b/tests/NATS.Net.DocsExamples/ObjectStore/IntroPage.cs index b9e3a9882..7421324a2 100644 --- a/tests/NATS.Net.DocsExamples/ObjectStore/IntroPage.cs +++ b/tests/NATS.Net.DocsExamples/ObjectStore/IntroPage.cs @@ -23,9 +23,8 @@ public async Task Run() Console.WriteLine("NATS.Net.DocsExamples.ObjectStore.IntroPage"); #region obj - await using var nats = new NatsConnection(); - var js = new NatsJSContext(nats); - var obj = new NatsObjContext(js); + await using var nc = new NatsClient(); + var obj = nc.CreateObjectStoreContext(); #endregion try diff --git a/tests/NATS.Net.DocsExamples/Program.cs b/tests/NATS.Net.DocsExamples/Program.cs index d64d84713..1b16e60a1 100644 --- a/tests/NATS.Net.DocsExamples/Program.cs +++ b/tests/NATS.Net.DocsExamples/Program.cs @@ -1,3 +1,18 @@ +#pragma warning disable SA1515 +#pragma warning disable SA1512 + +if (args.Length > 0) +{ + if (args[0] == "demo-sub") + { + await new NATS.Net.DocsExamples.IndexPageSub().Run(); + } + else if (args[0] == "demo-pub") + { + await new NATS.Net.DocsExamples.IndexPagePub().Run(); + } +} + await new NATS.Net.DocsExamples.IntroPage().Run(); await new NATS.Net.DocsExamples.Core.IntroPage().Run(); await new NATS.Net.DocsExamples.Core.PubSubPage().Run(); @@ -9,7 +24,7 @@ await new NATS.Net.DocsExamples.KeyValueStore.IntroPage().Run(); await new NATS.Net.DocsExamples.ObjectStore.IntroPage().Run(); await new NATS.Net.DocsExamples.Services.IntroPage().Run(); -await new NATS.Net.DocsExamples.SecurityPage().Run(); -await new NATS.Net.DocsExamples.SerializationPage().Run(); +await new NATS.Net.DocsExamples.Advanced.SecurityPage().Run(); +await new NATS.Net.DocsExamples.Advanced.SerializationPage().Run(); Console.WriteLine("Bye"); diff --git a/tests/NATS.Net.DocsExamples/Services/IntroPage.cs b/tests/NATS.Net.DocsExamples/Services/IntroPage.cs index 7bec62b16..8497f57f4 100644 --- a/tests/NATS.Net.DocsExamples/Services/IntroPage.cs +++ b/tests/NATS.Net.DocsExamples/Services/IntroPage.cs @@ -25,8 +25,8 @@ public async Task Run() Console.WriteLine("NATS.Net.DocsExamples.Services.IntroPage"); #region svc - await using var nats = new NatsConnection(); - var svc = new NatsSvcContext(nats); + await using var nc = new NatsClient(); + var svc = nc.CreateServicesContext(); #endregion #region add @@ -36,6 +36,14 @@ public async Task Run() #region endpoint await testService.AddEndpointAsync(name: "divide42", handler: async m => { + // Handle exceptions which may occur during message processing, + // usually due to serialization errors + if (m.Exception != null) + { + await m.ReplyErrorAsync(500, m.Exception.Message); + return; + } + if (m.Data == 0) { await m.ReplyErrorAsync(400, "Division by zero");