Skip to content

Commit 579b6da

Browse files
authored
Merge pull request #18 from smdn/add-hosting-apis
Add APIs to support .NET generic host
2 parents cd5cd23 + 7625156 commit 579b6da

File tree

6 files changed

+413
-0
lines changed

6 files changed

+413
-0
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2025 smdn <smdn@smdn.jp>
3+
SPDX-License-Identifier: MIT
4+
-->
5+
<Project Sdk="Microsoft.NET.Sdk">
6+
<PropertyGroup>
7+
<TargetFrameworks>netstandard2.1;net8.0</TargetFrameworks>
8+
<VersionPrefix>3.0.0</VersionPrefix>
9+
<VersionSuffix></VersionSuffix>
10+
<!--<PackageValidationBaselineVersion>3.0.0</PackageValidationBaselineVersion>-->
11+
<RootNamespace/> <!-- empty the root namespace so that the namespace is determined only by the directory name, for code style rule IDE0030 -->
12+
<Nullable>enable</Nullable>
13+
<NoWarn>CS1591;$(NoWarn)</NoWarn> <!-- CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' -->
14+
</PropertyGroup>
15+
16+
<PropertyGroup Label="assembly attributes">
17+
<Description>
18+
<![CDATA[A .NET implementation of [Munin-Node](https://guide.munin-monitoring.org/en/latest/node/index.html) for [.NET Generic Host](https://learn.microsoft.com/dotnet/core/extensions/generic-host).
19+
20+
This library provides APIs to run Munin-Node as a background service running on a .NET Generic Host.
21+
22+
This library mainly provides a `MuninNodeBackgroundService` class derived from `BackgroundService`, and extension methods to register the Munin-Node service to the `ServiceCollection`.
23+
24+
This library uses [Smdn.Net.MuninNode](https://www.nuget.org/packages/Smdn.Net.MuninNode) and the API is provided as an extension to `Smdn.Net.MuninNode`.
25+
]]>
26+
</Description>
27+
<CopyrightYear>2025</CopyrightYear>
28+
</PropertyGroup>
29+
30+
<PropertyGroup Label="package properties">
31+
<PackageTags>Munin,Munin-Node,Munin-Plugin,hosting,generic-host,dependency-injection</PackageTags>
32+
<GenerateNupkgReadmeFileDependsOnTargets>$(GenerateNupkgReadmeFileDependsOnTargets);GenerateReadmeFileContent</GenerateNupkgReadmeFileDependsOnTargets>
33+
</PropertyGroup>
34+
35+
<ItemGroup>
36+
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
37+
<!-- <ProjectOrPackageReference ReferencePackageVersion="[2.2.0,4.0.0)" Include="..\Smdn.Net.MuninNode\Smdn.Net.MuninNode.csproj" /> -->
38+
<ProjectReference Include="..\Smdn.Net.MuninNode\Smdn.Net.MuninNode.csproj" />
39+
</ItemGroup>
40+
41+
<Target Name="GenerateReadmeFileContent">
42+
<PropertyGroup>
43+
<PackageReadmeFileContent><![CDATA[# $(PackageId) $(PackageVersion)
44+
$(Description)
45+
46+
## Contributing
47+
This project welcomes contributions, feedbacks and suggestions. You can contribute to this project by submitting [Issues]($(RepositoryUrl)/issues/new/choose) or [Pull Requests]($(RepositoryUrl)/pulls/) on the [GitHub repository]($(RepositoryUrl)).
48+
]]></PackageReadmeFileContent>
49+
</PropertyGroup>
50+
</Target>
51+
</Project>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// SPDX-FileCopyrightText: 2025 smdn <smdn@smdn.jp>
2+
// SPDX-License-Identifier: MIT
3+
4+
using System;
5+
6+
using Microsoft.Extensions.DependencyInjection;
7+
8+
using Smdn.Net.MuninNode.DependencyInjection;
9+
10+
namespace Smdn.Net.MuninNode.Hosting;
11+
12+
public static class IServiceCollectionExtensions {
13+
/// <summary>
14+
/// Add <see cref="MuninNodeBackgroundService"/>, which runs <c>Munin-Node</c> as an
15+
/// <see cref="Microsoft.Extensions.Hosting.IHostedService"/>, to <see cref="IServiceCollection"/>.
16+
/// </summary>
17+
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
18+
/// <param name="configureNode">
19+
/// An <see cref="Action{MuninNodeOptions}"/> to setup <see cref="MuninNodeOptions"/> to
20+
/// configure the <c>Munin-Node</c> to be built.
21+
/// </param>
22+
/// <param name="buildNode">
23+
/// An <see cref="Action{IMuninServiceBuilder}"/> to build <c>Munin-Node</c> using with
24+
/// the <see cref="IMuninServiceBuilder"/>.
25+
/// </param>
26+
/// <returns>The current <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
27+
/// <exception cref="ArgumentNullException">
28+
/// <paramref name="services"/> is <see langword="null"/>, or
29+
/// <paramref name="configureNode"/> is <see langword="null"/>, or
30+
/// <paramref name="buildNode"/> is <see langword="null"/>.
31+
/// </exception>
32+
public static IServiceCollection AddHostedMuninNodeService(
33+
this IServiceCollection services,
34+
Action<MuninNodeOptions> configureNode,
35+
Action<IMuninNodeBuilder> buildNode
36+
)
37+
{
38+
if (services is null)
39+
throw new ArgumentNullException(nameof(services));
40+
if (configureNode is null)
41+
throw new ArgumentNullException(nameof(configureNode));
42+
if (buildNode is null)
43+
throw new ArgumentNullException(nameof(buildNode));
44+
45+
return services.AddMunin(
46+
muninBuilder => {
47+
var muninNodeBuilder = muninBuilder.AddNode(configureNode);
48+
49+
buildNode(muninNodeBuilder);
50+
51+
muninNodeBuilder.Services.AddHostedService<MuninNodeBackgroundService>();
52+
53+
// TODO: support keyed service
54+
#if false
55+
var muninNodeBuilder = muninBuilder.AddKeyedNode(configureNode);
56+
57+
buildNode(muninNodeBuilder);
58+
59+
// these code does not work currently
60+
// https://github.com/dotnet/runtime/issues/99085
61+
muninNodeBuilder.Services.AddHostedService<MuninNodeBackgroundService>(
62+
serviceKey: muninNodeBuilder.ServiceKey
63+
);
64+
65+
muninNodeBuilder.Services.TryAddEnumerable(
66+
ServiceDescriptor.KeyedSingleton<IHostedService, MuninNodeBackgroundService>(
67+
serviceKey: muninNodeBuilder.ServiceKey
68+
)
69+
);
70+
#endif
71+
}
72+
);
73+
}
74+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// SPDX-FileCopyrightText: 2025 smdn <smdn@smdn.jp>
2+
// SPDX-License-Identifier: MIT
3+
4+
#pragma warning disable CA1848 // For improved performance, use the LoggerMessage delegates instead of calling 'LoggerExtensions.LogInformation(ILogger, string?, params object?[])'
5+
6+
using System;
7+
using System.Net;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
11+
using Microsoft.Extensions.Hosting;
12+
using Microsoft.Extensions.Logging;
13+
14+
namespace Smdn.Net.MuninNode.Hosting;
15+
16+
public class MuninNodeBackgroundService : BackgroundService {
17+
private IMuninNode node;
18+
private readonly ILogger<MuninNodeBackgroundService>? logger;
19+
20+
/// <inheritdoc cref="IMuninNode.EndPoint"/>
21+
public EndPoint EndPoint => (node ?? throw new ObjectDisposedException(GetType().FullName)).EndPoint;
22+
23+
#if false
24+
// TODO: support ServiceKey
25+
// this code does not work currently
26+
// https://github.com/dotnet/runtime/issues/99085
27+
public MuninNodeBackgroundService(
28+
[ServiceKey] string serviceKey,
29+
IServiceProvider serviceProvider
30+
)
31+
{
32+
this.node = serviceProvider.GetRequiredKeyedService<IMuninNode>(serviceKey);
33+
this.logger = serviceProvider.GetService<ILoggerFactory>()?.CreateLogger<MuninNodeBackgroundService>();
34+
}
35+
#endif
36+
37+
public MuninNodeBackgroundService(
38+
IMuninNode node
39+
)
40+
: this(
41+
node: node ?? throw new ArgumentNullException(nameof(node)),
42+
logger: null
43+
)
44+
{
45+
}
46+
47+
public MuninNodeBackgroundService(
48+
IMuninNode node,
49+
ILogger<MuninNodeBackgroundService>? logger
50+
)
51+
{
52+
this.node = node ?? throw new ArgumentNullException(nameof(node));
53+
this.logger = logger;
54+
}
55+
56+
public override void Dispose()
57+
{
58+
if (node is IDisposable disposableNode)
59+
disposableNode.Dispose();
60+
61+
node = null!;
62+
63+
base.Dispose();
64+
65+
GC.SuppressFinalize(this);
66+
}
67+
68+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
69+
{
70+
if (node is null)
71+
throw new ObjectDisposedException(GetType().FullName);
72+
73+
logger?.LogInformation("Munin node '{HostName}' starting.", node.HostName);
74+
75+
await node.RunAsync(stoppingToken).ConfigureAwait(false);
76+
77+
logger?.LogInformation("Munin node '{HostName}' stopped.", node.HostName);
78+
79+
if (node is IDisposable disposableNode)
80+
disposableNode.Dispose();
81+
}
82+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2025 smdn <smdn@smdn.jp>
3+
SPDX-License-Identifier: MIT
4+
-->
5+
<Project Sdk="Microsoft.NET.Sdk">
6+
<PropertyGroup>
7+
<Nullable>enable</Nullable>
8+
<RootNamespace/> <!-- empty the root namespace so that the namespace is determined only by the directory name, for code style rule IDE0130 -->
9+
</PropertyGroup>
10+
11+
<PropertyGroup>
12+
<TargetFrameworks Condition=" '$(EnableTargetFrameworkDotNet80)' == 'true' ">net8.0;$(TargetFrameworks)</TargetFrameworks>
13+
<TargetFrameworks Condition=" '$(EnableTargetFrameworkNetFx)' == 'true' ">$(TargetFrameworks)</TargetFrameworks>
14+
</PropertyGroup>
15+
16+
<ItemGroup>
17+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.*" />
18+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.*"/>
19+
</ItemGroup>
20+
</Project>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// SPDX-FileCopyrightText: 2025 smdn <smdn@smdn.jp>
2+
// SPDX-License-Identifier: MIT
3+
using System;
4+
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Hosting;
7+
8+
using NUnit.Framework;
9+
10+
namespace Smdn.Net.MuninNode.Hosting;
11+
12+
[TestFixture]
13+
public class IServiceCollectionExtensionsTests {
14+
[Test]
15+
public void AddHostedMuninNodeService_ArgumentNull()
16+
{
17+
var services = new ServiceCollection();
18+
19+
Assert.That(
20+
() => services.AddHostedMuninNodeService(
21+
configureNode: null!,
22+
buildNode: builder => { }
23+
),
24+
Throws
25+
.ArgumentNullException
26+
.With
27+
.Property(nameof(ArgumentNullException.ParamName))
28+
.EqualTo("configureNode")
29+
);
30+
31+
Assert.That(
32+
() => services.AddHostedMuninNodeService(
33+
configureNode: options => { },
34+
buildNode: null!
35+
),
36+
Throws
37+
.ArgumentNullException
38+
.With
39+
.Property(nameof(ArgumentNullException.ParamName))
40+
.EqualTo("buildNode")
41+
);
42+
}
43+
44+
[Test]
45+
public void AddHostedMuninNodeService()
46+
{
47+
var services = new ServiceCollection();
48+
49+
services.AddHostedMuninNodeService(
50+
configureNode: options => { },
51+
buildNode: builder => { }
52+
);
53+
54+
var serviceProvider = services.BuildServiceProvider();
55+
var muninNodeService = serviceProvider.GetRequiredService<IHostedService>();
56+
57+
Assert.That(muninNodeService, Is.TypeOf<MuninNodeBackgroundService>());
58+
}
59+
}

0 commit comments

Comments
 (0)