From f301b86a1e5d2384d9aed96a5eef53a22b10d3f9 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Wed, 7 Jan 2026 14:23:49 +0000 Subject: [PATCH 1/4] Add API Compat for TFMs --- .github/workflows/apicompat.yml | 27 ++++++++++++ apicompat.suppression.xml | 20 +++++++++ scripts/apicompat.sh | 76 +++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 .github/workflows/apicompat.yml create mode 100644 apicompat.suppression.xml create mode 100644 scripts/apicompat.sh diff --git a/.github/workflows/apicompat.yml b/.github/workflows/apicompat.yml new file mode 100644 index 000000000..18dfe6f12 --- /dev/null +++ b/.github/workflows/apicompat.yml @@ -0,0 +1,27 @@ +name: API Compatibility + +on: + pull_request: {} + push: + branches: + - main + +jobs: + apicompat: + name: Check TFM API Compatibility + runs-on: ubuntu-latest + env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + NUGET_XMLDOC_MODE: skip + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup dotnet + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + + - name: Run API Compatibility Check + run: ./scripts/apicompat.sh --build diff --git a/apicompat.suppression.xml b/apicompat.suppression.xml new file mode 100644 index 000000000..881f5df51 --- /dev/null +++ b/apicompat.suppression.xml @@ -0,0 +1,20 @@ + + + + + + + CP0001 + T:NATS.Client.Core.SslClientAuthenticationOptions + + + + CP0002 + M:NATS.Client.Core.NatsTlsOpts.get_ConfigureClientAuthentication + + + + CP0002 + M:NATS.Client.Hosting.NatsHostingExtensions.AddNats(Microsoft.Extensions.DependencyInjection.IServiceCollection,System.Int32,System.Func{NATS.Client.Core.NatsOpts,NATS.Client.Core.NatsOpts},System.Action{NATS.Client.Core.NatsConnection}) + + diff --git a/scripts/apicompat.sh b/scripts/apicompat.sh new file mode 100644 index 000000000..aa64f3576 --- /dev/null +++ b/scripts/apicompat.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# API Compatibility Check Script +# Verifies that APIs are compatible across different TFMs +# Usage: ./scripts/apicompat.sh [--build] + +set -e + +# Get script directory (works on both Windows and Linux) +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +PROJECTS="NATS.Client.Abstractions NATS.Client.Core NATS.Client.Hosting NATS.Client.JetStream NATS.Client.KeyValueStore NATS.Client.ObjectStore NATS.Client.Services NATS.Client.Serializers.Json NATS.Client.Simplified NATS.Net NATS.Extensions.Microsoft.DependencyInjection" + +SUPPRESSION_FILE="$ROOT_DIR/apicompat.suppression.xml" + +# Check if apicompat is installed +if ! command -v apicompat &> /dev/null; then + echo "Installing Microsoft.DotNet.ApiCompat.Tool..." + dotnet tool install --global Microsoft.DotNet.ApiCompat.Tool +fi + +# Build if requested +if [ "$1" = "--build" ]; then + echo "Building solution..." + dotnet build -c Release "$ROOT_DIR/NATS.Net.sln" +fi + +check_compat() { + local baseline_tfm=$1 + local target_tfm=$2 + local failed=0 + + echo "" + echo "==============================================" + echo "Checking: $baseline_tfm (baseline) vs $target_tfm" + echo "==============================================" + + for project in $PROJECTS; do + left="$ROOT_DIR/src/$project/bin/Release/$baseline_tfm/$project.dll" + right="$ROOT_DIR/src/$project/bin/Release/$target_tfm/$project.dll" + + if [ -f "$left" ] && [ -f "$right" ]; then + echo -n " $project: " + if apicompat --left "$left" --right "$right" --suppression-file "$SUPPRESSION_FILE" --permit-unnecessary-suppressions > /dev/null 2>&1; then + echo "OK" + else + echo "FAILED" + apicompat --left "$left" --right "$right" --suppression-file "$SUPPRESSION_FILE" --permit-unnecessary-suppressions 2>&1 | head -20 + failed=1 + fi + else + echo " $project: SKIPPED (assemblies not found)" + fi + done + + return $failed +} + +echo "API Compatibility Check" +echo "=======================" + +exit_code=0 + +# Only check against net8.0 as the target TFM +# net6.0 has known differences with init accessors +check_compat "netstandard2.0" "net8.0" || exit_code=1 +check_compat "netstandard2.1" "net8.0" || exit_code=1 + +echo "" +if [ $exit_code -eq 0 ]; then + echo "All API compatibility checks passed!" +else + echo "Some API compatibility checks failed!" +fi + +exit $exit_code From 279c1544056125814252b47b65fb049b08f0452d Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Wed, 7 Jan 2026 14:30:16 +0000 Subject: [PATCH 2/4] Add apicompat workflow to solution and fix script execution command --- .github/workflows/apicompat.yml | 2 +- NATS.Net.sln | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/apicompat.yml b/.github/workflows/apicompat.yml index 18dfe6f12..44a31f3c2 100644 --- a/.github/workflows/apicompat.yml +++ b/.github/workflows/apicompat.yml @@ -24,4 +24,4 @@ jobs: dotnet-version: 8.x - name: Run API Compatibility Check - run: ./scripts/apicompat.sh --build + run: bash scripts/apicompat.sh --build diff --git a/NATS.Net.sln b/NATS.Net.sln index 20b706cc4..bea40d882 100644 --- a/NATS.Net.sln +++ b/NATS.Net.sln @@ -103,6 +103,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ .github\workflows\test_linux.yml = .github\workflows\test_linux.yml .github\workflows\test_linux_core.yml = .github\workflows\test_linux_core.yml .github\workflows\test_windows.yml = .github\workflows\test_windows.yml + .github\workflows\apicompat.yml = .github\workflows\apicompat.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{5BF3C0C5-94CA-4008-8DF5-890ED8E06990}" From dd94e20cf54fad8a3d1cf9cc7eb59590f5802de0 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Wed, 7 Jan 2026 14:42:15 +0000 Subject: [PATCH 3/4] Update apicompat workflow to include read permissions for contents --- .github/workflows/apicompat.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/apicompat.yml b/.github/workflows/apicompat.yml index 44a31f3c2..8ead46507 100644 --- a/.github/workflows/apicompat.yml +++ b/.github/workflows/apicompat.yml @@ -6,6 +6,9 @@ on: branches: - main +permissions: + contents: read + jobs: apicompat: name: Check TFM API Compatibility From feb830d4db2ccd5699d842f048ae08e544100f8d Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 8 Jan 2026 17:07:12 +0000 Subject: [PATCH 4/4] Add "Platform Compatibility" documentation to advanced topics --- .../site_src/documentation/advanced/intro.md | 1 + .../advanced/platform-compatibility.md | 93 +++++++++++++++++++ tools/site_src/documentation/toc.yml | 2 + 3 files changed, 96 insertions(+) create mode 100644 tools/site_src/documentation/advanced/platform-compatibility.md diff --git a/tools/site_src/documentation/advanced/intro.md b/tools/site_src/documentation/advanced/intro.md index 4e6ff7e55..ce111115b 100644 --- a/tools/site_src/documentation/advanced/intro.md +++ b/tools/site_src/documentation/advanced/intro.md @@ -95,3 +95,4 @@ essentially sent back to back after they're picked up from internal queues and b - [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. +- [Platform Compatibility](platform-compatibility.md) documents API differences across target frameworks (.NET Standard, .NET 6, .NET 8). diff --git a/tools/site_src/documentation/advanced/platform-compatibility.md b/tools/site_src/documentation/advanced/platform-compatibility.md new file mode 100644 index 000000000..602512cc8 --- /dev/null +++ b/tools/site_src/documentation/advanced/platform-compatibility.md @@ -0,0 +1,93 @@ +# Platform Compatibility + +NATS.Net targets multiple .NET platforms to provide broad compatibility: + +- `netstandard2.0` - .NET Framework 4.6.1+, .NET Core 2.0+, Mono, Xamarin, Unity +- `netstandard2.1` - .NET Core 3.0+ +- `net6.0` - .NET 6 +- `net8.0` - .NET 8 + +While the API surface is designed to be consistent across all target frameworks, there are some +intentional differences due to platform capabilities. This page documents these differences. + +## TLS Configuration + +### SslClientAuthenticationOptions + +The [`NatsTlsOpts.ConfigureClientAuthentication`](xref:NATS.Client.Core.NatsTlsOpts.ConfigureClientAuthentication) +property allows you to configure TLS client authentication options. + +| Target Framework | Type | +|-----------------|------| +| `netstandard2.0` | `NATS.Client.Core.SslClientAuthenticationOptions` (polyfill) | +| `netstandard2.1`, `net6.0`, `net8.0` | `System.Net.Security.SslClientAuthenticationOptions` (BCL) | + +On `netstandard2.0`, the library provides a polyfill type `NATS.Client.Core.SslClientAuthenticationOptions` +because the BCL type doesn't exist in that target framework. The polyfill provides a subset of the +properties available in the BCL type: + +- `TargetHost` +- `EnabledSslProtocols` +- `ClientCertificates` +- `CertificateRevocationCheckMode` +- `RemoteCertificateValidationCallback` +- `LocalCertificateSelectionCallback` + +If you need the full `SslClientAuthenticationOptions` functionality, consider targeting `netstandard2.1` or later. + +## Dependency Injection + +### Keyed Services + +The [`AddNats`](xref:NATS.Client.Hosting.NatsHostingExtensions.AddNats*) extension method has different +signatures depending on the target framework: + +**netstandard2.0, netstandard2.1, net6.0:** +```csharp +public static IServiceCollection AddNats( + this IServiceCollection services, + int poolSize = 1, + Func? configureOpts = null, + Action? configureConnection = null) +``` + +**net8.0:** +```csharp +public static IServiceCollection AddNats( + this IServiceCollection services, + int poolSize = 1, + Func? configureOpts = null, + Action? configureConnection = null, + object? key = null) // Additional parameter for keyed services +``` + +[Keyed dependency injection services](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#keyed-services) +were introduced in .NET 8. The `key` parameter allows you to register multiple NATS connections +with different keys: + +```csharp +// .NET 8+ only +services.AddNats(key: "primary", configureOpts: opts => opts with { Url = "nats://primary:4222" }); +services.AddNats(key: "secondary", configureOpts: opts => opts with { Url = "nats://secondary:4222" }); + +// Inject with [FromKeyedServices("primary")] +public class MyService([FromKeyedServices("primary")] INatsConnection primaryNats) { } +``` + +## API Compatibility Checking + +The repository includes an API compatibility check that runs in CI to ensure APIs remain consistent +across target frameworks. Known intentional differences are documented in `apicompat.suppression.xml` +at the repository root. + +To run the compatibility check locally: + +```bash +./scripts/apicompat.sh --build +``` + +## 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. diff --git a/tools/site_src/documentation/toc.yml b/tools/site_src/documentation/toc.yml index fb81e4203..481d7f7b0 100644 --- a/tools/site_src/documentation/toc.yml +++ b/tools/site_src/documentation/toc.yml @@ -42,6 +42,8 @@ href: advanced/security.md - name: AOT Deployments href: advanced/aot.md + - name: Platform Compatibility + href: advanced/platform-compatibility.md - name: Updating Documentation href: update-docs.md