Skip to content

Commit f19a1fe

Browse files
Adds registration helper for Custom Delegates as factories (#16)
1 parent cbf0175 commit f19a1fe

File tree

11 files changed

+189
-20
lines changed

11 files changed

+189
-20
lines changed

src/Microsoft.Health.Extensions.DependencyInjection.UnitTests/ServiceControllerExtensionsTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using System;
77
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Health.Extensions.DependencyInjection.UnitTests.TestObjects;
89
using NSubstitute;
910
using Xunit;
1011

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// -------------------------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
4+
// -------------------------------------------------------------------------------------------------
5+
6+
namespace Microsoft.Health.Extensions.DependencyInjection.UnitTests.TestObjects
7+
{
8+
public class ComponentA : IComponent
9+
{
10+
public string Name { get; } = nameof(ComponentA);
11+
}
12+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// -------------------------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
4+
// -------------------------------------------------------------------------------------------------
5+
6+
namespace Microsoft.Health.Extensions.DependencyInjection.UnitTests.TestObjects
7+
{
8+
public class ComponentB : IComponent
9+
{
10+
public delegate IComponent Factory();
11+
12+
public string Name { get; } = nameof(ComponentB);
13+
}
14+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// -------------------------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
4+
// -------------------------------------------------------------------------------------------------
5+
6+
namespace Microsoft.Health.Extensions.DependencyInjection.UnitTests.TestObjects
7+
{
8+
public interface IComponent
9+
{
10+
public string Name { get; }
11+
}
12+
}

src/Microsoft.Health.Extensions.DependencyInjection.UnitTests/TestComponent.cs renamed to src/Microsoft.Health.Extensions.DependencyInjection.UnitTests/TestObjects/TestComponent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
44
// -------------------------------------------------------------------------------------------------
55

6-
namespace Microsoft.Health.Extensions.DependencyInjection.UnitTests
6+
namespace Microsoft.Health.Extensions.DependencyInjection.UnitTests.TestObjects
77
{
88
public class TestComponent
99
{
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// -------------------------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
4+
// -------------------------------------------------------------------------------------------------
5+
6+
using System;
7+
8+
namespace Microsoft.Health.Extensions.DependencyInjection.UnitTests.TestObjects
9+
{
10+
public class TestDisposableObjectWithInterface : IEquatable<string>, IDisposable
11+
{
12+
public bool Equals(string other)
13+
{
14+
throw new NotImplementedException();
15+
}
16+
17+
public void Dispose()
18+
{
19+
throw new NotImplementedException();
20+
}
21+
}
22+
}

src/Microsoft.Health.Extensions.DependencyInjection.UnitTests/TestModule.cs renamed to src/Microsoft.Health.Extensions.DependencyInjection.UnitTests/TestObjects/TestModule.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
using Microsoft.Extensions.DependencyInjection;
77

8-
namespace Microsoft.Health.Extensions.DependencyInjection.UnitTests
8+
namespace Microsoft.Health.Extensions.DependencyInjection.UnitTests.TestObjects
99
{
1010
public class TestModule : IStartupModule
1111
{
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// -------------------------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
4+
// -------------------------------------------------------------------------------------------------
5+
6+
using System.Collections.Generic;
7+
8+
namespace Microsoft.Health.Extensions.DependencyInjection.UnitTests.TestObjects
9+
{
10+
public class TestScope : IScoped<IList<string>>
11+
{
12+
public TestScope(IList<string> value)
13+
{
14+
Value = value;
15+
}
16+
17+
public IList<string> Value { get; }
18+
19+
public void Dispose()
20+
{
21+
}
22+
}
23+
}

src/Microsoft.Health.Extensions.DependencyInjection.UnitTests/TypeRegistrationTests.cs

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.IO;
99
using System.Linq;
1010
using Microsoft.Extensions.DependencyInjection;
11+
using Microsoft.Health.Extensions.DependencyInjection.UnitTests.TestObjects;
1112
using Xunit;
1213

1314
namespace Microsoft.Health.Extensions.DependencyInjection.UnitTests
@@ -332,31 +333,56 @@ public void GivenFactory_WhenRegisteringAsSelfAsAService_ThenTheTypeAppearsAsMet
332333
Assert.All(_collection, sd => Assert.Equal(typeof(List<string>), (sd as ServiceDescriptorWithMetadata)?.Metadata));
333334
}
334335

335-
private class TestScope : IScoped<IList<string>>
336+
[Fact]
337+
public void GivenADelegate_WhenResolvingComponent_ThenResolverReturnsRegisteredService()
336338
{
337-
public TestScope(IList<string> value)
338-
{
339-
Value = value;
340-
}
339+
_collection.Add<ComponentA>()
340+
.Transient()
341+
.AsSelf()
342+
.AsService<IComponent>();
343+
344+
_collection.Add<ComponentB>()
345+
.Transient()
346+
.AsSelf();
341347

342-
public IList<string> Value { get; }
348+
_collection.AddDelegate<ComponentB.Factory, ComponentB>();
343349

344-
public void Dispose()
345-
{
346-
}
350+
var provider = _collection.BuildServiceProvider();
351+
352+
var componentFactory = provider.GetService<ComponentB.Factory>();
353+
IComponent instance = componentFactory.Invoke();
354+
355+
Assert.IsType<ComponentB>(instance);
347356
}
348357

349-
private class TestDisposableObjectWithInterface : IEquatable<string>, IDisposable
358+
[Fact]
359+
public void GivenADelegateFromTypeBuilder_WhenResolvingComponent_ThenResolverReturnsRegisteredService()
350360
{
351-
public bool Equals(string other)
352-
{
353-
throw new NotImplementedException();
354-
}
361+
_collection.Add<ComponentA>()
362+
.Transient()
363+
.AsSelf()
364+
.AsService<IComponent>();
355365

356-
public void Dispose()
357-
{
358-
throw new NotImplementedException();
359-
}
366+
_collection.Add<ComponentB>()
367+
.Transient()
368+
.AsSelf()
369+
.AsService<IComponent>()
370+
.AsDelegate<ComponentB.Factory>();
371+
372+
var provider = _collection.BuildServiceProvider();
373+
374+
// Using Func<IComponent> won't work here because there are 2 components that implement this interface.
375+
// Using the delegate ComponentB.Factory works to resolve the desired instance while maintaining the interface
376+
var componentFactory = provider.GetService<ComponentB.Factory>();
377+
IComponent instance = componentFactory.Invoke();
378+
379+
Assert.IsType<ComponentB>(instance);
380+
}
381+
382+
[Fact]
383+
public void GivenADelegateWithIncompatibleType_WhenResolvingComponent_ThenExceptionIsThrown()
384+
{
385+
Assert.Throws<InvalidOperationException>(() => _collection.AddDelegate<ComponentB.Factory, int>());
360386
}
361387
}
362388
}

src/Microsoft.Health.Extensions.DependencyInjection/TypeRegistrationBuilder.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ namespace Microsoft.Health.Extensions.DependencyInjection
1616
public class TypeRegistrationBuilder
1717
{
1818
private readonly MethodInfo _factoryGenericMethod = typeof(TypeRegistrationExtensions).GetMethod(nameof(TypeRegistrationExtensions.AddFactory), BindingFlags.Public | BindingFlags.Static);
19+
private readonly MethodInfo _factoryDelegateMethod = typeof(TypeRegistrationExtensions).GetMethod(nameof(TypeRegistrationExtensions.AddDelegate), BindingFlags.Public | BindingFlags.Static);
1920
private readonly IServiceCollection _serviceCollection;
2021
private readonly Type _type;
2122
private readonly Func<IServiceProvider, object> _delegateRegistration;
@@ -86,6 +87,21 @@ public TypeRegistrationBuilder AsFactory()
8687
return this;
8788
}
8889

90+
/// <summary>
91+
/// Creates a service registration for the specified interface that can be resolved with a custom delegate
92+
/// </summary>
93+
/// <typeparam name="TDelegate">Custom delegate that will resolve the service</typeparam>
94+
/// <returns>The registration builder</returns>
95+
public TypeRegistrationBuilder AsDelegate<TDelegate>()
96+
where TDelegate : Delegate
97+
{
98+
var factoryMethod = _factoryDelegateMethod.MakeGenericMethod(typeof(TDelegate), _type);
99+
100+
factoryMethod.Invoke(null, new object[] { _serviceCollection });
101+
102+
return this;
103+
}
104+
89105
/// <summary>
90106
/// Replaces a service registration for the specified interface
91107
/// </summary>

0 commit comments

Comments
 (0)