diff --git a/NATS.Client.sln b/NATS.Client.sln
index 6de6c8971..ee9dac34d 100644
--- a/NATS.Client.sln
+++ b/NATS.Client.sln
@@ -99,7 +99,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NATS.Net", "src\NATS.Net\NA
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NATS.Net.DocsExamples", "tests\NATS.Net.DocsExamples\NATS.Net.DocsExamples.csproj", "{389C05EB-A0B3-4097-8C1F-4D55818438CC}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NATS.Client.Hosting.Tests", "tests\NATS.Client.Hosting.Tests\NATS.Client.Hosting.Tests.csproj", "{766C2486-34C3-4DD1-B31C-540C17C044B0}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NATS.Extensions.Microsoft.DependencyInjection.Tests", "tests\NATS.Extensions.Microsoft.DependencyInjection.Tests\NATS.Extensions.Microsoft.DependencyInjection.Tests.csproj", "{766C2486-34C3-4DD1-B31C-540C17C044B0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NATS.Extensions.Microsoft.DependencyInjection", "src\NATS.Extensions.Microsoft.DependencyInjection\NATS.Extensions.Microsoft.DependencyInjection.csproj", "{2EA0EB68-1AA4-40AC-8FA2-B51532075567}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.OpenTelemetry", "sandbox\Example.OpenTelemetry\Example.OpenTelemetry.csproj", "{474BA453-9CFF-41C2-B2E7-ADD92CC93E86}"
EndProject
@@ -271,6 +273,10 @@ Global
{766C2486-34C3-4DD1-B31C-540C17C044B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{766C2486-34C3-4DD1-B31C-540C17C044B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{766C2486-34C3-4DD1-B31C-540C17C044B0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2EA0EB68-1AA4-40AC-8FA2-B51532075567}.Release|Any CPU.Build.0 = Release|Any CPU
{474BA453-9CFF-41C2-B2E7-ADD92CC93E86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{474BA453-9CFF-41C2-B2E7-ADD92CC93E86}.Debug|Any CPU.Build.0 = Debug|Any CPU
{474BA453-9CFF-41C2-B2E7-ADD92CC93E86}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -325,6 +331,7 @@ Global
{6A7B9B9F-BFA4-4A6D-9006-0AAF597FC6DD} = {4827B3EC-73D8-436D-AE2A-5E29AC95FD0C}
{389C05EB-A0B3-4097-8C1F-4D55818438CC} = {C526E8AB-739A-48D7-8FC4-048978C9B650}
{766C2486-34C3-4DD1-B31C-540C17C044B0} = {C526E8AB-739A-48D7-8FC4-048978C9B650}
+ {2EA0EB68-1AA4-40AC-8FA2-B51532075567} = {4827B3EC-73D8-436D-AE2A-5E29AC95FD0C}
{474BA453-9CFF-41C2-B2E7-ADD92CC93E86} = {95A69671-16CA-4133-981C-CC381B7AAA30}
{B8554582-DE19-41A2-9784-9B27C9F22429} = {C526E8AB-739A-48D7-8FC4-048978C9B650}
EndGlobalSection
diff --git a/src/NATS.Extensions.Microsoft.DependencyInjection/NATS.Extensions.Microsoft.DependencyInjection.csproj b/src/NATS.Extensions.Microsoft.DependencyInjection/NATS.Extensions.Microsoft.DependencyInjection.csproj
new file mode 100644
index 000000000..e645c3bfd
--- /dev/null
+++ b/src/NATS.Extensions.Microsoft.DependencyInjection/NATS.Extensions.Microsoft.DependencyInjection.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net6.0;net8.0
+ enable
+ enable
+ true
+
+
+ pubsub;messaging
+ ASP.NET Core and Generic Host support for NATS.Net.
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/NATS.Extensions.Microsoft.DependencyInjection/NatsBuilder.cs b/src/NATS.Extensions.Microsoft.DependencyInjection/NatsBuilder.cs
new file mode 100644
index 000000000..73ab65b82
--- /dev/null
+++ b/src/NATS.Extensions.Microsoft.DependencyInjection/NatsBuilder.cs
@@ -0,0 +1,135 @@
+using System.Text.Json.Serialization;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Logging;
+using NATS.Client.Core;
+
+namespace NATS.Extensions.Microsoft.DependencyInjection;
+
+public class NatsBuilder
+{
+ private readonly IServiceCollection _services;
+ private int _poolSize = 1;
+ private Func? _configureOpts;
+ private Action? _configureConnection;
+ private object? _diKey = null;
+
+ public NatsBuilder(IServiceCollection services)
+ => _services = services;
+
+ public NatsBuilder WithPoolSize(int size)
+ {
+ _poolSize = Math.Max(size, 1);
+ return this;
+ }
+
+ public NatsBuilder ConfigureOptions(Func optsFactory)
+ {
+ var previousFactory = _configureOpts;
+ _configureOpts = opts =>
+ {
+ // Apply the previous configurator if it exists.
+ if (previousFactory != null)
+ {
+ opts = previousFactory(opts);
+ }
+
+ // Then apply the new configurator.
+ return optsFactory(opts);
+ };
+ return this;
+ }
+
+ public NatsBuilder ConfigureConnection(Action connectionOpts)
+ {
+ _configureConnection = connectionOpts;
+ return this;
+ }
+
+ public NatsBuilder AddJsonSerialization(JsonSerializerContext context)
+ => ConfigureOptions(opts =>
+ {
+ var jsonRegistry = new NatsJsonContextSerializerRegistry(context);
+ return opts with { SerializerRegistry = jsonRegistry };
+ });
+
+#if NET8_0_OR_GREATER
+ public NatsBuilder WithKey(object key)
+ {
+ _diKey = key;
+ return this;
+ }
+#endif
+
+ public IServiceCollection Build()
+ {
+ if (_poolSize != 1)
+ {
+ if (_diKey == null)
+ {
+ _services.TryAddSingleton(provider => PoolFactory(provider));
+ _services.TryAddSingleton(static provider => provider.GetRequiredService());
+ _services.TryAddTransient(static provider => PooledConnectionFactory(provider, null));
+ _services.TryAddTransient(static provider => provider.GetRequiredService());
+ }
+ else
+ {
+#if NET8_0_OR_GREATER
+ _services.TryAddKeyedSingleton(_diKey, PoolFactory);
+ _services.TryAddKeyedSingleton(_diKey, static (provider, key) => provider.GetRequiredKeyedService(key));
+ _services.TryAddKeyedTransient(_diKey, PooledConnectionFactory);
+ _services.TryAddKeyedTransient(_diKey, static (provider, key) => provider.GetRequiredKeyedService(key));
+#endif
+ }
+ }
+ else
+ {
+ if (_diKey == null)
+ {
+ _services.TryAddSingleton(provider => SingleConnectionFactory(provider));
+ _services.TryAddSingleton(static provider => provider.GetRequiredService());
+ }
+ else
+ {
+#if NET8_0_OR_GREATER
+ _services.TryAddKeyedSingleton(_diKey, SingleConnectionFactory);
+ _services.TryAddKeyedSingleton(_diKey, static (provider, key) => provider.GetRequiredKeyedService(key));
+#endif
+ }
+ }
+
+ return _services;
+ }
+
+ private static NatsConnection PooledConnectionFactory(IServiceProvider provider, object? key)
+ {
+#if NET8_0_OR_GREATER
+ if (key != null)
+ {
+ var keyedConnection = provider.GetRequiredKeyedService(key).GetConnection();
+ return keyedConnection as NatsConnection ?? throw new InvalidOperationException("Connection is not of type NatsConnection");
+ }
+#endif
+ var connection = provider.GetRequiredService().GetConnection();
+ return connection as NatsConnection ?? throw new InvalidOperationException("Connection is not of type NatsConnection");
+ }
+
+ private NatsConnectionPool PoolFactory(IServiceProvider provider, object? diKey = null)
+ {
+ var options = NatsOpts.Default with { LoggerFactory = provider.GetRequiredService() };
+ options = _configureOpts?.Invoke(options) ?? options;
+
+ return new NatsConnectionPool(_poolSize, options, _configureConnection ?? (_ => { }));
+ }
+
+ private NatsConnection SingleConnectionFactory(IServiceProvider provider, object? diKey = null)
+ {
+ var options = NatsOpts.Default with { LoggerFactory = provider.GetRequiredService() };
+ options = _configureOpts?.Invoke(options) ?? options;
+
+ var conn = new NatsConnection(options);
+ _configureConnection?.Invoke(conn);
+
+ return conn;
+ }
+}
diff --git a/src/NATS.Extensions.Microsoft.DependencyInjection/NatsHostingExtensions.cs b/src/NATS.Extensions.Microsoft.DependencyInjection/NatsHostingExtensions.cs
new file mode 100644
index 000000000..4341f2270
--- /dev/null
+++ b/src/NATS.Extensions.Microsoft.DependencyInjection/NatsHostingExtensions.cs
@@ -0,0 +1,15 @@
+using Microsoft.Extensions.DependencyInjection;
+
+namespace NATS.Extensions.Microsoft.DependencyInjection;
+
+public static class NatsHostingExtensions
+{
+ public static IServiceCollection AddNatsClient(this IServiceCollection services, Action? buildAction = null)
+ {
+ var builder = new NatsBuilder(services);
+ buildAction?.Invoke(builder);
+
+ builder.Build();
+ return services;
+ }
+}
diff --git a/tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/MyJsonContext.cs b/tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/MyJsonContext.cs
new file mode 100644
index 000000000..fceba059f
--- /dev/null
+++ b/tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/MyJsonContext.cs
@@ -0,0 +1,13 @@
+using System.Text.Json.Serialization;
+
+namespace NATS.Extensions.Microsoft.DependencyInjection.Tests;
+
+[JsonSerializable(typeof(MyData))]
+internal partial class MyJsonContext : JsonSerializerContext;
+
+public record MyData
+{
+ public MyData(string name) => Name = name;
+
+ public string Name { get; set; }
+}
diff --git a/tests/NATS.Client.Hosting.Tests/NATS.Client.Hosting.Tests.csproj b/tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NATS.Extensions.Microsoft.DependencyInjection.Tests.csproj
similarity index 83%
rename from tests/NATS.Client.Hosting.Tests/NATS.Client.Hosting.Tests.csproj
rename to tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NATS.Extensions.Microsoft.DependencyInjection.Tests.csproj
index a023e1ca0..e4d3dd79f 100644
--- a/tests/NATS.Client.Hosting.Tests/NATS.Client.Hosting.Tests.csproj
+++ b/tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NATS.Extensions.Microsoft.DependencyInjection.Tests.csproj
@@ -28,7 +28,8 @@
-
+
+
diff --git a/tests/NATS.Client.Hosting.Tests/NatsHostingExtensionsTests.cs b/tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs
similarity index 71%
rename from tests/NATS.Client.Hosting.Tests/NatsHostingExtensionsTests.cs
rename to tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs
index 0c5e5d091..fe1987de1 100644
--- a/tests/NATS.Client.Hosting.Tests/NatsHostingExtensionsTests.cs
+++ b/tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs
@@ -2,8 +2,9 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using NATS.Client.Core;
+using NATS.Client.Core.Tests;
-namespace NATS.Client.Hosting.Tests;
+namespace NATS.Extensions.Microsoft.DependencyInjection.Tests;
public class NatsHostingExtensionsTests
{
@@ -12,10 +13,9 @@ public void AddNats_RegistersNatsConnectionAsSingleton_WhenPoolSizeIsOne()
{
var services = new ServiceCollection();
services.AddSingleton();
+ services.AddNatsClient();
- services.AddNats(poolSize: 1);
var provider = services.BuildServiceProvider();
-
var natsConnection1 = provider.GetRequiredService();
var natsConnection2 = provider.GetRequiredService();
@@ -28,10 +28,9 @@ public void AddNats_RegistersNatsConnectionAsTransient_WhenPoolSizeIsGreaterThan
{
var services = new ServiceCollection();
services.AddSingleton();
+ services.AddNatsClient(builder => builder.WithPoolSize(2));
- services.AddNats(poolSize: 2);
var provider = services.BuildServiceProvider();
-
var natsConnection1 = provider.GetRequiredService();
var natsConnection2 = provider.GetRequiredService();
@@ -39,6 +38,33 @@ public void AddNats_RegistersNatsConnectionAsTransient_WhenPoolSizeIsGreaterThan
Assert.NotSame(natsConnection1, natsConnection2); // Transient should return different instances
}
+ [Fact]
+ public async Task AddNats_WithJsonSerializer()
+ {
+ await using var server = NatsServer.Start();
+
+ var services = new ServiceCollection();
+ services.AddSingleton();
+ services.AddNatsClient(builder =>
+ {
+ builder.ConfigureOptions(opts => server.ClientOpts(opts));
+ builder.AddJsonSerialization(MyJsonContext.Default);
+ });
+
+ var provider = services.BuildServiceProvider();
+ var nats = provider.GetRequiredService();
+
+ var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
+ var cancellationToken = cts.Token;
+
+ await using var sub = await nats.SubscribeCoreAsync("foo", cancellationToken: cancellationToken);
+ await nats.PingAsync(cancellationToken);
+ await nats.PublishAsync("foo", new MyData("bar"), cancellationToken: cancellationToken);
+
+ var msg = await sub.Msgs.ReadAsync(cancellationToken);
+ Assert.Equal("bar", msg.Data?.Name);
+ }
+
#if NET8_0_OR_GREATER
[Fact]
public void AddNats_RegistersKeyedNatsConnection_WhenKeyIsProvided()
@@ -49,8 +75,9 @@ public void AddNats_RegistersKeyedNatsConnection_WhenKeyIsProvided()
var services = new ServiceCollection();
services.AddSingleton();
- services.AddNats(poolSize: 1, key: key1);
- services.AddNats(poolSize: 1, key: key2);
+ services.AddNatsClient(builder => builder.WithKey(key1));
+ services.AddNatsClient(builder => builder.WithKey(key2));
+
var provider = services.BuildServiceProvider();
var natsConnection1A = provider.GetKeyedService(key1);
@@ -73,8 +100,8 @@ public void AddNats_RegistersKeyedNatsConnection_WhenKeyIsProvided_pooled()
var services = new ServiceCollection();
services.AddSingleton();
- services.AddNats(poolSize: 2, key: key1);
- services.AddNats(poolSize: 2, key: key2);
+ services.AddNatsClient(builder => builder.WithPoolSize(2).WithKey(key1));
+ services.AddNatsClient(builder => builder.WithPoolSize(2).WithKey(key2));
var provider = services.BuildServiceProvider();
Dictionary> connections = new();