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 6ad094f..d62c293 100644
--- a/src/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting.csproj
+++ b/src/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting.csproj
@@ -34,7 +34,7 @@ This library uses [Smdn.Net.MuninNode](https://www.nuget.org/packages/Smdn.Net.M
-
+
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 Munin-Node to be built.
///
///
- /// An to build Munin-Node using with
- /// the .
+ /// An to build Munin-Node using with
+ /// the .
///
/// The current so that additional calls can be chained.
///
@@ -29,41 +32,264 @@ public static class IServiceCollectionExtensions {
/// is , or
/// is .
///
+#pragma warning disable CS0618 // accept MuninNodeBuilder instead of IMuninNodeBuilder
public static IServiceCollection AddHostedMuninNodeService(
this IServiceCollection services,
Action configureNode,
Action 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) {
+ }
+
+ ///
+ /// Add , which runs as an
+ /// , to .
+ ///
+ ///
+ /// The type of service to add to the .
+ ///
+ ///
+ /// The type of service to add to the .
+ ///
+ ///
+ /// The extended type of to configure the .
+ ///
+ ///
+ /// The extended type of to build the .
+ ///
+ ///
+ /// An that the built and
+ /// will be added to.
+ ///
+ ///
+ /// An to setup to
+ /// configure the to be built.
+ ///
+ ///
+ /// An to create to build
+ /// the .
+ ///
+ ///
+ /// An to build using with
+ /// the .
+ ///
+ /// The current so that additional calls can be chained.
+ ///
+ /// is , or
+ /// is , or
+ /// is , or
+ /// is .
+ ///
+#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 configureNode,
+ Func createNodeBuilder,
+ Action 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))
+ );
+
+ ///
+ /// Add , which runs as an
+ /// , to .
+ ///
+ ///
+ /// The type of service to add to the .
+ ///
+ ///
+ /// The type of service to add to the .
+ ///
+ ///
+ /// The type of implementation.
+ ///
+ ///
+ /// The extended type of to configure the .
+ ///
+ ///
+ /// The extended type of to build the .
+ ///
+ ///
+ /// An that the built and
+ /// will be added to.
+ ///
+ ///
+ /// An to setup to
+ /// configure the to be built.
+ ///
+ ///
+ /// An to create to build
+ /// the .
+ ///
+ ///
+ /// An to build using with
+ /// the .
+ ///
+ /// The current so that additional calls can be chained.
+ ///
+ /// is , or
+ /// is , or
+ /// is , or
+ /// is .
+ ///
+#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 configureNode,
+ Func createNodeBuilder,
+ Action 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(
+ services: services,
+ buildMunin: muninBuilder => {
+ var muninNodeBuilder = muninBuilder.AddNode<
+ TMuninNodeService,
+ TMuninNodeImplementation,
+ TMuninNodeOptions,
+ TMuninNodeBuilder
+ >(
+ configureNode,
+ createNodeBuilder
+ );
buildNode(muninNodeBuilder);
- muninNodeBuilder.Services.AddHostedService();
+ return muninNodeBuilder;
+ }
+ );
+ }
- // TODO: support keyed service
-#if false
- var muninNodeBuilder = muninBuilder.AddKeyedNode(configureNode);
+ ///
+ /// Add , which runs Munin-Node as an
+ /// , to .
+ ///
+ ///
+ /// The type of service to add to the .
+ ///
+ ///
+ /// The extended type of to build the Munin-Node.
+ ///
+ ///
+ /// An that the built and
+ /// Munin-Node will be added to.
+ ///
+ ///
+ /// A that registers at least one to
+ /// and returns , which builds the
+ /// to be registered.
+ ///
+ /// The current so that additional calls can be chained.
+ ///
+ /// is , or
+ /// is .
+ ///
+ ///
+ /// In future implementations, to be registered by
+ /// this method will use the same key as the of the
+ /// returned by the .
+ ///
+#pragma warning disable IDE0055
+ public static
+ IServiceCollection AddHostedMuninNodeService<
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_DYNAMICALLYACCESSEDMEMBERSATTRIBUTE
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+#endif
+ TMuninNodeBackgroundService,
+ TMuninNodeBuilder
+ >(
+ this IServiceCollection services,
+ Func 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();
+ // TODO: support keyed service
+#if false
// these code does not work currently
// https://github.com/dotnet/runtime/issues/99085
- muninNodeBuilder.Services.AddHostedService(
+ muninNodeBuilder.Services.AddHostedService(
serviceKey: muninNodeBuilder.ServiceKey
);
muninNodeBuilder.Services.TryAddEnumerable(
- ServiceDescriptor.KeyedSingleton(
+ ServiceDescriptor.KeyedSingleton(
serviceKey: muninNodeBuilder.ServiceKey
)
);
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninNodeBuilder.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninNodeBuilder.cs
index b17d19c..cc82372 100644
--- a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninNodeBuilder.cs
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninNodeBuilder.cs
@@ -14,6 +14,7 @@ namespace Smdn.Net.MuninNode.DependencyInjection;
///
///
///
+[Obsolete($"Use or inherit {nameof(MuninNodeBuilder)} instead.")]
public interface IMuninNodeBuilder {
///
/// Gets the where the Munin-Node services are configured.
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninNodeBuilderExtensions.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninNodeBuilderExtensions.cs
index 07c8689..6098230 100644
--- a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninNodeBuilderExtensions.cs
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninNodeBuilderExtensions.cs
@@ -11,7 +11,16 @@
namespace Smdn.Net.MuninNode.DependencyInjection;
+[Obsolete($"Use {nameof(MuninNodeBuilderExtensions)} instead.")]
public static class IMuninNodeBuilderExtensions {
+ private static MuninNodeBuilder ThrowIfBuilderTypeIsNotSupported(IMuninNodeBuilder builder)
+ {
+ if (builder is not MuninNodeBuilder muninNodeBuilder)
+ throw new NotSupportedException($"The builder implementation of type `{builder.GetType().FullName}` does not support service key configuration.");
+
+ return muninNodeBuilder;
+ }
+
#pragma warning disable CS0419
///
/// If is called, the configurations made by this method will be overridden.
@@ -21,17 +30,10 @@ public static IMuninNodeBuilder AddPlugin(
IPlugin plugin
)
#pragma warning restore CS0419
- {
- if (builder is null)
- throw new ArgumentNullException(nameof(builder));
- if (plugin is null)
- throw new ArgumentNullException(nameof(plugin));
-
- return AddPlugin(
- builder: builder,
- buildPlugin: _ => plugin
+ => MuninNodeBuilderExtensions.AddPlugin(
+ builder: ThrowIfBuilderTypeIsNotSupported(builder ?? throw new ArgumentNullException(nameof(builder))),
+ plugin: plugin ?? throw new ArgumentNullException(nameof(plugin))
);
- }
#pragma warning disable CS0419
///
@@ -42,19 +44,10 @@ public static IMuninNodeBuilder AddPlugin(
Func buildPlugin
)
#pragma warning restore CS0419
- {
- if (builder is null)
- throw new ArgumentNullException(nameof(builder));
- if (buildPlugin is null)
- throw new ArgumentNullException(nameof(buildPlugin));
-
- if (builder is not DefaultMuninNodeBuilder defaultMuninNodeBuilder)
- throw new NotSupportedException($"The builder implementation of type `{builder.GetType().FullName}` does not support service key configuration.");
-
- defaultMuninNodeBuilder.AddPluginFactory(buildPlugin);
-
- return builder;
- }
+ => MuninNodeBuilderExtensions.AddPlugin(
+ builder: ThrowIfBuilderTypeIsNotSupported(builder ?? throw new ArgumentNullException(nameof(builder))),
+ buildPlugin: buildPlugin ?? throw new ArgumentNullException(nameof(buildPlugin))
+ );
#pragma warning disable CS0419
///
@@ -66,17 +59,10 @@ public static IMuninNodeBuilder UsePluginProvider(
IPluginProvider pluginProvider
)
#pragma warning restore CS0419
- {
- if (builder is null)
- throw new ArgumentNullException(nameof(builder));
- if (pluginProvider is null)
- throw new ArgumentNullException(nameof(pluginProvider));
-
- return UsePluginProvider(
- builder: builder,
- buildPluginProvider: _ => pluginProvider
+ => MuninNodeBuilderExtensions.UsePluginProvider(
+ builder: ThrowIfBuilderTypeIsNotSupported(builder ?? throw new ArgumentNullException(nameof(builder))),
+ pluginProvider: pluginProvider ?? throw new ArgumentNullException(nameof(pluginProvider))
);
- }
#pragma warning disable CS0419
///
@@ -88,19 +74,10 @@ public static IMuninNodeBuilder UsePluginProvider(
Func buildPluginProvider
)
#pragma warning restore CS0419
- {
- if (builder is null)
- throw new ArgumentNullException(nameof(builder));
- if (buildPluginProvider is null)
- throw new ArgumentNullException(nameof(buildPluginProvider));
-
- if (builder is not DefaultMuninNodeBuilder defaultMuninNodeBuilder)
- throw new NotSupportedException($"The builder implementation of type `{builder.GetType().FullName}` does not support service key configuration.");
-
- defaultMuninNodeBuilder.SetPluginProviderFactory(buildPluginProvider);
-
- return builder;
- }
+ => MuninNodeBuilderExtensions.UsePluginProvider(
+ builder: ThrowIfBuilderTypeIsNotSupported(builder ?? throw new ArgumentNullException(nameof(builder))),
+ buildPluginProvider: buildPluginProvider ?? throw new ArgumentNullException(nameof(buildPluginProvider))
+ );
#pragma warning disable CS0419
///
@@ -111,17 +88,10 @@ public static IMuninNodeBuilder UseSessionCallback(
INodeSessionCallback sessionCallback
)
#pragma warning restore CS0419
- {
- if (builder is null)
- throw new ArgumentNullException(nameof(builder));
- if (sessionCallback is null)
- throw new ArgumentNullException(nameof(sessionCallback));
-
- return UseSessionCallback(
- builder: builder,
- buildSessionCallback: _ => sessionCallback
+ => MuninNodeBuilderExtensions.UseSessionCallback(
+ builder: ThrowIfBuilderTypeIsNotSupported(builder ?? throw new ArgumentNullException(nameof(builder))),
+ sessionCallback: sessionCallback ?? throw new ArgumentNullException(nameof(sessionCallback))
);
- }
#pragma warning disable CS0419
///
@@ -133,29 +103,12 @@ public static IMuninNodeBuilder UseSessionCallback(
Func? reportSessionClosedAsyncFunc
)
#pragma warning restore CS0419
- => UseSessionCallback(
- builder: builder,
- buildSessionCallback: _ => new SessionCallbackFuncWrapper(
- reportSessionStartedAsyncFunc,
- reportSessionClosedAsyncFunc
- )
+ => MuninNodeBuilderExtensions.UseSessionCallback(
+ builder: ThrowIfBuilderTypeIsNotSupported(builder ?? throw new ArgumentNullException(nameof(builder))),
+ reportSessionStartedAsyncFunc: reportSessionStartedAsyncFunc,
+ reportSessionClosedAsyncFunc: reportSessionClosedAsyncFunc
);
- private sealed class SessionCallbackFuncWrapper(
- Func? reportSessionStartedAsyncFunc,
- Func? reportSessionClosedAsyncFunc
- ) : INodeSessionCallback {
- public ValueTask ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken)
- => reportSessionStartedAsyncFunc is null
- ? default
- : reportSessionStartedAsyncFunc(sessionId, cancellationToken);
-
- public ValueTask ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken)
- => reportSessionClosedAsyncFunc is null
- ? default
- : reportSessionClosedAsyncFunc(sessionId, cancellationToken);
- }
-
#pragma warning disable CS0419
///
/// If is called, the configurations made by this method will be overridden.
@@ -165,78 +118,35 @@ public static IMuninNodeBuilder UseSessionCallback(
Func buildSessionCallback
)
#pragma warning restore CS0419
- {
- if (builder is null)
- throw new ArgumentNullException(nameof(builder));
- if (buildSessionCallback is null)
- throw new ArgumentNullException(nameof(buildSessionCallback));
-
- if (builder is not DefaultMuninNodeBuilder defaultMuninNodeBuilder)
- throw new NotSupportedException($"The builder implementation of type `{builder.GetType().FullName}` does not support service key configuration.");
-
- defaultMuninNodeBuilder.SetSessionCallbackFactory(buildSessionCallback);
-
- return builder;
- }
+ => MuninNodeBuilderExtensions.UseSessionCallback(
+ builder: ThrowIfBuilderTypeIsNotSupported(builder ?? throw new ArgumentNullException(nameof(builder))),
+ buildSessionCallback: buildSessionCallback ?? throw new ArgumentNullException(nameof(buildSessionCallback))
+ );
public static IMuninNodeBuilder UseListenerFactory(
this IMuninNodeBuilder builder,
IMuninNodeListenerFactory listenerFactory
)
- {
- if (builder is null)
- throw new ArgumentNullException(nameof(builder));
- if (listenerFactory is null)
- throw new ArgumentNullException(nameof(listenerFactory));
-
- return UseListenerFactory(
- builder: builder,
- buildListenerFactory: _ => listenerFactory
+ => MuninNodeBuilderExtensions.UseListenerFactory(
+ builder: ThrowIfBuilderTypeIsNotSupported(builder ?? throw new ArgumentNullException(nameof(builder))),
+ listenerFactory: listenerFactory ?? throw new ArgumentNullException(nameof(listenerFactory))
);
- }
public static IMuninNodeBuilder UseListenerFactory(
this IMuninNodeBuilder builder,
Func> createListenerAsyncFunc
)
- {
- if (builder is null)
- throw new ArgumentNullException(nameof(builder));
- if (createListenerAsyncFunc is null)
- throw new ArgumentNullException(nameof(createListenerAsyncFunc));
-
- return UseListenerFactory(
- builder: builder,
- buildListenerFactory: serviceProvider => new CreateListenerAsyncFuncWrapper(
- serviceProvider,
- createListenerAsyncFunc
- )
+ => MuninNodeBuilderExtensions.UseListenerFactory(
+ builder: ThrowIfBuilderTypeIsNotSupported(builder ?? throw new ArgumentNullException(nameof(builder))),
+ createListenerAsyncFunc: createListenerAsyncFunc ?? throw new ArgumentNullException(nameof(createListenerAsyncFunc))
);
- }
-
- private sealed class CreateListenerAsyncFuncWrapper(
- IServiceProvider serviceProvider,
- Func> createListenerAsyncFunc
- ) : IMuninNodeListenerFactory {
- public ValueTask CreateAsync(EndPoint endPoint, IMuninNode node, CancellationToken cancellationToken)
- => createListenerAsyncFunc(serviceProvider, endPoint, node, cancellationToken);
- }
public static IMuninNodeBuilder UseListenerFactory(
this IMuninNodeBuilder builder,
Func buildListenerFactory
)
- {
- if (builder is null)
- throw new ArgumentNullException(nameof(builder));
- if (buildListenerFactory is null)
- throw new ArgumentNullException(nameof(buildListenerFactory));
-
- if (builder is not DefaultMuninNodeBuilder defaultMuninNodeBuilder)
- throw new NotSupportedException($"The builder implementation of type `{builder.GetType().FullName}` does not support service key configuration.");
-
- defaultMuninNodeBuilder.SetListenerFactory(buildListenerFactory);
-
- return builder;
- }
+ => MuninNodeBuilderExtensions.UseListenerFactory(
+ builder: ThrowIfBuilderTypeIsNotSupported(builder ?? throw new ArgumentNullException(nameof(builder))),
+ buildListenerFactory: buildListenerFactory ?? throw new ArgumentNullException(nameof(buildListenerFactory))
+ );
}
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninServiceBuilderExtensions.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninServiceBuilderExtensions.cs
index e89bf75..8579ac9 100644
--- a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninServiceBuilderExtensions.cs
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninServiceBuilderExtensions.cs
@@ -11,11 +11,15 @@ public static class IMuninServiceBuilderExtensions {
///
/// Adds a Munin-Node to the with default configurations.
///
- /// An to build the Munin-Node to be added.
+ ///
+ /// An that the built Munin-Node will be added to.
+ ///
/// The current so that additional calls can be chained.
+#pragma warning disable CS0618 // TODO: return IMuninNodeBuilder instead of MuninNodeBuilder
public static IMuninNodeBuilder AddNode(
this IMuninServiceBuilder builder
)
+#pragma warning restore CS0618
=> AddNode(
builder: builder,
configure: _ => { }
@@ -25,44 +29,202 @@ this IMuninServiceBuilder builder
/// Adds a Munin-Node to the with specified configurations.
///
///
- /// An to build the Munin-Node to be added.
+ /// An that the built Munin-Node will be added to.
///
///
/// An to setup to
/// configure the Munin-Node to be built.
///
/// The current so that additional calls can be chained.
+#pragma warning disable CS0618 // TODO: return IMuninNodeBuilder instead of MuninNodeBuilder
public static IMuninNodeBuilder AddNode(
this IMuninServiceBuilder builder,
Action configure
)
+#pragma warning restore CS0618
+ => AddNode<
+ IMuninNode,
+ IMuninNode,
+ MuninNodeOptions,
+ MuninNodeBuilder
+ >(
+ builder: builder ?? throw new ArgumentNullException(nameof(builder)),
+ configure: configure ?? throw new ArgumentNullException(nameof(configure)),
+ createBuilder: static (serviceBuilder, serviceKey) => new(serviceBuilder, serviceKey)
+ );
+
+ ///
+ /// Adds a Munin-Node to the with specified configurations.
+ ///
+ ///
+ /// The extended type of to configure the Munin-Node.
+ ///
+ ///
+ /// The extended type of to build the Munin-Node.
+ ///
+ ///
+ /// An that the built Munin-Node will be added to.
+ ///
+ ///
+ /// An to setup to
+ /// configure the Munin-Node to be built.
+ ///
+ ///
+ /// An to create to build
+ /// the Munin-Node.
+ ///
+ /// The current so that additional calls can be chained.
+ ///
+ /// is , or
+ /// is , or
+ /// is .
+ ///
+ public static
+ TMuninNodeBuilder AddNode<
+ TMuninNodeOptions,
+ TMuninNodeBuilder
+ >(
+ this IMuninServiceBuilder builder,
+ Action configure,
+ Func createBuilder
+ )
+ where TMuninNodeOptions : MuninNodeOptions, new()
+ where TMuninNodeBuilder : MuninNodeBuilder
+ => AddNode<
+ IMuninNode,
+ IMuninNode,
+ TMuninNodeOptions,
+ TMuninNodeBuilder
+ >(
+ builder: builder ?? throw new ArgumentNullException(nameof(builder)),
+ configure: configure ?? throw new ArgumentNullException(nameof(configure)),
+ createBuilder: createBuilder ?? throw new ArgumentNullException(nameof(createBuilder))
+ );
+
+ ///
+ /// Adds a to the with specified configurations.
+ ///
+ ///
+ /// The type of service to add to the .
+ ///
+ ///
+ /// The extended type of to configure the .
+ ///
+ ///
+ /// The extended type of to build the .
+ ///
+ ///
+ /// An that the built will be added to.
+ ///
+ ///
+ /// An to setup to
+ /// configure the to be built.
+ ///
+ ///
+ /// An to create to build
+ /// the .
+ ///
+ /// The current so that additional calls can be chained.
+ ///
+ /// is , or
+ /// is , or
+ /// is .
+ ///
+ public static
+ TMuninNodeBuilder AddNode<
+ TMuninNode,
+ TMuninNodeOptions,
+ TMuninNodeBuilder
+ >(
+ this IMuninServiceBuilder builder,
+ Action configure,
+ Func createBuilder
+ )
+ where TMuninNode : class, IMuninNode
+ where TMuninNodeOptions : MuninNodeOptions, new()
+ where TMuninNodeBuilder : MuninNodeBuilder
+ => AddNode<
+ TMuninNode,
+ TMuninNode,
+ TMuninNodeOptions,
+ TMuninNodeBuilder
+ >(
+ builder: builder ?? throw new ArgumentNullException(nameof(builder)),
+ configure: configure ?? throw new ArgumentNullException(nameof(configure)),
+ createBuilder: createBuilder ?? throw new ArgumentNullException(nameof(createBuilder))
+ );
+
+ ///
+ /// Adds a to the with specified configurations.
+ ///
+ ///
+ /// The type of service to add to the .
+ ///
+ ///
+ /// The type of implementation.
+ ///
+ ///
+ /// The extended type of to configure the .
+ ///
+ ///
+ /// The extended type of to build the .
+ ///
+ ///
+ /// An that the built will be added to.
+ ///
+ ///
+ /// An to setup to
+ /// configure the to be built.
+ ///
+ ///
+ /// An to create to build
+ /// the .
+ ///
+ /// The current so that additional calls can be chained.
+ ///
+ /// is , or
+ /// is , or
+ /// is .
+ ///
+ public static
+ TMuninNodeBuilder AddNode<
+ TMuninNodeService,
+ TMuninNodeImplementation,
+ TMuninNodeOptions,
+ TMuninNodeBuilder
+ >(
+ this IMuninServiceBuilder builder,
+ Action configure,
+ Func createBuilder
+ )
+ where TMuninNodeService : class, IMuninNode
+ where TMuninNodeImplementation : class, TMuninNodeService
+ where TMuninNodeOptions : MuninNodeOptions, new()
+ where TMuninNodeBuilder : MuninNodeBuilder
{
if (builder is null)
throw new ArgumentNullException(nameof(builder));
if (configure is null)
throw new ArgumentNullException(nameof(configure));
+ if (createBuilder is null)
+ throw new ArgumentNullException(nameof(createBuilder));
- var options = new MuninNodeOptions();
+ var configuredOptions = new TMuninNodeOptions();
- configure(options);
+ configure(configuredOptions);
- var nodeBuilder = new DefaultMuninNodeBuilder(
- serviceBuilder: builder,
- serviceKey: options.HostName // use configured hostname as a service key and option name
+ var nodeBuilder = createBuilder(
+ /* serviceBuilder: */ builder,
+ /* serviceKey: */ configuredOptions.HostName // use configured hostname as a service key and option name
);
- _ = builder.Services.Configure(
+ _ = builder.Services.Configure(
name: nodeBuilder.ServiceKey, // configure MuninNodeOptions for this builder
- opts => {
- opts.Address = options.Address;
- opts.Port = options.Port;
- opts.HostName = options.HostName;
- opts.AccessRule = options.AccessRule;
- }
+ options => options.Configure(configuredOptions)
);
builder.Services.Add(
- ServiceDescriptor.KeyedSingleton(
+ ServiceDescriptor.KeyedSingleton(
serviceKey: nodeBuilder.ServiceKey,
implementationFactory: (_, _) => nodeBuilder
)
@@ -70,19 +232,23 @@ Action configure
// add keyed/singleton IMuninNode
builder.Services.Add(
- ServiceDescriptor.KeyedSingleton(
+ ServiceDescriptor.KeyedSingleton(
serviceKey: nodeBuilder.ServiceKey,
static (serviceProvider, serviceKey)
- => serviceProvider.GetRequiredKeyedService(serviceKey).Build(serviceProvider)
+ => serviceProvider
+ .GetRequiredKeyedService(serviceKey)
+ .Build(serviceProvider)
)
);
// add keyless/multiple IMuninNode
+#pragma warning disable IDE0200
builder.Services.Add(
- ServiceDescriptor.Transient(
- nodeBuilder.Build
+ ServiceDescriptor.Transient(
+ serviceProvider => nodeBuilder.Build(serviceProvider)
)
);
+#pragma warning restore IDE0200
return nodeBuilder;
}
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/DefaultMuninNodeBuilder.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/MuninNodeBuilder.cs
similarity index 56%
rename from src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/DefaultMuninNodeBuilder.cs
rename to src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/MuninNodeBuilder.cs
index 9b43d69..b7505a0 100644
--- a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/DefaultMuninNodeBuilder.cs
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/MuninNodeBuilder.cs
@@ -14,22 +14,39 @@
namespace Smdn.Net.MuninNode.DependencyInjection;
-internal sealed class DefaultMuninNodeBuilder : IMuninNodeBuilder {
+///
+/// Provides builder pattern for configuring and building the Munin-Node.
+///
+///
+#pragma warning disable CS0618 // TODO: remove IMuninNodeBuilder
+public class MuninNodeBuilder : IMuninNodeBuilder {
+#pragma warning restore CS0618
private readonly List> pluginFactories = new(capacity: 4);
private Func? buildPluginProvider;
private Func? buildSessionCallback;
private Func? buildListenerFactory;
+ ///
+ /// Gets the where the Munin-Node services are configured.
+ ///
public IServiceCollection Services { get; }
+
+ ///
+ /// Gets the key of Munin-Node service.
+ ///
+ ///
+ /// The value set as the hostname of the Munin-Node (see ) is used as the service key.
+ ///
+ ///
public string ServiceKey { get; }
- public DefaultMuninNodeBuilder(IMuninServiceBuilder serviceBuilder, string serviceKey)
+ protected internal MuninNodeBuilder(IMuninServiceBuilder serviceBuilder, string serviceKey)
{
Services = (serviceBuilder ?? throw new ArgumentNullException(nameof(serviceBuilder))).Services;
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
}
- public void AddPluginFactory(Func buildPlugin)
+ internal void AddPluginFactory(Func buildPlugin)
{
if (buildPlugin is null)
throw new ArgumentNullException(nameof(buildPlugin));
@@ -37,7 +54,7 @@ public void AddPluginFactory(Func buildPlugin)
pluginFactories.Add(serviceProvider => buildPlugin(serviceProvider));
}
- public void SetPluginProviderFactory(
+ internal void SetPluginProviderFactory(
Func buildPluginProvider
)
{
@@ -47,7 +64,7 @@ Func buildPluginProvider
this.buildPluginProvider = buildPluginProvider;
}
- public void SetSessionCallbackFactory(
+ internal void SetSessionCallbackFactory(
Func buildSessionCallback
)
{
@@ -57,7 +74,7 @@ Func buildSessionCallback
this.buildSessionCallback = buildSessionCallback;
}
- public void SetListenerFactory(
+ internal void SetListenerFactory(
Func buildListenerFactory
)
{
@@ -67,13 +84,19 @@ Func buildListenerFactory
this.buildListenerFactory = buildListenerFactory;
}
+ ///
+ /// Builds the Munin-Node with current configurations.
+ ///
+ ///
+ /// An that provides the services to be used by the being built.
+ ///
+ /// An initialized .
public IMuninNode Build(IServiceProvider serviceProvider)
{
if (serviceProvider is null)
throw new ArgumentNullException(nameof(serviceProvider));
- return new DefaultMuninNode(
- options: serviceProvider.GetRequiredService>().Get(name: ServiceKey),
+ return Build(
pluginProvider: buildPluginProvider is null
? new PluginProvider(
plugins: pluginFactories.Select(factory => factory(serviceProvider)).ToList(),
@@ -81,7 +104,7 @@ public IMuninNode Build(IServiceProvider serviceProvider)
)
: buildPluginProvider.Invoke(serviceProvider),
listenerFactory: buildListenerFactory?.Invoke(serviceProvider),
- logger: serviceProvider.GetService()?.CreateLogger()
+ serviceProvider: serviceProvider
);
}
@@ -98,4 +121,32 @@ public PluginProvider(
SessionCallback = sessionCallback;
}
}
+
+ protected virtual IMuninNode Build(
+ IPluginProvider pluginProvider,
+ IMuninNodeListenerFactory? listenerFactory,
+ IServiceProvider serviceProvider
+ )
+ {
+ if (serviceProvider is null)
+ throw new ArgumentNullException(nameof(serviceProvider));
+
+ return new DefaultMuninNode(
+ options: GetConfiguredOptions(serviceProvider),
+ pluginProvider: pluginProvider,
+ listenerFactory: listenerFactory,
+ logger: serviceProvider.GetService()?.CreateLogger()
+ );
+ }
+
+ protected TMuninNodeOptions GetConfiguredOptions(IServiceProvider serviceProvider)
+ where TMuninNodeOptions : MuninNodeOptions
+ {
+ if (serviceProvider is null)
+ throw new ArgumentNullException(nameof(serviceProvider));
+
+ return serviceProvider
+ .GetRequiredService>()
+ .Get(name: ServiceKey);
+ }
}
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/MuninNodeBuilderExtensions.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/MuninNodeBuilderExtensions.cs
new file mode 100644
index 0000000..f500ae1
--- /dev/null
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/MuninNodeBuilderExtensions.cs
@@ -0,0 +1,258 @@
+// SPDX-FileCopyrightText: 2025 smdn
+// SPDX-License-Identifier: MIT
+
+using System;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Smdn.Net.MuninNode.Transport;
+using Smdn.Net.MuninPlugin;
+
+namespace Smdn.Net.MuninNode.DependencyInjection;
+
+public static class MuninNodeBuilderExtensions {
+#pragma warning disable CS0419
+ ///
+ /// If is called, the configurations made by this method will be overridden.
+ ///
+ public static TMuninNodeBuilder AddPlugin(
+ this TMuninNodeBuilder builder,
+ IPlugin plugin
+ )
+ where TMuninNodeBuilder : MuninNodeBuilder
+#pragma warning restore CS0419
+ {
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+ if (plugin is null)
+ throw new ArgumentNullException(nameof(plugin));
+
+ return AddPlugin(
+ builder: builder,
+ buildPlugin: _ => plugin
+ );
+ }
+
+#pragma warning disable CS0419
+ ///
+ /// If is called, the configurations made by this method will be overridden.
+ ///
+ public static TMuninNodeBuilder AddPlugin(
+ this TMuninNodeBuilder builder,
+ Func buildPlugin
+ )
+ where TMuninNodeBuilder : MuninNodeBuilder
+#pragma warning restore CS0419
+ {
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+ if (buildPlugin is null)
+ throw new ArgumentNullException(nameof(buildPlugin));
+
+ builder.AddPluginFactory(buildPlugin);
+
+ return builder;
+ }
+
+#pragma warning disable CS0419
+ ///
+ /// Calling this method will override the configurations made by
+ /// and .
+ ///
+ public static TMuninNodeBuilder UsePluginProvider(
+ this TMuninNodeBuilder builder,
+ IPluginProvider pluginProvider
+ )
+ where TMuninNodeBuilder : MuninNodeBuilder
+#pragma warning restore CS0419
+ {
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+ if (pluginProvider is null)
+ throw new ArgumentNullException(nameof(pluginProvider));
+
+ return UsePluginProvider(
+ builder: builder,
+ buildPluginProvider: _ => pluginProvider
+ );
+ }
+
+#pragma warning disable CS0419
+ ///
+ /// Calling this method will override the configurations made by
+ /// and .
+ ///
+ public static TMuninNodeBuilder UsePluginProvider(
+ this TMuninNodeBuilder builder,
+ Func buildPluginProvider
+ )
+ where TMuninNodeBuilder : MuninNodeBuilder
+#pragma warning restore CS0419
+ {
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+ if (buildPluginProvider is null)
+ throw new ArgumentNullException(nameof(buildPluginProvider));
+
+ builder.SetPluginProviderFactory(buildPluginProvider);
+
+ return builder;
+ }
+
+#pragma warning disable CS0419
+ ///
+ /// If is called, the configurations made by this method will be overridden.
+ ///
+ public static TMuninNodeBuilder UseSessionCallback(
+ this TMuninNodeBuilder builder,
+ INodeSessionCallback sessionCallback
+ )
+ where TMuninNodeBuilder : MuninNodeBuilder
+#pragma warning restore CS0419
+ {
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+ if (sessionCallback is null)
+ throw new ArgumentNullException(nameof(sessionCallback));
+
+ return UseSessionCallback(
+ builder: builder,
+ buildSessionCallback: _ => sessionCallback
+ );
+ }
+
+#pragma warning disable CS0419
+ ///
+ /// If is called, the configurations made by this method will be overridden.
+ ///
+ public static TMuninNodeBuilder UseSessionCallback(
+ this TMuninNodeBuilder builder,
+ Func? reportSessionStartedAsyncFunc,
+ Func? reportSessionClosedAsyncFunc
+ )
+ where TMuninNodeBuilder : MuninNodeBuilder
+#pragma warning restore CS0419
+ => UseSessionCallback(
+ builder: builder,
+ buildSessionCallback: _ => new SessionCallbackFuncWrapper(
+ reportSessionStartedAsyncFunc,
+ reportSessionClosedAsyncFunc
+ )
+ );
+
+ private sealed class SessionCallbackFuncWrapper(
+ Func? reportSessionStartedAsyncFunc,
+ Func? reportSessionClosedAsyncFunc
+ ) : INodeSessionCallback {
+ public ValueTask ReportSessionStartedAsync(string sessionId, CancellationToken cancellationToken)
+ => reportSessionStartedAsyncFunc is null
+ ? default
+ : reportSessionStartedAsyncFunc(sessionId, cancellationToken);
+
+ public ValueTask ReportSessionClosedAsync(string sessionId, CancellationToken cancellationToken)
+ => reportSessionClosedAsyncFunc is null
+ ? default
+ : reportSessionClosedAsyncFunc(sessionId, cancellationToken);
+ }
+
+#pragma warning disable CS0419
+ ///
+ /// If is called, the configurations made by this method will be overridden.
+ ///
+ public static TMuninNodeBuilder UseSessionCallback(
+ this TMuninNodeBuilder builder,
+ Func buildSessionCallback
+ )
+ where TMuninNodeBuilder : MuninNodeBuilder
+#pragma warning restore CS0419
+ {
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+ if (buildSessionCallback is null)
+ throw new ArgumentNullException(nameof(buildSessionCallback));
+
+ builder.SetSessionCallbackFactory(buildSessionCallback);
+
+ return builder;
+ }
+
+ public static TMuninNodeBuilder UseListenerFactory(
+ this TMuninNodeBuilder builder,
+ IMuninNodeListenerFactory listenerFactory
+ )
+ where TMuninNodeBuilder : MuninNodeBuilder
+ {
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+ if (listenerFactory is null)
+ throw new ArgumentNullException(nameof(listenerFactory));
+
+ return UseListenerFactory(
+ builder: builder,
+ buildListenerFactory: _ => listenerFactory
+ );
+ }
+
+ public static TMuninNodeBuilder UseListenerFactory(
+ this TMuninNodeBuilder builder,
+ Func> createListenerAsyncFunc
+ )
+ where TMuninNodeBuilder : MuninNodeBuilder
+ {
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+ if (createListenerAsyncFunc is null)
+ throw new ArgumentNullException(nameof(createListenerAsyncFunc));
+
+ return UseListenerFactory(
+ builder: builder,
+ buildListenerFactory: serviceProvider => new CreateListenerAsyncFuncWrapper(
+ serviceProvider,
+ createListenerAsyncFunc
+ )
+ );
+ }
+
+ private sealed class CreateListenerAsyncFuncWrapper(
+ IServiceProvider serviceProvider,
+ Func> createListenerAsyncFunc
+ ) : IMuninNodeListenerFactory {
+ public ValueTask CreateAsync(EndPoint endPoint, IMuninNode node, CancellationToken cancellationToken)
+ => createListenerAsyncFunc(serviceProvider, endPoint, node, cancellationToken);
+ }
+
+ public static TMuninNodeBuilder UseListenerFactory(
+ this TMuninNodeBuilder builder,
+ Func buildListenerFactory
+ )
+ where TMuninNodeBuilder : MuninNodeBuilder
+ {
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+ if (buildListenerFactory is null)
+ throw new ArgumentNullException(nameof(buildListenerFactory));
+
+ builder.SetListenerFactory(buildListenerFactory);
+
+ return builder;
+ }
+
+ public static TMuninNode Build(
+ this MuninNodeBuilder builder,
+ IServiceProvider serviceProvider
+ ) where TMuninNode : IMuninNode
+ {
+ if (builder is null)
+ throw new ArgumentNullException(nameof(builder));
+ if (serviceProvider is null)
+ throw new ArgumentNullException(nameof(serviceProvider));
+
+ var n = builder.Build(serviceProvider);
+
+ if (n is not TMuninNode node)
+ throw new InvalidOperationException($"The type '{n.GetType()}' of the constructed instance did not match the requested type '{typeof(TMuninNode)}'.");
+
+ return node;
+ }
+}
diff --git a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/MuninNodeOptions.cs b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/MuninNodeOptions.cs
index 2b4a062..3a77f2b 100644
--- a/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/MuninNodeOptions.cs
+++ b/src/Smdn.Net.MuninNode/Smdn.Net.MuninNode/MuninNodeOptions.cs
@@ -14,7 +14,7 @@ namespace Smdn.Net.MuninNode;
/// Options to configure the Munin-Node.
///
///
-public sealed class MuninNodeOptions {
+public class MuninNodeOptions {
private static IPAddress LoopbackAddress => Socket.OSSupportsIPv6 ? IPAddress.IPv6Loopback : IPAddress.Loopback;
private static IPAddress AnyAddress => Socket.OSSupportsIPv6 ? IPAddress.IPv6Any : IPAddress.Any;
@@ -124,6 +124,17 @@ public MuninNodeOptions Clone()
=> (MuninNodeOptions)MemberwiseClone();
#endif
+ protected internal virtual void Configure(MuninNodeOptions baseOptions)
+ {
+ if (baseOptions is null)
+ throw new ArgumentNullException(nameof(baseOptions));
+
+ Address = baseOptions.Address;
+ Port = baseOptions.Port;
+ HostName = baseOptions.HostName;
+ AccessRule = baseOptions.AccessRule;
+ }
+
///
/// Set the value of the property to use the address of
/// or .
diff --git a/tests/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting/IServiceCollectionExtensions.cs b/tests/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting/IServiceCollectionExtensions.cs
index 8cd7078..1baacde 100644
--- a/tests/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting/IServiceCollectionExtensions.cs
+++ b/tests/Smdn.Net.MuninNode.Hosting/Smdn.Net.MuninNode.Hosting/IServiceCollectionExtensions.cs
@@ -4,9 +4,14 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Options;
using NUnit.Framework;
+using Smdn.Net.MuninNode.DependencyInjection;
+using Smdn.Net.MuninNode.Transport;
+using Smdn.Net.MuninPlugin;
+
namespace Smdn.Net.MuninNode.Hosting;
[TestFixture]
@@ -56,4 +61,170 @@ public void AddHostedMuninNodeService()
Assert.That(muninNodeService, Is.TypeOf());
}
+
+ private class CustomMuninNodeBuilder : MuninNodeBuilder
+ where TMuninNodeOptions : MuninNodeOptions {
+ private readonly Func nodeFactory;
+
+ public CustomMuninNodeBuilder(
+ IMuninServiceBuilder serviceBuilder,
+ string serviceKey,
+ Func nodeFactory
+ )
+ : base(serviceBuilder, serviceKey)
+ {
+ this.nodeFactory = nodeFactory;
+ }
+
+ protected override IMuninNode Build(
+ IPluginProvider pluginProvider,
+ IMuninNodeListenerFactory? listenerFactory,
+ IServiceProvider serviceProvider
+ )
+ => nodeFactory(
+ GetConfiguredOptions(serviceProvider),
+ pluginProvider,
+ listenerFactory,
+ serviceProvider
+ );
+ }
+
+ private class CustomMuninNode : LocalNode {
+ public CustomMuninNodeOptions Options { get; }
+ public override string HostName => Options.HostName;
+ public override IPluginProvider PluginProvider { get; }
+
+ public string? ExtraOption => Options.ExtraOption;
+
+ public CustomMuninNode(
+ CustomMuninNodeOptions options,
+ IPluginProvider pluginProvider,
+ IMuninNodeListenerFactory? listenerFactory
+ )
+ : base(
+ listenerFactory: listenerFactory,
+ accessRule: null,
+ logger: null
+ )
+ {
+ Options = options;
+ PluginProvider = pluginProvider;
+ }
+ }
+
+ private class CustomMuninNodeOptions : MuninNodeOptions {
+ public string? ExtraOption { get; set; }
+
+ protected override void Configure(MuninNodeOptions baseOptions)
+ {
+ base.Configure(baseOptions ?? throw new ArgumentNullException(nameof(baseOptions)));
+
+ if (baseOptions is CustomMuninNodeOptions options)
+ ExtraOption = options.ExtraOption;
+ }
+ }
+
+ private class CustomMuninNodeBackgroundService(CustomMuninNode node) : MuninNodeBackgroundService(node) {
+ public string? ExtraOption => node.ExtraOption;
+ }
+
+ [Test]
+ public void AddHostedMuninNodeService_CustomBackgroundServiceType()
+ {
+ const string HostName = "munin-node.localhost";
+ const string ExtraOptionValue = "foo";
+
+ var services = new ServiceCollection();
+
+ services.AddHostedMuninNodeService<
+ CustomMuninNodeBackgroundService,
+ CustomMuninNode,
+ CustomMuninNode,
+ CustomMuninNodeOptions,
+ CustomMuninNodeBuilder
+ >(
+ configureNode: options => {
+ options.HostName = HostName;
+ options.ExtraOption = ExtraOptionValue;
+ },
+ createNodeBuilder: static (serviceBuilder, serviceKey) => new CustomMuninNodeBuilder(
+ serviceBuilder: serviceBuilder,
+ serviceKey: serviceKey,
+ nodeFactory: static (options, pluginProvider, listenerFactory, serviceProvider) => new CustomMuninNode(
+ options,
+ pluginProvider,
+ listenerFactory
+ )
+ ),
+ buildNode: nodeBuilder => { }
+ );
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ var options = serviceProvider
+ .GetRequiredService>()
+ .Get(name: HostName);
+
+ Assert.That(options.HostName, Is.EqualTo(HostName));
+ Assert.That(options.ExtraOption, Is.EqualTo(ExtraOptionValue));
+
+ var muninNodeBackgroundService = serviceProvider.GetRequiredService();
+
+ Assert.That(muninNodeBackgroundService, Is.TypeOf());
+ Assert.That(
+ (muninNodeBackgroundService as CustomMuninNodeBackgroundService)!.ExtraOption,
+ Is.EqualTo(ExtraOptionValue)
+ );
+ }
+
+ [Test]
+ public void AddHostedMuninNodeService_CustomBackgroundServiceType_WithIMuninServiceBuilder()
+ {
+ const string HostName = "munin-node.localhost";
+ const string ExtraOptionValue = "foo";
+
+ var services = new ServiceCollection();
+
+ services.AddHostedMuninNodeService<
+ CustomMuninNodeBackgroundService,
+ CustomMuninNodeBuilder
+ >(
+ buildMunin: muninServiceBuilder => muninServiceBuilder.AddNode<
+ CustomMuninNode,
+ CustomMuninNodeOptions,
+ CustomMuninNodeBuilder
+ >(
+ configure: options => {
+ options.HostName = HostName;
+ options.ExtraOption = ExtraOptionValue;
+ },
+ createBuilder: static (serviceBuilder, serviceKey) => new CustomMuninNodeBuilder(
+ serviceBuilder: serviceBuilder,
+ serviceKey: serviceKey,
+ nodeFactory: static (options, pluginProvider, listenerFactory, serviceProvider) => new CustomMuninNode(
+ options,
+ pluginProvider,
+ listenerFactory
+ )
+ )
+ )
+ );
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ var options = serviceProvider
+ .GetRequiredService>()
+ .Get(name: HostName);
+
+ Assert.That(options.HostName, Is.EqualTo(HostName));
+ Assert.That(options.ExtraOption, Is.EqualTo(ExtraOptionValue));
+
+ var muninNodeBackgroundService = serviceProvider.GetRequiredService();
+
+ Assert.That(muninNodeBackgroundService, Is.TypeOf());
+ Assert.That(
+ (muninNodeBackgroundService as CustomMuninNodeBackgroundService)!.ExtraOption,
+ Is.EqualTo(ExtraOptionValue)
+ );
+ }
}
diff --git a/tests/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninNodeBuilderExtensions.cs b/tests/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninNodeBuilderExtensions.cs
index 45c3bff..1c2e009 100644
--- a/tests/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninNodeBuilderExtensions.cs
+++ b/tests/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninNodeBuilderExtensions.cs
@@ -306,7 +306,7 @@ public void UseSessionCallback_INodeSessionCallback()
private static System.Collections.IEnumerable YieldTestCases_UseSessionCallback_FuncSessionCallback()
{
yield return new object[] {
- (IMuninNodeBuilder builder) => {
+ (MuninNodeBuilder builder) => {
builder.UseSessionCallback(
reportSessionStartedAsyncFunc: null,
reportSessionClosedAsyncFunc: null
@@ -326,7 +326,7 @@ private static System.Collections.IEnumerable YieldTestCases_UseSessionCallback_
};
yield return new object[] {
- (IMuninNodeBuilder builder) => {
+ (MuninNodeBuilder builder) => {
builder.UseSessionCallback(
reportSessionStartedAsyncFunc: (sessionId, ct) => throw new NotImplementedException($"sessionId={sessionId}"),
reportSessionClosedAsyncFunc: null
@@ -350,7 +350,7 @@ private static System.Collections.IEnumerable YieldTestCases_UseSessionCallback_
};
yield return new object[] {
- (IMuninNodeBuilder builder) => {
+ (MuninNodeBuilder builder) => {
builder.UseSessionCallback(
reportSessionStartedAsyncFunc: null,
reportSessionClosedAsyncFunc: (sessionId, ct) => throw new NotImplementedException($"sessionId={sessionId}")
@@ -376,14 +376,14 @@ private static System.Collections.IEnumerable YieldTestCases_UseSessionCallback_
[TestCaseSource(nameof(YieldTestCases_UseSessionCallback_FuncSessionCallback))]
public void UseSessionCallback_FuncSessionCallback(
- Action callUseSessionCallback,
+ Action callUseSessionCallback,
Action assertBuiltNode
)
{
var services = new ServiceCollection();
services.AddMunin(
- builder => callUseSessionCallback(builder.AddNode(option => { }))
+ builder => callUseSessionCallback((MuninNodeBuilder)builder.AddNode(option => { }))
);
var serviceProvider = services.BuildServiceProvider();
diff --git a/tests/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninServiceBuilderExtensions.cs b/tests/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninServiceBuilderExtensions.cs
index 8582ed3..ff83b2a 100644
--- a/tests/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninServiceBuilderExtensions.cs
+++ b/tests/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninServiceBuilderExtensions.cs
@@ -4,9 +4,13 @@
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
using NUnit.Framework;
+using Smdn.Net.MuninNode.Transport;
+using Smdn.Net.MuninPlugin;
+
namespace Smdn.Net.MuninNode.DependencyInjection;
[TestFixture]
@@ -161,4 +165,286 @@ public void AddNode_Multiple_GetKeyedService(string hostName)
Assert.That(node.HostName, Is.EqualTo(hostName));
Assert.That(node, Is.Not.SameAs(anotherNode));
}
+
+ private class CustomMuninNodeBuilder : MuninNodeBuilder
+ where TMuninNodeOptions : MuninNodeOptions
+ {
+ private readonly Func nodeFactory;
+
+ public CustomMuninNodeBuilder(
+ IMuninServiceBuilder serviceBuilder,
+ string serviceKey,
+ Func nodeFactory
+ )
+ : base(serviceBuilder, serviceKey)
+ {
+ this.nodeFactory = nodeFactory;
+ }
+
+ protected override IMuninNode Build(
+ IPluginProvider pluginProvider,
+ IMuninNodeListenerFactory? listenerFactory,
+ IServiceProvider serviceProvider
+ )
+ => nodeFactory(
+ GetConfiguredOptions(serviceProvider),
+ pluginProvider,
+ listenerFactory,
+ serviceProvider
+ );
+ }
+
+ private class CustomMuninNode : LocalNode {
+ public MuninNodeOptions Options { get; }
+ public override string HostName => Options.HostName;
+ public override IPluginProvider PluginProvider { get; }
+
+ public CustomMuninNode(
+ MuninNodeOptions options,
+ IPluginProvider pluginProvider,
+ IMuninNodeListenerFactory? listenerFactory
+ )
+ : base(
+ listenerFactory: listenerFactory,
+ accessRule: null,
+ logger: null
+ )
+ {
+ Options = options;
+ PluginProvider = pluginProvider;
+ }
+ }
+
+ [Test]
+ public void AddNode_CustomBuilderType()
+ {
+ const string HostName = "munin-node.localhost";
+ var services = new ServiceCollection();
+
+ services.AddMunin(configure: builder => {
+ builder.AddNode>(
+ configure: options => options.HostName = HostName,
+ createBuilder: static (serviceBuilder, serviceKey) => new CustomMuninNodeBuilder(
+ serviceBuilder: serviceBuilder,
+ serviceKey: serviceKey,
+ nodeFactory: static (options, pluginProvider, listenerFactory, serviceProvider) => new CustomMuninNode(
+ options,
+ pluginProvider,
+ listenerFactory
+ )
+ )
+ );
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ var node = serviceProvider.GetService();
+
+ Assert.That(node, Is.Not.Null);
+ Assert.That(node, Is.TypeOf());
+ Assert.That(node.HostName, Is.EqualTo(HostName));
+
+ var keyedNode = serviceProvider.GetKeyedService(HostName);
+
+ Assert.That(keyedNode, Is.Not.Null);
+ Assert.That(keyedNode, Is.TypeOf());
+ Assert.That(keyedNode.HostName, Is.EqualTo(HostName));
+ }
+
+ [Test]
+ public void AddNode_CustomBuilderType_AbstractServiceType()
+ {
+ const string HostName = "munin-node.localhost";
+ var services = new ServiceCollection();
+
+ services.AddMunin(configure: builder => {
+ builder.AddNode>(
+ configure: options => options.HostName = HostName,
+ createBuilder: static (serviceBuilder, serviceKey) => new CustomMuninNodeBuilder(
+ serviceBuilder: serviceBuilder,
+ serviceKey: serviceKey,
+ nodeFactory: static (options, pluginProvider, listenerFactory, serviceProvider) => new CustomMuninNode(
+ options,
+ pluginProvider,
+ listenerFactory
+ )
+ )
+ );
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ var node = serviceProvider.GetService();
+
+ Assert.That(node, Is.Not.Null);
+ Assert.That(node, Is.TypeOf());
+ Assert.That(node.HostName, Is.EqualTo(HostName));
+
+ var keyedNode = serviceProvider.GetKeyedService(HostName);
+
+ Assert.That(keyedNode, Is.Not.Null);
+ Assert.That(keyedNode, Is.TypeOf());
+ Assert.That(keyedNode.HostName, Is.EqualTo(HostName));
+ }
+
+ [Test]
+ public void AddNode_CustomBuilderType_ConcreteServiceType()
+ {
+ const string HostName = "munin-node.localhost";
+ var services = new ServiceCollection();
+
+ services.AddMunin(configure: builder => {
+ builder.AddNode>(
+ configure: options => options.HostName = HostName,
+ createBuilder: static (serviceBuilder, serviceKey) => new CustomMuninNodeBuilder(
+ serviceBuilder: serviceBuilder,
+ serviceKey: serviceKey,
+ nodeFactory: static (options, pluginProvider, listenerFactory, serviceProvider) => new CustomMuninNode(
+ options,
+ pluginProvider,
+ listenerFactory
+ )
+ )
+ );
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ var node = serviceProvider.GetService();
+
+ Assert.That(node, Is.Not.Null);
+ Assert.That(node.HostName, Is.EqualTo(HostName));
+
+ var keyedNode = serviceProvider.GetKeyedService(HostName);
+
+ Assert.That(keyedNode, Is.Not.Null);
+ Assert.That(keyedNode.HostName, Is.EqualTo(HostName));
+ }
+
+ private class ExtendedCustomMuninNode : CustomMuninNode {
+ public ExtendedCustomMuninNode(
+ MuninNodeOptions options,
+ IPluginProvider pluginProvider,
+ IMuninNodeListenerFactory? listenerFactory
+ )
+ : base(
+ options: options,
+ pluginProvider: pluginProvider,
+ listenerFactory: listenerFactory
+ )
+ {
+ }
+ }
+
+ [Test]
+ public void AddNode_CustomBuilderType_ConcreteServiceType_ImplementationTypeMismatch()
+ {
+ const string HostName = "munin-node.localhost";
+ var services = new ServiceCollection();
+
+ services.AddMunin(configure: builder => {
+ builder.AddNode>(
+ configure: options => options.HostName = HostName,
+ createBuilder: static (serviceBuilder, serviceKey) => new CustomMuninNodeBuilder(
+ serviceBuilder: serviceBuilder,
+ serviceKey: serviceKey,
+ nodeFactory: static (options, pluginProvider, listenerFactory, serviceProvider) => new CustomMuninNode(
+ options,
+ pluginProvider,
+ listenerFactory
+ )
+ )
+ );
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ Assert.That(
+ () => serviceProvider.GetService(),
+ Throws.InvalidOperationException
+ );
+ Assert.That(
+ () => serviceProvider.GetService(),
+ Is.Null
+ );
+
+ Assert.That(
+ () => serviceProvider.GetKeyedService(HostName),
+ Throws.InvalidOperationException
+ );
+ Assert.That(
+ () => serviceProvider.GetKeyedService(HostName),
+ Is.Null
+ );
+ }
+
+ private class CustomMuninNodeOptions : MuninNodeOptions {
+ public string? ExtraOption { get; set; }
+
+ protected override void Configure(MuninNodeOptions baseOptions)
+ {
+ base.Configure(baseOptions ?? throw new ArgumentNullException(nameof(baseOptions)));
+
+ if (baseOptions is CustomMuninNodeOptions options)
+ ExtraOption = options.ExtraOption;
+ }
+ }
+
+ [Test]
+ public void AddNode_CustomOptionsType()
+ {
+ const string HostName = "munin-node.localhost";
+ const string ExtraOptionValue = "foo";
+
+ var services = new ServiceCollection();
+
+ services.AddMunin(configure: builder => {
+ builder.AddNode>(
+ configure: options => {
+ options.HostName = HostName;
+ options.ExtraOption = ExtraOptionValue;
+ },
+ createBuilder: static (serviceBuilder, serviceKey) => new CustomMuninNodeBuilder(
+ serviceBuilder: serviceBuilder,
+ serviceKey: serviceKey,
+ nodeFactory: static (options, pluginProvider, listenerFactory, serviceProvider) => new CustomMuninNode(
+ options,
+ pluginProvider,
+ listenerFactory
+ )
+ )
+ );
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ var options = serviceProvider
+ .GetRequiredService>()
+ .Get(name: HostName);
+
+ Assert.That(options.HostName, Is.EqualTo(HostName));
+ Assert.That(options.ExtraOption, Is.EqualTo(ExtraOptionValue));
+
+ var node = serviceProvider.GetService();
+
+ Assert.That(node, Is.Not.Null);
+ Assert.That(node, Is.TypeOf());
+
+ var customNode = (CustomMuninNode)node;
+
+ Assert.That(customNode.Options, Is.TypeOf());
+ Assert.That((customNode.Options as CustomMuninNodeOptions)!.ExtraOption, Is.EqualTo(ExtraOptionValue));
+ Assert.That(customNode.HostName, Is.EqualTo(HostName));
+
+ var keyedNode = serviceProvider.GetKeyedService(HostName);
+
+ Assert.That(keyedNode, Is.Not.Null);
+ Assert.That(keyedNode, Is.TypeOf());
+
+ var customKeyedNode = (CustomMuninNode)keyedNode;
+
+ Assert.That(customKeyedNode.Options, Is.TypeOf());
+ Assert.That((customKeyedNode.Options as CustomMuninNodeOptions)!.ExtraOption, Is.EqualTo(ExtraOptionValue));
+ Assert.That(customKeyedNode.HostName, Is.EqualTo(HostName));
+ }
}