Skip to content

Commit

Permalink
Support Keyed DI services in .NET 8 (#331)
Browse files Browse the repository at this point in the history
* Support Keyed DI services in .NET 8 #315

* add NATS.Client.Hosting.Tests

* StyleCop suppression for conditional build
  • Loading branch information
rickdotnet authored Jan 14, 2024
1 parent 62a6c31 commit dc6301f
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 15 deletions.
7 changes: 7 additions & 0 deletions NATS.Client.sln
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ 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}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -261,6 +263,10 @@ Global
{389C05EB-A0B3-4097-8C1F-4D55818438CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{389C05EB-A0B3-4097-8C1F-4D55818438CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{389C05EB-A0B3-4097-8C1F-4D55818438CC}.Release|Any CPU.Build.0 = Release|Any CPU
{766C2486-34C3-4DD1-B31C-540C17C044B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -306,6 +312,7 @@ Global
{5B4B30A5-941B-44F9-98C6-06F0BB2242AB} = {4827B3EC-73D8-436D-AE2A-5E29AC95FD0C}
{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}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8CBB7278-D093-448E-B3DE-B5991209A1AA}
Expand Down
6 changes: 5 additions & 1 deletion src/NATS.Client.Hosting/NATS.Client.Hosting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@
<IsPackable>true</IsPackable>
</PropertyGroup>

<ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net8.0'">
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0"/>
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\NATS.Client.Core\NATS.Client.Core.csproj"/>
</ItemGroup>
Expand Down
53 changes: 39 additions & 14 deletions src/NATS.Client.Hosting/NatsHostingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,28 @@ namespace NATS.Client.Hosting;
public static class NatsHostingExtensions
{
/// <summary>
/// Add NatsConnection/Pool to ServiceCollection. When poolSize = 1, registered `NatsConnection` and `INatsCommand` as singleton.
/// Others, registered `NatsConnectionPool` as singleton, `NatsConnection` and `INatsCommand` as transient(get from pool).
/// Add NatsConnection/Pool to ServiceCollection. When poolSize = 1, registered `NatsConnection` and `INatsConnection` as singleton.
/// Others, registered `NatsConnectionPool` as singleton, `NatsConnection` and `INatsConnection` as transient(get from pool).
/// </summary>
public static IServiceCollection AddNats(this IServiceCollection services, int poolSize = 1, Func<NatsOpts, NatsOpts>? configureOpts = null, Action<NatsConnection>? configureConnection = null)
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:Commas should not be preceded by whitespace", Justification = "Required for conditional build.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1009:Closing parenthesis should not be preceded by a space", Justification = "Required for conditional build.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1111:Closing parenthesis should be on the same line as the last parameter", Justification = "Required for conditional build.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1113:Comma should be on the same line as previous parameter", Justification = "Required for conditional build.")]
public static IServiceCollection AddNats(
this IServiceCollection services,
int poolSize = 1,
Func<NatsOpts, NatsOpts>? configureOpts = null,
Action<NatsConnection>? configureConnection = null
#if NET8_0_OR_GREATER
, string? key = null // This parameter is only available in .NET 8 or greater
#endif
)
{
string? diKey = null;
#if NET8_0_OR_GREATER
diKey = key;
#endif

poolSize = Math.Max(poolSize, 1);

if (poolSize != 1)
Expand All @@ -28,21 +45,23 @@ public static IServiceCollection AddNats(this IServiceCollection services, int p
return new NatsConnectionPool(poolSize, options, configureConnection ?? (_ => { }));
});

services.TryAddSingleton<INatsConnectionPool>(static provider =>
{
return provider.GetRequiredService<NatsConnectionPool>();
});

services.TryAddSingleton<INatsConnectionPool>(static provider => provider.GetRequiredService<NatsConnectionPool>());
services.TryAddTransient<NatsConnection>(static provider =>
{
var pool = provider.GetRequiredService<NatsConnectionPool>();
return (pool.GetConnection() as NatsConnection)!;
});

services.TryAddTransient<INatsConnection>(static provider =>
if (string.IsNullOrEmpty(diKey))
{
return provider.GetRequiredService<NatsConnection>();
});
services.TryAddTransient<INatsConnection>(static provider => provider.GetRequiredService<NatsConnection>());
}
else
{
#if NET8_0_OR_GREATER
services.AddKeyedTransient<INatsConnection>(diKey, static (provider, _) => provider.GetRequiredService<NatsConnection>());
#endif
}
}
else
{
Expand All @@ -63,10 +82,16 @@ public static IServiceCollection AddNats(this IServiceCollection services, int p
return conn;
});

services.TryAddSingleton<INatsConnection>(static provider =>
if (string.IsNullOrEmpty(diKey))
{
return provider.GetRequiredService<NatsConnection>();
});
services.TryAddSingleton<INatsConnection>(static provider => provider.GetRequiredService<NatsConnection>());
}
else
{
#if NET8_0_OR_GREATER
services.AddKeyedSingleton<INatsConnection>(diKey, static (provider, _) => provider.GetRequiredService<NatsConnection>());
#endif
}
}

return services;
Expand Down
34 changes: 34 additions & 0 deletions tests/NATS.Client.Hosting.Tests/NATS.Client.Hosting.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<NoWarn>$(NoWarn);CS8002</NoWarn>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
<PackageReference Include="xunit" Version="2.4.2"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.2.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<Using Include="Xunit"/>
<Using Include="Xunit.Abstractions"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\NATS.Client.Hosting\NATS.Client.Hosting.csproj" />
</ItemGroup>

</Project>
58 changes: 58 additions & 0 deletions tests/NATS.Client.Hosting.Tests/NatsHostingExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using NATS.Client.Core;

namespace NATS.Client.Hosting.Tests;

public class NatsHostingExtensionsTests
{
[Fact]
public void AddNats_RegistersNatsConnectionAsSingleton_WhenPoolSizeIsOne()
{
var services = new ServiceCollection();
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();

services.AddNats(poolSize: 1);
var provider = services.BuildServiceProvider();

var natsConnection1 = provider.GetRequiredService<INatsConnection>();
var natsConnection2 = provider.GetRequiredService<INatsConnection>();

Assert.NotNull(natsConnection1);
Assert.Same(natsConnection1, natsConnection2); // Singleton should return the same instance
}

[Fact]
public void AddNats_RegistersNatsConnectionAsTransient_WhenPoolSizeIsGreaterThanOne()
{
var services = new ServiceCollection();
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();

services.AddNats(poolSize: 2);
var provider = services.BuildServiceProvider();

var natsConnection1 = provider.GetRequiredService<INatsConnection>();
var natsConnection2 = provider.GetRequiredService<INatsConnection>();

Assert.NotNull(natsConnection1);
Assert.NotSame(natsConnection1, natsConnection2); // Transient should return different instances
}

#if NET8_0_OR_GREATER
[Fact]
public void AddNats_RegistersKeyedNatsConnection_WhenKeyIsProvided()
{
var key = "TestKey";

var services = new ServiceCollection();
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();

services.AddNats(poolSize: 1, key: key);
var provider = services.BuildServiceProvider();

var natsConnection = provider.GetKeyedService<INatsConnection>(key);
Assert.NotNull(natsConnection);
}
#endif
}

0 comments on commit dc6301f

Please sign in to comment.