Skip to content

Commit

Permalink
Add IServiceProvider to Nats Client Extensions (#6929)
Browse files Browse the repository at this point in the history
* Add IServiceProvider to Nats Client Extensions

* Update src/Components/Aspire.NATS.Net/AspireNatsClientExtensions.cs

Co-authored-by: Eric Erhardt <[email protected]>

---------

Co-authored-by: Eric Erhardt <[email protected]>
  • Loading branch information
larsfjerm and eerhardt authored Dec 13, 2024
1 parent 386f0c0 commit 3991282
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 11 deletions.
81 changes: 71 additions & 10 deletions src/Components/Aspire.NATS.Net/AspireNatsClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,26 @@ public static class AspireNatsClientExtensions
private const string DefaultConfigSectionName = "Aspire:NATS:Net";
private const string ActivityNameSource = "NATS.Net";

/// <inheritdoc cref="AddNatsClient(IHostApplicationBuilder, string, Action{NatsClientSettings}?, Func{IServiceProvider,NatsOpts,NatsOpts}?)"/>
public static void AddNatsClient(this IHostApplicationBuilder builder, string connectionName)
=> AddNatsClientInternal(builder, connectionName: connectionName, serviceKey: null, configureSettings: null, configureOptions: null);

/// <inheritdoc cref="AddNatsClient(IHostApplicationBuilder, string, Action{NatsClientSettings}?, Func{IServiceProvider,NatsOpts,NatsOpts}?)"/>
public static void AddNatsClient(this IHostApplicationBuilder builder, string connectionName, Action<NatsClientSettings>? configureSettings)
=> AddNatsClientInternal(builder, connectionName: connectionName, serviceKey: null, configureSettings: configureSettings, configureOptions: null);

/// <inheritdoc cref="AddNatsClient(IHostApplicationBuilder, string, Action{NatsClientSettings}?, Func{IServiceProvider,NatsOpts,NatsOpts}?)"/>
public static void AddNatsClient(this IHostApplicationBuilder builder, string connectionName, Func<NatsOpts, NatsOpts>? configureOptions)
=> AddNatsClientInternal(builder, connectionName: connectionName, serviceKey: null, configureSettings: null, configureOptions: Wrap(configureOptions));

/// <inheritdoc cref="AddNatsClient(IHostApplicationBuilder, string, Action{NatsClientSettings}?, Func{IServiceProvider,NatsOpts,NatsOpts}?)"/>
public static void AddNatsClient(this IHostApplicationBuilder builder, string connectionName, Func<IServiceProvider, NatsOpts, NatsOpts>? configureOptions)
=> AddNatsClientInternal(builder, connectionName: connectionName, serviceKey: null, configureSettings: null, configureOptions: configureOptions);

/// <inheritdoc cref="AddNatsClient(IHostApplicationBuilder, string, Action{NatsClientSettings}?, Func{IServiceProvider,NatsOpts,NatsOpts}?)"/>
public static void AddNatsClient(this IHostApplicationBuilder builder, string connectionName, Action<NatsClientSettings>? configureSettings, Func<NatsOpts, NatsOpts>? configureOptions)
=> AddNatsClientInternal(builder, connectionName: connectionName, serviceKey: null, configureSettings: configureSettings, configureOptions: Wrap(configureOptions));

/// <summary>
/// Registers <see cref="INatsConnection"/> service for connecting NATS server with NATS client.
/// Configures health check and logging for the NATS client.
Expand All @@ -31,12 +51,44 @@ public static class AspireNatsClientExtensions
/// <param name="configureOptions">An optional delegate that can be used for customizing NATS options that aren't exposed as standard configuration.</param>
/// <exception cref="ArgumentNullException">Thrown if mandatory <paramref name="builder"/> is null.</exception>
/// <exception cref="InvalidOperationException">Thrown when mandatory <see cref="NatsClientSettings.ConnectionString"/> is not provided.</exception>
public static void AddNatsClient(this IHostApplicationBuilder builder, string connectionName, Action<NatsClientSettings>? configureSettings = null, Func<NatsOpts, NatsOpts>? configureOptions = null)
public static void AddNatsClient(this IHostApplicationBuilder builder, string connectionName, Action<NatsClientSettings>? configureSettings, Func<IServiceProvider, NatsOpts, NatsOpts>? configureOptions)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(connectionName);
AddNatsClientInternal(builder, connectionName: connectionName, serviceKey: null, configureSettings: configureSettings, configureOptions: configureOptions);
}

/// <inheritdoc cref="AddKeyedNatsClient(IHostApplicationBuilder, string, Action{NatsClientSettings}?, Func{IServiceProvider,NatsOpts,NatsOpts}?)"/>
public static void AddKeyedNatsClient(this IHostApplicationBuilder builder, string name)
{
ArgumentException.ThrowIfNullOrEmpty(name);
AddNatsClientInternal(builder, connectionName: name, serviceKey: name, configureSettings: null, configureOptions: null);
}

AddNatsClient(builder, connectionName: connectionName, serviceKey: null, configureSettings: configureSettings, configureOptions: configureOptions);
/// <inheritdoc cref="AddKeyedNatsClient(IHostApplicationBuilder, string, Action{NatsClientSettings}?, Func{IServiceProvider,NatsOpts,NatsOpts}?)"/>
public static void AddKeyedNatsClient(this IHostApplicationBuilder builder, string name, Action<NatsClientSettings>? configureSettings)
{
ArgumentException.ThrowIfNullOrEmpty(name);
AddNatsClientInternal(builder, connectionName: name, serviceKey: name, configureSettings: configureSettings, configureOptions: null);
}

/// <inheritdoc cref="AddKeyedNatsClient(IHostApplicationBuilder, string, Action{NatsClientSettings}?, Func{IServiceProvider,NatsOpts,NatsOpts}?)"/>
public static void AddKeyedNatsClient(this IHostApplicationBuilder builder, string name, Func<NatsOpts, NatsOpts>? configureOptions)
{
ArgumentException.ThrowIfNullOrEmpty(name);
AddNatsClientInternal(builder, connectionName: name, serviceKey: name, configureSettings: null, configureOptions: Wrap(configureOptions));
}

/// <inheritdoc cref="AddKeyedNatsClient(IHostApplicationBuilder, string, Action{NatsClientSettings}?, Func{IServiceProvider,NatsOpts,NatsOpts}?)"/>
public static void AddKeyedNatsClient(this IHostApplicationBuilder builder, string name, Func<IServiceProvider, NatsOpts, NatsOpts>? configureOptions)
{
ArgumentException.ThrowIfNullOrEmpty(name);
AddNatsClientInternal(builder, connectionName: name, serviceKey: name, configureSettings: null, configureOptions: configureOptions);
}

/// <inheritdoc cref="AddKeyedNatsClient(IHostApplicationBuilder, string, Action{NatsClientSettings}?, Func{IServiceProvider,NatsOpts,NatsOpts}?)"/>
public static void AddKeyedNatsClient(this IHostApplicationBuilder builder, string name, Action<NatsClientSettings>? configureSettings, Func<NatsOpts, NatsOpts>? configureOptions)
{
ArgumentException.ThrowIfNullOrEmpty(name);
AddNatsClientInternal(builder, connectionName: name, serviceKey: name, configureSettings: configureSettings, configureOptions: Wrap(configureOptions));
}

/// <summary>
Expand All @@ -50,17 +102,16 @@ public static void AddNatsClient(this IHostApplicationBuilder builder, string co
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> or <paramref name="name"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown if mandatory <paramref name="name"/> is empty.</exception>
/// <exception cref="InvalidOperationException">Thrown when mandatory <see cref="NatsClientSettings.ConnectionString"/> is not provided.</exception>
public static void AddKeyedNatsClient(this IHostApplicationBuilder builder, string name, Action<NatsClientSettings>? configureSettings = null, Func<NatsOpts, NatsOpts>? configureOptions = null)
public static void AddKeyedNatsClient(this IHostApplicationBuilder builder, string name, Action<NatsClientSettings>? configureSettings, Func<IServiceProvider, NatsOpts, NatsOpts>? configureOptions)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(name);

AddNatsClient(builder, connectionName: name, serviceKey: name, configureSettings: configureSettings, configureOptions: configureOptions);
AddNatsClientInternal(builder, connectionName: name, serviceKey: name, configureSettings: configureSettings, configureOptions: configureOptions);
}

private static void AddNatsClient(this IHostApplicationBuilder builder, string connectionName, object? serviceKey, Action<NatsClientSettings>? configureSettings, Func<NatsOpts, NatsOpts>? configureOptions)
private static void AddNatsClientInternal(this IHostApplicationBuilder builder, string connectionName, object? serviceKey, Action<NatsClientSettings>? configureSettings, Func<IServiceProvider, NatsOpts, NatsOpts>? configureOptions)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(connectionName);

NatsClientSettings settings = new();
var configSection = builder.Configuration.GetSection(DefaultConfigSectionName);
Expand All @@ -84,7 +135,7 @@ NatsConnection Factory(IServiceProvider provider)

if (configureOptions != null)
{
options = configureOptions(options);
options = configureOptions(provider, options);
}

if (settings.ConnectionString == null)
Expand Down Expand Up @@ -145,4 +196,14 @@ public static void AddNatsJetStream(this IHostApplicationBuilder builder)
return new NatsJSContextFactory().CreateContext(provider.GetRequiredService<INatsConnection>());
});
}

private static Func<IServiceProvider, NatsOpts, NatsOpts>? Wrap(Func<NatsOpts, NatsOpts>? func)
{
if (func is null)
{
return null;
}

return (_, options) => func(options);
}
}
15 changes: 14 additions & 1 deletion src/Components/Aspire.NATS.Net/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,15 @@
#nullable enable

*REMOVED*static Microsoft.Extensions.Hosting.AspireNatsClientExtensions.AddKeyedNatsClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! name, System.Action<Aspire.NATS.Net.NatsClientSettings!>? configureSettings = null, System.Func<NATS.Client.Core.NatsOpts!, NATS.Client.Core.NatsOpts!>? configureOptions = null) -> void
static Microsoft.Extensions.Hosting.AspireNatsClientExtensions.AddKeyedNatsClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! name) -> void
static Microsoft.Extensions.Hosting.AspireNatsClientExtensions.AddKeyedNatsClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! name, System.Action<Aspire.NATS.Net.NatsClientSettings!>? configureSettings) -> void
static Microsoft.Extensions.Hosting.AspireNatsClientExtensions.AddKeyedNatsClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! name, System.Func<NATS.Client.Core.NatsOpts!, NATS.Client.Core.NatsOpts!>? configureOptions) -> void
static Microsoft.Extensions.Hosting.AspireNatsClientExtensions.AddKeyedNatsClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! name, System.Func<System.IServiceProvider!, NATS.Client.Core.NatsOpts!, NATS.Client.Core.NatsOpts!>? configureOptions) -> void
static Microsoft.Extensions.Hosting.AspireNatsClientExtensions.AddKeyedNatsClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! name, System.Action<Aspire.NATS.Net.NatsClientSettings!>? configureSettings, System.Func<NATS.Client.Core.NatsOpts!, NATS.Client.Core.NatsOpts!>? configureOptions) -> void
static Microsoft.Extensions.Hosting.AspireNatsClientExtensions.AddKeyedNatsClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! name, System.Action<Aspire.NATS.Net.NatsClientSettings!>? configureSettings, System.Func<System.IServiceProvider!, NATS.Client.Core.NatsOpts!, NATS.Client.Core.NatsOpts!>? configureOptions) -> void
*REMOVED*static Microsoft.Extensions.Hosting.AspireNatsClientExtensions.AddNatsClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! connectionName, System.Action<Aspire.NATS.Net.NatsClientSettings!>? configureSettings = null, System.Func<NATS.Client.Core.NatsOpts!, NATS.Client.Core.NatsOpts!>? configureOptions = null) -> void
static Microsoft.Extensions.Hosting.AspireNatsClientExtensions.AddNatsClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! connectionName) -> void
static Microsoft.Extensions.Hosting.AspireNatsClientExtensions.AddNatsClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! connectionName, System.Action<Aspire.NATS.Net.NatsClientSettings!>? configureSettings) -> void
static Microsoft.Extensions.Hosting.AspireNatsClientExtensions.AddNatsClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! connectionName, System.Func<NATS.Client.Core.NatsOpts!, NATS.Client.Core.NatsOpts!>? configureOptions) -> void
static Microsoft.Extensions.Hosting.AspireNatsClientExtensions.AddNatsClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! connectionName, System.Func<System.IServiceProvider!, NATS.Client.Core.NatsOpts!, NATS.Client.Core.NatsOpts!>? configureOptions) -> void
static Microsoft.Extensions.Hosting.AspireNatsClientExtensions.AddNatsClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! connectionName, System.Action<Aspire.NATS.Net.NatsClientSettings!>? configureSettings, System.Func<NATS.Client.Core.NatsOpts!, NATS.Client.Core.NatsOpts!>? configureOptions) -> void
static Microsoft.Extensions.Hosting.AspireNatsClientExtensions.AddNatsClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! connectionName, System.Action<Aspire.NATS.Net.NatsClientSettings!>? configureSettings, System.Func<System.IServiceProvider!, NATS.Client.Core.NatsOpts!, NATS.Client.Core.NatsOpts!>? configureOptions) -> void
83 changes: 83 additions & 0 deletions tests/Aspire.NATS.Net.Tests/NatsClientPublicApiTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NATS.Client.Core;
using Xunit;

namespace Aspire.NATS.Net.Tests;
Expand Down Expand Up @@ -96,4 +99,84 @@ public void AddNatsJetStreamShouldThrowWhenBuilderIsNull()
var exception = Assert.Throws<ArgumentNullException>(action);
Assert.Equal(nameof(builder), exception.ParamName);
}

[Theory]
[InlineData(true, true, false, false)]
[InlineData(true, true, true, false)]
[InlineData(true, true, false, true)]
[InlineData(true, false, false, false)]
[InlineData(true, false, true, false)]
[InlineData(true, false, false, true)]
[InlineData(false, true, false, false)]
[InlineData(false, true, true, false)]
[InlineData(false, true, false, true)]
[InlineData(false, false, false, false)]
[InlineData(false, false, true, false)]
[InlineData(false, false, false, true)]
public void AddNatsClientConfigured(bool useKeyed, bool useConfigureSettings, bool useConfigureOptions, bool useConfigureOptionsWithServiceProvider)
{
var builder = Host.CreateEmptyApplicationBuilder(null);
builder.Configuration.AddInMemoryCollection([
new KeyValuePair<string, string?>("ConnectionStrings:Nats", "nats")
]);
var name = "Nats";
bool configureSettingsIsCalled = false, configureOptionsIsCalled = false;

Action action = (useKeyed, useConfigureSettings, useConfigureOptions, useConfigureOptionsWithServiceProvider) switch
{
// Single Client
(false, false, false, false) => () => builder.AddNatsClient(name),
(false, true, false, false) => () => builder.AddNatsClient(name, configureSettings: ConfigureSettings),
(false, false, true, false) => () => builder.AddNatsClient(name, configureOptions: ConfigureOptions),
(false, false, false, true) => () => builder.AddNatsClient(name, configureOptions: ConfigureOptionsWithServiceProvider),
(false, true, true, false) => () => builder.AddNatsClient(name, configureSettings: ConfigureSettings, configureOptions: ConfigureOptions),
(false, true, false, true) => () => builder.AddNatsClient(name, configureSettings: ConfigureSettings, configureOptions: ConfigureOptionsWithServiceProvider),

// Keyed Client
(true, false, false, false) => () => builder.AddKeyedNatsClient(name),
(true, true, false, false) => () => builder.AddKeyedNatsClient(name, configureSettings: ConfigureSettings),
(true, false, true, false) => () => builder.AddKeyedNatsClient(name, configureOptions: ConfigureOptions),
(true, false, false, true) => () => builder.AddKeyedNatsClient(name, configureOptions: ConfigureOptionsWithServiceProvider),
(true, true, true, false) => () => builder.AddKeyedNatsClient(name, configureSettings: ConfigureSettings, configureOptions: ConfigureOptions),
(true, true, false, true) => () => builder.AddKeyedNatsClient(name, configureSettings: ConfigureSettings, configureOptions: ConfigureOptionsWithServiceProvider),

_ => throw new InvalidOperationException()
};

action();

using var host = builder.Build();

_ = useKeyed
? host.Services.GetRequiredKeyedService<INatsConnection>(name)
: host.Services.GetRequiredService<INatsConnection>();

if (useConfigureSettings)
{
Assert.True(configureSettingsIsCalled);
}

if (useConfigureOptions || useConfigureOptionsWithServiceProvider)
{
Assert.True(configureOptionsIsCalled);
}

void ConfigureSettings(NatsClientSettings _)
{
configureSettingsIsCalled = true;
}

NatsOpts ConfigureOptions(NatsOpts _)
{
configureOptionsIsCalled = true;
return NatsOpts.Default;
}

NatsOpts ConfigureOptionsWithServiceProvider(IServiceProvider provider, NatsOpts _)
{
var __ = provider.GetRequiredService<IConfiguration>();
configureOptionsIsCalled = true;
return NatsOpts.Default;
}
}
}

0 comments on commit 3991282

Please sign in to comment.