Skip to content

Smdn.Net.MuninNode.Hosting version 3.1.0

Latest
Compare
Choose a tag to compare
@smdn smdn released this 01 Jun 09:48
· 31 commits to main since this release
a8da76e

Released package

Release notes

The full release notes are available at gist.

Change log

Change log in this release:

API changes

API changes in this release:
diff --git a/doc/api-list/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting-net8.0.apilist.cs b/doc/api-list/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting-net8.0.apilist.cs
index 387ee95..4d934a8 100644
--- a/doc/api-list/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting-net8.0.apilist.cs
+++ b/doc/api-list/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting-net8.0.apilist.cs
@@ -1,43 +1,50 @@
-// Smdn.Net.MuninNode.Hosting.dll (Smdn.Net.MuninNode.Hosting-3.0.0)
+// Smdn.Net.MuninNode.Hosting.dll (Smdn.Net.MuninNode.Hosting-3.1.0)
 //   Name: Smdn.Net.MuninNode.Hosting
-//   AssemblyVersion: 3.0.0.0
-//   InformationalVersion: 3.0.0+0830d2fdea4a5b05d99958b5116ff7b474590f2f
+//   AssemblyVersion: 3.1.0.0
+//   InformationalVersion: 3.1.0+d9b937573b3b1dd41eaf878498bcf5d285c10471
 //   TargetFramework: .NETCoreApp,Version=v8.0
 //   Configuration: Release
 //   Referenced assemblies:
 //     Microsoft.Extensions.DependencyInjection.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
 //     Microsoft.Extensions.Hosting.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
 //     Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
-//     Smdn.Net.MuninNode, Version=2.2.0.0, Culture=neutral
+//     Smdn.Net.MuninNode, Version=2.5.0.0, Culture=neutral
 //     System.Net.Primitives, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 //     System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 #nullable enable annotations
 
 using System;
+using System.Diagnostics.CodeAnalysis;
 using System.Net;
 using System.Threading;
 using System.Threading.Tasks;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Logging;
 using Smdn.Net.MuninNode;
 using Smdn.Net.MuninNode.DependencyInjection;
 using Smdn.Net.MuninNode.Hosting;
 
 namespace Smdn.Net.MuninNode.Hosting {
   public static class IServiceCollectionExtensions {
     public static IServiceCollection AddHostedMuninNodeService(this IServiceCollection services, Action<MuninNodeOptions> configureNode, Action<IMuninNodeBuilder> buildNode) {}
+    public static IServiceCollection AddHostedMuninNodeService<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TMuninNodeBackgroundService, TMuninNode, TMuninNodeOptions, TMuninNodeBuilder>(this IServiceCollection services, Action<TMuninNodeOptions> configureNode, Func<IMuninServiceBuilder, string, TMuninNodeBuilder> createNodeBuilder, Action<TMuninNodeBuilder> buildNode) where TMuninNodeBackgroundService : MuninNodeBackgroundService where TMuninNode : class, IMuninNode where TMuninNodeOptions : MuninNodeOptions, new() where TMuninNodeBuilder : MuninNodeBuilder {}
+    public static IServiceCollection AddHostedMuninNodeService<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TMuninNodeBackgroundService, TMuninNodeBuilder>(this IServiceCollection services, Func<IMuninServiceBuilder, TMuninNodeBuilder> buildMunin) where TMuninNodeBackgroundService : MuninNodeBackgroundService where TMuninNodeBuilder : MuninNodeBuilder {}
+    public static IServiceCollection AddHostedMuninNodeService<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TMuninNodeBackgroundService, TMuninNodeService, TMuninNodeImplementation, TMuninNodeOptions, TMuninNodeBuilder>(this IServiceCollection services, Action<TMuninNodeOptions> configureNode, Func<IMuninServiceBuilder, string, TMuninNodeBuilder> createNodeBuilder, Action<TMuninNodeBuilder> buildNode) where TMuninNodeBackgroundService : MuninNodeBackgroundService where TMuninNodeService : class, IMuninNode where TMuninNodeImplementation : class, TMuninNodeService where TMuninNodeOptions : MuninNodeOptions, new() where TMuninNodeBuilder : MuninNodeBuilder {}
   }
 
   public class MuninNodeBackgroundService : BackgroundService {
     public MuninNodeBackgroundService(IMuninNode node) {}
     public MuninNodeBackgroundService(IMuninNode node, ILogger<MuninNodeBackgroundService>? logger) {}
 
     public EndPoint EndPoint { get; }
+    protected ILogger? Logger { get; }
 
     public override void Dispose() {}
     protected override async Task ExecuteAsync(CancellationToken stoppingToken) {}
+    public override async Task StartAsync(CancellationToken cancellationToken) {}
+    public override async Task StopAsync(CancellationToken cancellationToken) {}
   }
 }
 // API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.5.0.0.
 // Smdn.Reflection.ReverseGenerating.ListApi.Core v1.3.1.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
diff --git a/doc/api-list/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting-netstandard2.1.apilist.cs b/doc/api-list/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting-netstandard2.1.apilist.cs
index 3162c84..a325383 100644
--- a/doc/api-list/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting-netstandard2.1.apilist.cs
+++ b/doc/api-list/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting-netstandard2.1.apilist.cs
@@ -1,42 +1,48 @@
-// Smdn.Net.MuninNode.Hosting.dll (Smdn.Net.MuninNode.Hosting-3.0.0)
+// Smdn.Net.MuninNode.Hosting.dll (Smdn.Net.MuninNode.Hosting-3.1.0)
 //   Name: Smdn.Net.MuninNode.Hosting
-//   AssemblyVersion: 3.0.0.0
-//   InformationalVersion: 3.0.0+0830d2fdea4a5b05d99958b5116ff7b474590f2f
+//   AssemblyVersion: 3.1.0.0
+//   InformationalVersion: 3.1.0+d9b937573b3b1dd41eaf878498bcf5d285c10471
 //   TargetFramework: .NETStandard,Version=v2.1
 //   Configuration: Release
 //   Referenced assemblies:
 //     Microsoft.Extensions.DependencyInjection.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
 //     Microsoft.Extensions.Hosting.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
 //     Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
-//     Smdn.Net.MuninNode, Version=2.2.0.0, Culture=neutral
+//     Smdn.Net.MuninNode, Version=2.5.0.0, Culture=neutral
 //     netstandard, Version=2.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
 #nullable enable annotations
 
 using System;
 using System.Net;
 using System.Threading;
 using System.Threading.Tasks;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Logging;
 using Smdn.Net.MuninNode;
 using Smdn.Net.MuninNode.DependencyInjection;
 using Smdn.Net.MuninNode.Hosting;
 
 namespace Smdn.Net.MuninNode.Hosting {
   public static class IServiceCollectionExtensions {
     public static IServiceCollection AddHostedMuninNodeService(this IServiceCollection services, Action<MuninNodeOptions> configureNode, Action<IMuninNodeBuilder> buildNode) {}
+    public static IServiceCollection AddHostedMuninNodeService<TMuninNodeBackgroundService, TMuninNode, TMuninNodeOptions, TMuninNodeBuilder>(this IServiceCollection services, Action<TMuninNodeOptions> configureNode, Func<IMuninServiceBuilder, string, TMuninNodeBuilder> createNodeBuilder, Action<TMuninNodeBuilder> buildNode) where TMuninNodeBackgroundService : MuninNodeBackgroundService where TMuninNode : class, IMuninNode where TMuninNodeOptions : MuninNodeOptions, new() where TMuninNodeBuilder : MuninNodeBuilder {}
+    public static IServiceCollection AddHostedMuninNodeService<TMuninNodeBackgroundService, TMuninNodeBuilder>(this IServiceCollection services, Func<IMuninServiceBuilder, TMuninNodeBuilder> buildMunin) where TMuninNodeBackgroundService : MuninNodeBackgroundService where TMuninNodeBuilder : MuninNodeBuilder {}
+    public static IServiceCollection AddHostedMuninNodeService<TMuninNodeBackgroundService, TMuninNodeService, TMuninNodeImplementation, TMuninNodeOptions, TMuninNodeBuilder>(this IServiceCollection services, Action<TMuninNodeOptions> configureNode, Func<IMuninServiceBuilder, string, TMuninNodeBuilder> createNodeBuilder, Action<TMuninNodeBuilder> buildNode) where TMuninNodeBackgroundService : MuninNodeBackgroundService where TMuninNodeService : class, IMuninNode where TMuninNodeImplementation : class, TMuninNodeService where TMuninNodeOptions : MuninNodeOptions, new() where TMuninNodeBuilder : MuninNodeBuilder {}
   }
 
   public class MuninNodeBackgroundService : BackgroundService {
     public MuninNodeBackgroundService(IMuninNode node) {}
     public MuninNodeBackgroundService(IMuninNode node, ILogger<MuninNodeBackgroundService>? logger) {}
 
     public EndPoint EndPoint { get; }
+    protected ILogger? Logger { get; }
 
     public override void Dispose() {}
     protected override async Task ExecuteAsync(CancellationToken stoppingToken) {}
+    public override async Task StartAsync(CancellationToken cancellationToken) {}
+    public override async Task StopAsync(CancellationToken cancellationToken) {}
   }
 }
 // API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.5.0.0.
 // Smdn.Reflection.ReverseGenerating.ListApi.Core v1.3.1.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)

Full changes

Full changes in this release:
diff --git a/src/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting.csproj b/src/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting.csproj
index f3af407..bf9dc3d 100644
--- a/src/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting.csproj
+++ b/src/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting.csproj
@@ -5,9 +5,9 @@ SPDX-License-Identifier: MIT
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFrameworks>netstandard2.1;net8.0</TargetFrameworks>
-    <VersionPrefix>3.0.0</VersionPrefix>
+    <VersionPrefix>3.1.0</VersionPrefix>
     <VersionSuffix></VersionSuffix>
-    <!--<PackageValidationBaselineVersion>3.0.0</PackageValidationBaselineVersion>-->
+    <PackageValidationBaselineVersion>3.0.0</PackageValidationBaselineVersion>
     <RootNamespace/> <!-- empty the root namespace so that the namespace is determined only by the directory name, for code style rule IDE0030 -->
     <Nullable>enable</Nullable>
     <NoWarn>CS1591;$(NoWarn)</NoWarn> <!-- CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' -->
@@ -34,7 +34,7 @@ This library uses [Smdn.Net.MuninNode](https://www.nuget.org/packages/Smdn.Net.M
 
   <ItemGroup>
     <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
-    <ProjectOrPackageReference ReferencePackageVersion="[2.2.0,4.0.0)" Include="..\Smdn.Net.MuninNode\Smdn.Net.MuninNode.csproj" />
+    <ProjectOrPackageReference ReferencePackageVersion="[2.5.0,4.0.0)" Include="..\Smdn.Net.MuninNode\Smdn.Net.MuninNode.csproj" />
   </ItemGroup>
 
   <Target Name="GenerateReadmeFileContent">
diff --git a/src/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting/IServiceCollectionExtensions.cs b/src/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting/IServiceCollectionExtensions.cs
index 4a3b85a..fc1893e 100644
--- a/src/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting/IServiceCollectionExtensions.cs
+++ b/src/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting/IServiceCollectionExtensions.cs
@@ -2,6 +2,9 @@
 // SPDX-License-Identifier: MIT
 
 using System;
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_DYNAMICALLYACCESSEDMEMBERSATTRIBUTE
+using System.Diagnostics.CodeAnalysis;
+#endif
 
 using Microsoft.Extensions.DependencyInjection;
 
@@ -20,8 +23,8 @@ public static class IServiceCollectionExtensions {
   /// configure the <c>Munin-Node</c> to be built.
   /// </param>
   /// <param name="buildNode">
-  /// An <see cref="Action{IMuninServiceBuilder}"/> to build <c>Munin-Node</c> using with
-  /// the <see cref="IMuninServiceBuilder"/>.
+  /// An <see cref="Action{IMuninNodeBuilder}"/> to build <c>Munin-Node</c> using with
+  /// the <see cref="IMuninNodeBuilder"/>.
   /// </param>
   /// <returns>The current <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
   /// <exception cref="ArgumentNullException">
@@ -29,41 +32,264 @@ public static class IServiceCollectionExtensions {
   /// <paramref name="configureNode"/> is <see langword="null"/>, or
   /// <paramref name="buildNode"/> is <see langword="null"/>.
   /// </exception>
+#pragma warning disable CS0618 // accept MuninNodeBuilder instead of IMuninNodeBuilder
   public static IServiceCollection AddHostedMuninNodeService(
     this IServiceCollection services,
     Action<MuninNodeOptions> configureNode,
     Action<IMuninNodeBuilder> buildNode
   )
+#pragma warning restore CS0618
+    => AddHostedMuninNodeService<
+      MuninNodeBackgroundService,
+      IMuninNode,
+      IMuninNode,
+      MuninNodeOptions,
+      DefaultMuninNodeBuilder
+    >(
+      services: services ?? throw new ArgumentNullException(nameof(services)),
+      configureNode: configureNode ?? throw new ArgumentNullException(nameof(configureNode)),
+      createNodeBuilder: static (serviceBuilder, serviceKey) => new(serviceBuilder, serviceKey),
+      buildNode: builder => (buildNode ?? throw new ArgumentNullException(nameof(buildNode)))(builder)
+    );
+
+  private class DefaultMuninNodeBuilder(IMuninServiceBuilder serviceBuilder, string serviceKey)
+    : MuninNodeBuilder(serviceBuilder, serviceKey) {
+  }
+
+  /// <summary>
+  /// Add <typeparamref name="TMuninNodeBackgroundService"/>, which runs <typeparamref name="TMuninNode"/> as an
+  /// <see cref="Microsoft.Extensions.Hosting.IHostedService"/>, to <see cref="IServiceCollection"/>.
+  /// </summary>
+  /// <typeparam name="TMuninNodeBackgroundService">
+  /// The type of <see cref="Microsoft.Extensions.Hosting.IHostedService"/> service to add to the <seealso cref="IServiceCollection"/>.
+  /// </typeparam>
+  /// <typeparam name="TMuninNode">
+  /// The type of <see cref="IMuninNode"/> service to add to the <seealso cref="IServiceCollection"/>.
+  /// </typeparam>
+  /// <typeparam name="TMuninNodeOptions">
+  /// The extended type of <see cref="MuninNodeOptions"/> to configure the <typeparamref name="TMuninNode"/>.
+  /// </typeparam>
+  /// <typeparam name="TMuninNodeBuilder">
+  /// The extended type of <see cref="MuninNodeBuilder"/> to build the <typeparamref name="TMuninNode"/>.
+  /// </typeparam>
+  /// <param name="services">
+  /// An <see cref="IServiceCollection"/> that the built <typeparamref name="TMuninNodeBackgroundService"/> and
+  /// <typeparamref name="TMuninNode"/> will be added to.
+  /// </param>
+  /// <param name="configureNode">
+  /// An <see cref="Action{TMuninNodeOptions}"/> to setup <typeparamref name="TMuninNodeOptions"/> to
+  /// configure the <typeparamref name="TMuninNode"/> to be built.
+  /// </param>
+  /// <param name="createNodeBuilder">
+  /// An <see cref="Func{TMuninNodeBuilder}"/> to create <typeparamref name="TMuninNodeBuilder"/> to build
+  /// the <typeparamref name="TMuninNode"/>.
+  /// </param>
+  /// <param name="buildNode">
+  /// An <see cref="Action{TMuninNodeBuilder}"/> to build <typeparamref name="TMuninNode"/> using with
+  /// the <typeparamref name="TMuninNodeBuilder"/>.
+  /// </param>
+  /// <returns>The current <see cref="IMuninNodeBuilder"/> so that additional calls can be chained.</returns>
+  /// <exception cref="ArgumentNullException">
+  /// <paramref name="services"/> is <see langword="null"/>, or
+  /// <paramref name="configureNode"/> is <see langword="null"/>, or
+  /// <paramref name="createNodeBuilder"/> is <see langword="null"/>, or
+  /// <paramref name="buildNode"/> is <see langword="null"/>.
+  /// </exception>
+#pragma warning disable IDE0055
+  public static
+  IServiceCollection AddHostedMuninNodeService<
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_DYNAMICALLYACCESSEDMEMBERSATTRIBUTE
+    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+#endif
+    TMuninNodeBackgroundService,
+    TMuninNode,
+    TMuninNodeOptions,
+    TMuninNodeBuilder
+  >(
+    this IServiceCollection services,
+    Action<TMuninNodeOptions> configureNode,
+    Func<IMuninServiceBuilder, string, TMuninNodeBuilder> createNodeBuilder,
+    Action<TMuninNodeBuilder> buildNode
+  )
+    where TMuninNodeBackgroundService : MuninNodeBackgroundService
+    where TMuninNode : class, IMuninNode
+    where TMuninNodeOptions : MuninNodeOptions, new()
+    where TMuninNodeBuilder : MuninNodeBuilder
+#pragma warning restore IDE0055
+    => AddHostedMuninNodeService<
+      TMuninNodeBackgroundService,
+      TMuninNode,
+      TMuninNode,
+      TMuninNodeOptions,
+      TMuninNodeBuilder
+    >(
+      services: services ?? throw new ArgumentNullException(nameof(services)),
+      configureNode: configureNode ?? throw new ArgumentNullException(nameof(configureNode)),
+      createNodeBuilder: createNodeBuilder ?? throw new ArgumentNullException(nameof(configureNode)),
+      buildNode: buildNode ?? throw new ArgumentNullException(nameof(buildNode))
+    );
+
+  /// <summary>
+  /// Add <typeparamref name="TMuninNodeBackgroundService"/>, which runs <typeparamref name="TMuninNodeImplementation"/> as an
+  /// <see cref="Microsoft.Extensions.Hosting.IHostedService"/>, to <see cref="IServiceCollection"/>.
+  /// </summary>
+  /// <typeparam name="TMuninNodeBackgroundService">
+  /// The type of <see cref="Microsoft.Extensions.Hosting.IHostedService"/> service to add to the <seealso cref="IServiceCollection"/>.
+  /// </typeparam>
+  /// <typeparam name="TMuninNodeService">
+  /// The type of <see cref="IMuninNode"/> service to add to the <seealso cref="IServiceCollection"/>.
+  /// </typeparam>
+  /// <typeparam name="TMuninNodeImplementation">
+  /// The type of <typeparamref name="TMuninNodeService"/> implementation.
+  /// </typeparam>
+  /// <typeparam name="TMuninNodeOptions">
+  /// The extended type of <see cref="MuninNodeOptions"/> to configure the <typeparamref name="TMuninNodeImplementation"/>.
+  /// </typeparam>
+  /// <typeparam name="TMuninNodeBuilder">
+  /// The extended type of <see cref="MuninNodeBuilder"/> to build the <typeparamref name="TMuninNodeImplementation"/>.
+  /// </typeparam>
+  /// <param name="services">
+  /// An <see cref="IServiceCollection"/> that the built <typeparamref name="TMuninNodeBackgroundService"/> and
+  /// <typeparamref name="TMuninNodeImplementation"/> will be added to.
+  /// </param>
+  /// <param name="configureNode">
+  /// An <see cref="Action{TMuninNodeOptions}"/> to setup <typeparamref name="TMuninNodeOptions"/> to
+  /// configure the <typeparamref name="TMuninNodeImplementation"/> to be built.
+  /// </param>
+  /// <param name="createNodeBuilder">
+  /// An <see cref="Func{TMuninNodeBuilder}"/> to create <typeparamref name="TMuninNodeBuilder"/> to build
+  /// the <typeparamref name="TMuninNodeImplementation"/>.
+  /// </param>
+  /// <param name="buildNode">
+  /// An <see cref="Action{TMuninNodeBuilder}"/> to build <typeparamref name="TMuninNodeImplementation"/> using with
+  /// the <typeparamref name="TMuninNodeBuilder"/>.
+  /// </param>
+  /// <returns>The current <see cref="IMuninNodeBuilder"/> so that additional calls can be chained.</returns>
+  /// <exception cref="ArgumentNullException">
+  /// <paramref name="services"/> is <see langword="null"/>, or
+  /// <paramref name="configureNode"/> is <see langword="null"/>, or
+  /// <paramref name="createNodeBuilder"/> is <see langword="null"/>, or
+  /// <paramref name="buildNode"/> is <see langword="null"/>.
+  /// </exception>
+#pragma warning disable IDE0055
+  public static
+  IServiceCollection AddHostedMuninNodeService<
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_DYNAMICALLYACCESSEDMEMBERSATTRIBUTE
+    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+#endif
+    TMuninNodeBackgroundService,
+    TMuninNodeService,
+    TMuninNodeImplementation,
+    TMuninNodeOptions,
+    TMuninNodeBuilder
+  >(
+    this IServiceCollection services,
+    Action<TMuninNodeOptions> configureNode,
+    Func<IMuninServiceBuilder, string, TMuninNodeBuilder> createNodeBuilder,
+    Action<TMuninNodeBuilder> buildNode
+  )
+    where TMuninNodeBackgroundService : MuninNodeBackgroundService
+    where TMuninNodeService : class, IMuninNode
+    where TMuninNodeImplementation : class, TMuninNodeService
+    where TMuninNodeOptions : MuninNodeOptions, new()
+    where TMuninNodeBuilder : MuninNodeBuilder
+#pragma warning restore IDE0055
   {
     if (services is null)
       throw new ArgumentNullException(nameof(services));
     if (configureNode is null)
       throw new ArgumentNullException(nameof(configureNode));
+    if (createNodeBuilder is null)
+      throw new ArgumentNullException(nameof(createNodeBuilder));
     if (buildNode is null)
       throw new ArgumentNullException(nameof(buildNode));
 
-    return services.AddMunin(
-      muninBuilder => {
-        var muninNodeBuilder = muninBuilder.AddNode(configureNode);
+    return AddHostedMuninNodeService<TMuninNodeBackgroundService, TMuninNodeBuilder>(
+      services: services,
+      buildMunin: muninBuilder => {
+        var muninNodeBuilder = muninBuilder.AddNode<
+          TMuninNodeService,
+          TMuninNodeImplementation,
+          TMuninNodeOptions,
+          TMuninNodeBuilder
+        >(
+          configureNode,
+          createNodeBuilder
+        );
 
         buildNode(muninNodeBuilder);
 
-        muninNodeBuilder.Services.AddHostedService<MuninNodeBackgroundService>();
+        return muninNodeBuilder;
+      }
+    );
+  }
 
-        // TODO: support keyed service
-#if false
-        var muninNodeBuilder = muninBuilder.AddKeyedNode(configureNode);
+  /// <summary>
+  /// Add <typeparamref name="TMuninNodeBackgroundService"/>, which runs <c>Munin-Node</c> as an
+  /// <see cref="Microsoft.Extensions.Hosting.IHostedService"/>, to <see cref="IServiceCollection"/>.
+  /// </summary>
+  /// <typeparam name="TMuninNodeBackgroundService">
+  /// The type of <see cref="Microsoft.Extensions.Hosting.IHostedService"/> service to add to the <seealso cref="IServiceCollection"/>.
+  /// </typeparam>
+  /// <typeparam name="TMuninNodeBuilder">
+  /// The extended type of <see cref="MuninNodeBuilder"/> to build the <c>Munin-Node</c>.
+  /// </typeparam>
+  /// <param name="services">
+  /// An <see cref="IServiceCollection"/> that the built <typeparamref name="TMuninNodeBackgroundService"/> and
+  /// <c>Munin-Node</c> will be added to.
+  /// </param>
+  /// <param name="buildMunin">
+  /// A <see cref="Func{IMuninServiceBuilder, TMuninNodeBuilder}"/> that registers at least one <see cref="IMuninNode"/> to
+  /// <paramref name="services"/> and returns <typeparamref name="TMuninNodeBuilder"/>, which builds the <see cref="IMuninNode"/>
+  /// to be registered.
+  /// </param>
+  /// <returns>The current <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
+  /// <exception cref="ArgumentNullException">
+  /// <paramref name="services"/> is <see langword="null"/>, or
+  /// <paramref name="buildMunin"/> is <see langword="null"/>.
+  /// </exception>
+  /// <remarks>
+  /// In future implementations, <typeparamref name="TMuninNodeBackgroundService"/> to be registered by
+  /// this method will use the same key as the <see cref="MuninNodeBuilder.ServiceKey"/> of the
+  /// <typeparamref name="TMuninNodeBuilder"/> returned by the <paramref name="buildMunin"/>.
+  /// </remarks>
+#pragma warning disable IDE0055
+  public static
+  IServiceCollection AddHostedMuninNodeService<
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_DYNAMICALLYACCESSEDMEMBERSATTRIBUTE
+    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+#endif
+    TMuninNodeBackgroundService,
+    TMuninNodeBuilder
+  >(
+    this IServiceCollection services,
+    Func<IMuninServiceBuilder, TMuninNodeBuilder> buildMunin
+  )
+    where TMuninNodeBackgroundService : MuninNodeBackgroundService
+    where TMuninNodeBuilder : MuninNodeBuilder
+#pragma warning restore IDE0055
+  {
+    if (services is null)
+      throw new ArgumentNullException(nameof(services));
+    if (buildMunin is null)
+      throw new ArgumentNullException(nameof(buildMunin));
 
-        buildNode(muninNodeBuilder);
+    return services.AddMunin(
+      muninBuilder => {
+        var muninNodeBuilder = buildMunin(muninBuilder);
+
+        muninNodeBuilder.Services.AddHostedService<TMuninNodeBackgroundService>();
 
+        // TODO: support keyed service
+#if false
         // these code does not work currently
         // https://github.com/dotnet/runtime/issues/99085
-        muninNodeBuilder.Services.AddHostedService<MuninNodeBackgroundService>(
+        muninNodeBuilder.Services.AddHostedService<TMuninNodeBackgroundService>(
           serviceKey: muninNodeBuilder.ServiceKey
         );
 
         muninNodeBuilder.Services.TryAddEnumerable(
-          ServiceDescriptor.KeyedSingleton<IHostedService, MuninNodeBackgroundService>(
+          ServiceDescriptor.KeyedSingleton<IHostedService, TMuninNodeBackgroundService>(
             serviceKey: muninNodeBuilder.ServiceKey
           )
         );
diff --git a/src/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting/MuninNodeBackgroundService.cs b/src/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting/MuninNodeBackgroundService.cs
index 7243909..459e79f 100644
--- a/src/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting/MuninNodeBackgroundService.cs
+++ b/src/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting/MuninNodeBackgroundService.cs
@@ -1,8 +1,5 @@
 // SPDX-FileCopyrightText: 2025 smdn <[email protected]>
 // SPDX-License-Identifier: MIT
-
-#pragma warning disable CA1848 // For improved performance, use the LoggerMessage delegates instead of calling 'LoggerExtensions.LogInformation(ILogger, string?, params object?[])'
-
 using System;
 using System.Net;
 using System.Threading;
@@ -14,12 +11,34 @@ using Microsoft.Extensions.Logging;
 namespace Smdn.Net.MuninNode.Hosting;
 
 public class MuninNodeBackgroundService : BackgroundService {
+  private static readonly Action<ILogger, string, Exception?> LogStarting = LoggerMessage.Define<string>(
+    LogLevel.Information,
+    eventId: default, // TODO
+    formatString: "Munin node '{HostName}' starting."
+  );
+  private static readonly Action<ILogger, string, Exception?> LogStarted = LoggerMessage.Define<string>(
+    LogLevel.Information,
+    eventId: default, // TODO
+    formatString: "Munin node '{HostName}' started."
+  );
+  private static readonly Action<ILogger, string, Exception?> LogStopping = LoggerMessage.Define<string>(
+    LogLevel.Information,
+    eventId: default, // TODO
+    formatString: "Munin node '{HostName}' stopping."
+  );
+  private static readonly Action<ILogger, string, Exception?> LogStopped = LoggerMessage.Define<string>(
+    LogLevel.Information,
+    eventId: default, // TODO
+    formatString: "Munin node '{HostName}' stopped."
+  );
+
   private IMuninNode node;
-  private readonly ILogger<MuninNodeBackgroundService>? logger;
 
   /// <inheritdoc cref="IMuninNode.EndPoint"/>
   public EndPoint EndPoint => (node ?? throw new ObjectDisposedException(GetType().FullName)).EndPoint;
 
+  protected ILogger? Logger { get; }
+
 #if false
   // TODO: support ServiceKey
   // this code does not work currently
@@ -30,7 +49,7 @@ public class MuninNodeBackgroundService : BackgroundService {
   )
   {
     this.node = serviceProvider.GetRequiredKeyedService<IMuninNode>(serviceKey);
-    this.logger = serviceProvider.GetService<ILoggerFactory>()?.CreateLogger<MuninNodeBackgroundService>();
+    Logger = serviceProvider.GetService<ILoggerFactory>()?.CreateLogger<MuninNodeBackgroundService>();
   }
 #endif
 
@@ -50,7 +69,7 @@ public class MuninNodeBackgroundService : BackgroundService {
   )
   {
     this.node = node ?? throw new ArgumentNullException(nameof(node));
-    this.logger = logger;
+    Logger = logger;
   }
 
   public override void Dispose()
@@ -65,16 +84,48 @@ public class MuninNodeBackgroundService : BackgroundService {
     GC.SuppressFinalize(this);
   }
 
-  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+  public override async Task StartAsync(CancellationToken cancellationToken)
   {
     if (node is null)
       throw new ObjectDisposedException(GetType().FullName);
 
-    logger?.LogInformation("Munin node '{HostName}' starting.", node.HostName);
+    cancellationToken.ThrowIfCancellationRequested();
+
+    if (Logger is not null)
+      LogStarting(Logger, node.HostName, null);
+
+    await base.StartAsync(cancellationToken).ConfigureAwait(false);
+
+    if (Logger is not null)
+      LogStarted(Logger, node.HostName, null);
+  }
+
+  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+  {
+    if (node is null)
+      throw new ObjectDisposedException(GetType().FullName);
 
     await node.RunAsync(stoppingToken).ConfigureAwait(false);
+  }
+
+  public override async Task StopAsync(CancellationToken cancellationToken)
+  {
+    if (node is null)
+      throw new ObjectDisposedException(GetType().FullName);
+
+    if (Logger is not null)
+      LogStopping(Logger, node.HostName, null);
+
+    await base.StopAsync(cancellationToken).ConfigureAwait(false);
+
+    cancellationToken.ThrowIfCancellationRequested();
+
+    // attempt graceful shutdown if possible
+    if (node is NodeBase stoppableNode)
+      await stoppableNode.StopAsync(cancellationToken).ConfigureAwait(false);
 
-    logger?.LogInformation("Munin node '{HostName}' stopped.", node.HostName);
+    if (Logger is not null)
+      LogStopped(Logger, node.HostName, null);
 
     if (node is IDisposable disposableNode)
       disposableNode.Dispose();

Notes

What's Changed

Notable changes

  • Introduce protocol abstraction interfaces by @smdn in #22
  • Implement 'dirtyconfig' protocol extension by @smdn in #23
  • Implement 'multigraph' protocol extension by @smdn in #25
  • Improve DI-related APIs to support custom types by @smdn in #27

Full Changelog: releases/Smdn.Net.MuninNode.Hosting-3.0.0...releases/Smdn.Net.MuninNode.Hosting-3.1.0