Skip to content

Commit a7a8c39

Browse files
committed
add support for specifying the service type and implementation type of registering nodes
1 parent 0e785ed commit a7a8c39

File tree

3 files changed

+290
-6
lines changed

3 files changed

+290
-6
lines changed

src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninNodeBuilderExtensions.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,4 +239,22 @@ Func<IServiceProvider, IMuninNodeListenerFactory> buildListenerFactory
239239

240240
return builder;
241241
}
242+
243+
internal static TMuninNode Build<TMuninNode>(
244+
this IMuninNodeBuilder builder,
245+
IServiceProvider serviceProvider
246+
) where TMuninNode : IMuninNode
247+
{
248+
if (builder is null)
249+
throw new ArgumentNullException(nameof(builder));
250+
if (serviceProvider is null)
251+
throw new ArgumentNullException(nameof(serviceProvider));
252+
253+
var n = builder.Build(serviceProvider);
254+
255+
if (n is not TMuninNode node)
256+
throw new InvalidOperationException($"The type '{n.GetType()}' of the constructed instance did not match the requested type '{typeof(TMuninNode)}'.");
257+
258+
return node;
259+
}
242260
}

src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninServiceBuilderExtensions.cs

Lines changed: 123 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public static IMuninNodeBuilder AddNode(
3939
Action<MuninNodeOptions> configure
4040
)
4141
=> AddNode<
42+
IMuninNode,
43+
IMuninNode,
4244
MuninNodeOptions,
4345
MuninNodeBuilder
4446
>(
@@ -67,21 +69,132 @@ Action<MuninNodeOptions> configure
6769
/// An <see cref="Func{TMuninNodeBuilder}"/> to create <typeparamref name="TMuninNodeBuilder"/> to build
6870
/// the <c>Munin-Node</c>.
6971
/// </param>
72+
/// <returns>The current <typeparamref name="TMuninNodeBuilder"/> so that additional calls can be chained.</returns>
73+
/// <exception cref="ArgumentNullException">
74+
/// <paramref name="builder"/> is <see langword="null"/>, or
75+
/// <paramref name="configure"/> is <see langword="null"/>, or
76+
/// <paramref name="createBuilder"/> is <see langword="null"/>.
77+
/// </exception>
78+
public static
79+
TMuninNodeBuilder AddNode<
80+
TMuninNodeOptions,
81+
TMuninNodeBuilder
82+
>(
83+
this IMuninServiceBuilder builder,
84+
Action<TMuninNodeOptions> configure,
85+
Func<IMuninServiceBuilder, string, TMuninNodeBuilder> createBuilder
86+
)
87+
where TMuninNodeOptions : MuninNodeOptions, new()
88+
where TMuninNodeBuilder : MuninNodeBuilder
89+
=> AddNode<
90+
IMuninNode,
91+
IMuninNode,
92+
TMuninNodeOptions,
93+
TMuninNodeBuilder
94+
>(
95+
builder: builder ?? throw new ArgumentNullException(nameof(builder)),
96+
configure: configure ?? throw new ArgumentNullException(nameof(configure)),
97+
createBuilder: createBuilder ?? throw new ArgumentNullException(nameof(createBuilder))
98+
);
99+
100+
/// <summary>
101+
/// Adds a <typeparamref name="TMuninNode"/> to the <see cref="IMuninServiceBuilder"/> with specified configurations.
102+
/// </summary>
103+
/// <typeparam name="TMuninNode">
104+
/// The type of <see cref="IMuninNode"/> service to add to the <seealso cref="IServiceCollection"/>.
105+
/// </typeparam>
106+
/// <typeparam name="TMuninNodeOptions">
107+
/// The extended type of <see cref="MuninNodeOptions"/> to configure the <typeparamref name="TMuninNode"/>.
108+
/// </typeparam>
109+
/// <typeparam name="TMuninNodeBuilder">
110+
/// The extended type of <see cref="MuninNodeBuilder"/> to build the <typeparamref name="TMuninNode"/>.
111+
/// </typeparam>
112+
/// <param name="builder">
113+
/// An <see cref="IMuninServiceBuilder"/> that the built <typeparamref name="TMuninNode"/> will be added to.
114+
/// </param>
115+
/// <param name="configure">
116+
/// An <see cref="Action{TMuninNodeOptions}"/> to setup <typeparamref name="TMuninNodeOptions"/> to
117+
/// configure the <typeparamref name="TMuninNode"/> to be built.
118+
/// </param>
119+
/// <param name="createBuilder">
120+
/// An <see cref="Func{TMuninNodeBuilder}"/> to create <typeparamref name="TMuninNodeBuilder"/> to build
121+
/// the <typeparamref name="TMuninNode"/>.
122+
/// </param>
123+
/// <returns>The current <typeparamref name="TMuninNodeBuilder"/> so that additional calls can be chained.</returns>
124+
/// <exception cref="ArgumentNullException">
125+
/// <paramref name="builder"/> is <see langword="null"/>, or
126+
/// <paramref name="configure"/> is <see langword="null"/>, or
127+
/// <paramref name="createBuilder"/> is <see langword="null"/>.
128+
/// </exception>
129+
public static
130+
TMuninNodeBuilder AddNode<
131+
TMuninNode,
132+
TMuninNodeOptions,
133+
TMuninNodeBuilder
134+
>(
135+
this IMuninServiceBuilder builder,
136+
Action<TMuninNodeOptions> configure,
137+
Func<IMuninServiceBuilder, string, TMuninNodeBuilder> createBuilder
138+
)
139+
where TMuninNode : class, IMuninNode
140+
where TMuninNodeOptions : MuninNodeOptions, new()
141+
where TMuninNodeBuilder : MuninNodeBuilder
142+
=> AddNode<
143+
TMuninNode,
144+
TMuninNode,
145+
TMuninNodeOptions,
146+
TMuninNodeBuilder
147+
>(
148+
builder: builder ?? throw new ArgumentNullException(nameof(builder)),
149+
configure: configure ?? throw new ArgumentNullException(nameof(configure)),
150+
createBuilder: createBuilder ?? throw new ArgumentNullException(nameof(createBuilder))
151+
);
152+
153+
/// <summary>
154+
/// Adds a <typeparamref name="TMuninNodeImplementation"/> to the <see cref="IMuninServiceBuilder"/> with specified configurations.
155+
/// </summary>
156+
/// <typeparam name="TMuninNodeService">
157+
/// The type of <see cref="IMuninNode"/> service to add to the <seealso cref="IServiceCollection"/>.
158+
/// </typeparam>
159+
/// <typeparam name="TMuninNodeImplementation">
160+
/// The type of <typeparamref name="TMuninNodeService"/> implementation.
161+
/// </typeparam>
162+
/// <typeparam name="TMuninNodeOptions">
163+
/// The extended type of <see cref="MuninNodeOptions"/> to configure the <typeparamref name="TMuninNodeImplementation"/>.
164+
/// </typeparam>
165+
/// <typeparam name="TMuninNodeBuilder">
166+
/// The extended type of <see cref="MuninNodeBuilder"/> to build the <typeparamref name="TMuninNodeImplementation"/>.
167+
/// </typeparam>
168+
/// <param name="builder">
169+
/// An <see cref="IMuninServiceBuilder"/> that the built <typeparamref name="TMuninNodeImplementation"/> will be added to.
170+
/// </param>
171+
/// <param name="configure">
172+
/// An <see cref="Action{TMuninNodeOptions}"/> to setup <typeparamref name="TMuninNodeOptions"/> to
173+
/// configure the <typeparamref name="TMuninNodeImplementation"/> to be built.
174+
/// </param>
175+
/// <param name="createBuilder">
176+
/// An <see cref="Func{TMuninNodeBuilder}"/> to create <typeparamref name="TMuninNodeBuilder"/> to build
177+
/// the <typeparamref name="TMuninNodeImplementation"/>.
178+
/// </param>
70179
/// <returns>The current <see cref="IMuninNodeBuilder"/> so that additional calls can be chained.</returns>
71180
/// <exception cref="ArgumentNullException">
72181
/// <paramref name="builder"/> is <see langword="null"/>, or
73182
/// <paramref name="configure"/> is <see langword="null"/>, or
74183
/// <paramref name="createBuilder"/> is <see langword="null"/>.
75184
/// </exception>
76185
public static
77-
IMuninNodeBuilder AddNode<
186+
TMuninNodeBuilder AddNode<
187+
TMuninNodeService,
188+
TMuninNodeImplementation,
78189
TMuninNodeOptions,
79190
TMuninNodeBuilder
80191
>(
81192
this IMuninServiceBuilder builder,
82193
Action<TMuninNodeOptions> configure,
83194
Func<IMuninServiceBuilder, string, TMuninNodeBuilder> createBuilder
84195
)
196+
where TMuninNodeService : class, IMuninNode
197+
where TMuninNodeImplementation : class, TMuninNodeService
85198
where TMuninNodeOptions : MuninNodeOptions, new()
86199
where TMuninNodeBuilder : MuninNodeBuilder
87200
{
@@ -107,27 +220,31 @@ Func<IMuninServiceBuilder, string, TMuninNodeBuilder> createBuilder
107220
);
108221

109222
builder.Services.Add(
110-
ServiceDescriptor.KeyedSingleton<IMuninNodeBuilder>(
223+
ServiceDescriptor.KeyedSingleton<TMuninNodeBuilder>(
111224
serviceKey: nodeBuilder.ServiceKey,
112225
implementationFactory: (_, _) => nodeBuilder
113226
)
114227
);
115228

116229
// add keyed/singleton IMuninNode
117230
builder.Services.Add(
118-
ServiceDescriptor.KeyedSingleton<IMuninNode>(
231+
ServiceDescriptor.KeyedSingleton<TMuninNodeService, TMuninNodeImplementation>(
119232
serviceKey: nodeBuilder.ServiceKey,
120233
static (serviceProvider, serviceKey)
121-
=> serviceProvider.GetRequiredKeyedService<IMuninNodeBuilder>(serviceKey).Build(serviceProvider)
234+
=> serviceProvider
235+
.GetRequiredKeyedService<TMuninNodeBuilder>(serviceKey)
236+
.Build<TMuninNodeImplementation>(serviceProvider)
122237
)
123238
);
124239

125240
// add keyless/multiple IMuninNode
241+
#pragma warning disable IDE0200
126242
builder.Services.Add(
127-
ServiceDescriptor.Transient<IMuninNode>(
128-
nodeBuilder.Build
243+
ServiceDescriptor.Transient<TMuninNodeService, TMuninNodeImplementation>(
244+
serviceProvider => nodeBuilder.Build<TMuninNodeImplementation>(serviceProvider)
129245
)
130246
);
247+
#pragma warning restore IDE0200
131248

132249
return nodeBuilder;
133250
}

tests/Smdn.Net.MuninNode/Smdn.Net.MuninNode.DependencyInjection/IMuninServiceBuilderExtensions.cs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,133 @@ public void AddNode_CustomBuilderType()
251251
Assert.That(keyedNode.HostName, Is.EqualTo(HostName));
252252
}
253253

254+
[Test]
255+
public void AddNode_CustomBuilderType_AbstractServiceType()
256+
{
257+
const string HostName = "munin-node.localhost";
258+
var services = new ServiceCollection();
259+
260+
services.AddMunin(configure: builder => {
261+
builder.AddNode<IMuninNode, CustomMuninNode, MuninNodeOptions, CustomMuninNodeBuilder<MuninNodeOptions>>(
262+
configure: options => options.HostName = HostName,
263+
createBuilder: static (serviceBuilder, serviceKey) => new CustomMuninNodeBuilder<MuninNodeOptions>(
264+
serviceBuilder: serviceBuilder,
265+
serviceKey: serviceKey,
266+
nodeFactory: static (options, pluginProvider, listenerFactory, serviceProvider) => new CustomMuninNode(
267+
options,
268+
pluginProvider,
269+
listenerFactory
270+
)
271+
)
272+
);
273+
});
274+
275+
var serviceProvider = services.BuildServiceProvider();
276+
277+
var node = serviceProvider.GetService<IMuninNode>();
278+
279+
Assert.That(node, Is.Not.Null);
280+
Assert.That(node, Is.TypeOf<CustomMuninNode>());
281+
Assert.That(node.HostName, Is.EqualTo(HostName));
282+
283+
var keyedNode = serviceProvider.GetKeyedService<IMuninNode>(HostName);
284+
285+
Assert.That(keyedNode, Is.Not.Null);
286+
Assert.That(keyedNode, Is.TypeOf<CustomMuninNode>());
287+
Assert.That(keyedNode.HostName, Is.EqualTo(HostName));
288+
}
289+
290+
[Test]
291+
public void AddNode_CustomBuilderType_ConcreteServiceType()
292+
{
293+
const string HostName = "munin-node.localhost";
294+
var services = new ServiceCollection();
295+
296+
services.AddMunin(configure: builder => {
297+
builder.AddNode<CustomMuninNode, MuninNodeOptions, CustomMuninNodeBuilder<MuninNodeOptions>>(
298+
configure: options => options.HostName = HostName,
299+
createBuilder: static (serviceBuilder, serviceKey) => new CustomMuninNodeBuilder<MuninNodeOptions>(
300+
serviceBuilder: serviceBuilder,
301+
serviceKey: serviceKey,
302+
nodeFactory: static (options, pluginProvider, listenerFactory, serviceProvider) => new CustomMuninNode(
303+
options,
304+
pluginProvider,
305+
listenerFactory
306+
)
307+
)
308+
);
309+
});
310+
311+
var serviceProvider = services.BuildServiceProvider();
312+
313+
var node = serviceProvider.GetService<CustomMuninNode>();
314+
315+
Assert.That(node, Is.Not.Null);
316+
Assert.That(node.HostName, Is.EqualTo(HostName));
317+
318+
var keyedNode = serviceProvider.GetKeyedService<CustomMuninNode>(HostName);
319+
320+
Assert.That(keyedNode, Is.Not.Null);
321+
Assert.That(keyedNode.HostName, Is.EqualTo(HostName));
322+
}
323+
324+
private class ExtendedCustomMuninNode : CustomMuninNode {
325+
public ExtendedCustomMuninNode(
326+
MuninNodeOptions options,
327+
IPluginProvider pluginProvider,
328+
IMuninNodeListenerFactory? listenerFactory
329+
)
330+
: base(
331+
options: options,
332+
pluginProvider: pluginProvider,
333+
listenerFactory: listenerFactory
334+
)
335+
{
336+
}
337+
}
338+
339+
[Test]
340+
public void AddNode_CustomBuilderType_ConcreteServiceType_ImplementationTypeMismatch()
341+
{
342+
const string HostName = "munin-node.localhost";
343+
var services = new ServiceCollection();
344+
345+
services.AddMunin(configure: builder => {
346+
builder.AddNode<ExtendedCustomMuninNode, MuninNodeOptions, CustomMuninNodeBuilder<MuninNodeOptions>>(
347+
configure: options => options.HostName = HostName,
348+
createBuilder: static (serviceBuilder, serviceKey) => new CustomMuninNodeBuilder<MuninNodeOptions>(
349+
serviceBuilder: serviceBuilder,
350+
serviceKey: serviceKey,
351+
nodeFactory: static (options, pluginProvider, listenerFactory, serviceProvider) => new CustomMuninNode(
352+
options,
353+
pluginProvider,
354+
listenerFactory
355+
)
356+
)
357+
);
358+
});
359+
360+
var serviceProvider = services.BuildServiceProvider();
361+
362+
Assert.That(
363+
() => serviceProvider.GetService<ExtendedCustomMuninNode>(),
364+
Throws.InvalidOperationException
365+
);
366+
Assert.That(
367+
() => serviceProvider.GetService<CustomMuninNode>(),
368+
Is.Null
369+
);
370+
371+
Assert.That(
372+
() => serviceProvider.GetKeyedService<ExtendedCustomMuninNode>(HostName),
373+
Throws.InvalidOperationException
374+
);
375+
Assert.That(
376+
() => serviceProvider.GetKeyedService<CustomMuninNode>(HostName),
377+
Is.Null
378+
);
379+
}
380+
254381
private class CustomMuninNodeOptions : MuninNodeOptions {
255382
public string? ExtraOption { get; set; }
256383

@@ -297,5 +424,27 @@ public void AddNode_CustomOptionsType()
297424

298425
Assert.That(options.HostName, Is.EqualTo(HostName));
299426
Assert.That(options.ExtraOption, Is.EqualTo(ExtraOptionValue));
427+
428+
var node = serviceProvider.GetService<IMuninNode>();
429+
430+
Assert.That(node, Is.Not.Null);
431+
Assert.That(node, Is.TypeOf<CustomMuninNode>());
432+
433+
var customNode = (CustomMuninNode)node;
434+
435+
Assert.That(customNode.Options, Is.TypeOf<CustomMuninNodeOptions>());
436+
Assert.That((customNode.Options as CustomMuninNodeOptions)!.ExtraOption, Is.EqualTo(ExtraOptionValue));
437+
Assert.That(customNode.HostName, Is.EqualTo(HostName));
438+
439+
var keyedNode = serviceProvider.GetKeyedService<IMuninNode>(HostName);
440+
441+
Assert.That(keyedNode, Is.Not.Null);
442+
Assert.That(keyedNode, Is.TypeOf<CustomMuninNode>());
443+
444+
var customKeyedNode = (CustomMuninNode)keyedNode;
445+
446+
Assert.That(customKeyedNode.Options, Is.TypeOf<CustomMuninNodeOptions>());
447+
Assert.That((customKeyedNode.Options as CustomMuninNodeOptions)!.ExtraOption, Is.EqualTo(ExtraOptionValue));
448+
Assert.That(customKeyedNode.HostName, Is.EqualTo(HostName));
300449
}
301450
}

0 commit comments

Comments
 (0)