Skip to content

Commit b9c90e3

Browse files
authored
Merge pull request #4 from AppCoreNet/facility-extensions-variance
Add support for type variance for IFacilityExtension.
2 parents 9559a29 + 4a520bd commit b9c90e3

File tree

21 files changed

+241
-32
lines changed

21 files changed

+241
-32
lines changed

DependencyInjection/src/AppCore.Extensions.DependencyInjection.Abstractions/AppCore.Extensions.DependencyInjection.Abstractions.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
<PackageReference Include="AppCore.Diagnostics.Sources" Version="$(AppCore_Shared_Version)">
1111
<PrivateAssets>all</PrivateAssets>
1212
</PackageReference>
13+
<PackageReference Include="AppCore.TypeHelpers.Sources" Version="$(AppCore_Shared_Version)">
14+
<PrivateAssets>all</PrivateAssets>
15+
</PackageReference>
1316
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(Microsoft_Extensions_Version)" />
1417
</ItemGroup>
1518

DependencyInjection/src/AppCore.Extensions.DependencyInjection.Abstractions/Facilities/FacilityBuilder.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Copyright (c) 2018-2022 the AppCore .NET project.
33

44
using System;
5+
using System.Linq;
56
using AppCore.Diagnostics;
67
using AppCore.Extensions.DependencyInjection.Activator;
78
using Microsoft.Extensions.DependencyInjection;
@@ -35,6 +36,18 @@ internal FacilityBuilder(IServiceCollection services, IActivator activator, Type
3536
FacilityType = facilityType;
3637
}
3738

39+
private IFacilityExtension<IFacility> CreateExtension(Type extensionType)
40+
{
41+
Type contractType = extensionType.GetInterfaces()
42+
.First(i => i.GetGenericTypeDefinition() == typeof(IFacilityExtension<>))
43+
.GenericTypeArguments[0];
44+
45+
Type extensionWrapperType = typeof(FacilityExtensionWrapper<>).MakeGenericType(contractType);
46+
object extension = Activator.CreateInstance(extensionType);
47+
48+
return (IFacilityExtension<IFacility>)System.Activator.CreateInstance(extensionWrapperType, extension);
49+
}
50+
3851
/// <summary>
3952
/// Adds an extension to the facility.
4053
/// </summary>
@@ -49,7 +62,7 @@ public FacilityBuilder AddExtension(Type extensionType)
4962
Ensure.Arg.NotNull(extensionType);
5063
Ensure.Arg.OfType(extensionType, typeof(IFacilityExtension<>).MakeGenericType(FacilityType));
5164

52-
var extension = (IFacilityExtension<IFacility>)Activator.CreateInstance(extensionType);
65+
IFacilityExtension<IFacility> extension = CreateExtension(extensionType);
5366
extension.ConfigureServices(Services);
5467
return this;
5568
}
@@ -107,9 +120,9 @@ internal FacilityBuilder(IServiceCollection services, IActivator activator)
107120
/// <typeparam name="TExtension">The type of the extension.</typeparam>
108121
/// <returns>The <see cref="FacilityBuilder{T}"/> to allow chaining.</returns>
109122
public FacilityBuilder<T> AddExtension<TExtension>()
110-
where TExtension : IFacilityExtension<T>
123+
where TExtension : class, IFacilityExtension<T>
111124
{
112-
var extension = (IFacilityExtension<IFacility>)Activator.CreateInstance(typeof(TExtension));
125+
IFacilityExtension<T> extension = Activator.CreateInstance<TExtension>();
113126
extension.ConfigureServices(Services);
114127
return this;
115128
}

DependencyInjection/src/AppCore.Extensions.DependencyInjection.Abstractions/Facilities/FacilityExtensionReflectionBuilder.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@ public IFacilityExtensionReflectionBuilder AddResolver<T>(Action<T>? configure =
3636

3737
public IReadOnlyCollection<IFacilityExtension<IFacility>> Resolve(Type facilityType)
3838
{
39-
return _resolvers.SelectMany(s => s.Resolve(facilityType))
39+
Type[] facilityTypes = facilityType.GetTypesAssignableFrom()
40+
.Where(t => t.GetInterfaces().Contains(typeof(IFacility)))
41+
.ToArray();
42+
43+
return _resolvers.SelectMany(s => facilityTypes.SelectMany(s.Resolve))
4044
.ToList()
4145
.AsReadOnly();
4246
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Licensed under the MIT License.
2+
// Copyright (c) 2018-2022 the AppCore .NET project.
3+
4+
using Microsoft.Extensions.DependencyInjection;
5+
6+
namespace AppCore.Extensions.DependencyInjection.Facilities;
7+
8+
internal sealed class FacilityExtensionWrapper<TContract> : IFacilityExtension<IFacility>
9+
where TContract : IFacility
10+
{
11+
private readonly IFacilityExtension<TContract> _extension;
12+
13+
public FacilityExtensionWrapper(IFacilityExtension<TContract> extension)
14+
{
15+
_extension = extension;
16+
}
17+
18+
public void ConfigureServices(IServiceCollection services)
19+
{
20+
_extension.ConfigureServices(services);
21+
}
22+
}

DependencyInjection/src/AppCore.Extensions.DependencyInjection.Abstractions/Facilities/IFacilityExtension.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace AppCore.Extensions.DependencyInjection.Facilities;
88
/// <summary>
99
/// Represents an extension for a facility.
1010
/// </summary>
11-
public interface IFacilityExtension<out T>
11+
public interface IFacilityExtension<in T>
1212
where T : IFacility
1313
{
1414
/// <summary>

DependencyInjection/src/AppCore.Extensions.DependencyInjection.AssemblyExtensions/Facilities/AssemblyResolver.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,18 @@ IEnumerable<IFacility> IFacilityResolver.Resolve()
114114
.Select(facilityType => (IFacility) _activator.CreateInstance(facilityType));
115115
}
116116

117+
private IFacilityExtension<IFacility> CreateExtension(Type extensionType)
118+
{
119+
Type contractType = extensionType.GetInterfaces()
120+
.First(i => i.GetGenericTypeDefinition() == typeof(IFacilityExtension<>))
121+
.GenericTypeArguments[0];
122+
123+
Type extensionWrapperType = typeof(FacilityExtensionWrapper<>).MakeGenericType(contractType);
124+
object extension = _activator.CreateInstance(extensionType);
125+
126+
return (IFacilityExtension<IFacility>)System.Activator.CreateInstance(extensionWrapperType, extension);
127+
}
128+
117129
/// <inheritdoc />
118130
IEnumerable<IFacilityExtension<IFacility>> IFacilityExtensionResolver.Resolve(Type facilityType)
119131
{
@@ -129,8 +141,6 @@ IEnumerable<IFacilityExtension<IFacility>> IFacilityExtensionResolver.Resolve(Ty
129141
scanner.Filters.Add(filter);
130142

131143
return scanner.ScanAssemblies()
132-
.Select(
133-
facilityExtensionType =>
134-
(IFacilityExtension<IFacility>)_activator.CreateInstance(facilityExtensionType));
144+
.Select(CreateExtension);
135145
}
136146
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Licensed under the MIT License.
2+
// Copyright (c) 2018-2022 the AppCore .NET project.
3+
4+
using Microsoft.Extensions.DependencyInjection;
5+
6+
namespace AppCore.Extensions.DependencyInjection.Facilities;
7+
8+
internal sealed class FacilityExtensionWrapper<TContract> : IFacilityExtension<IFacility>
9+
where TContract : IFacility
10+
{
11+
internal IFacilityExtension<TContract> Extension { get; }
12+
13+
public FacilityExtensionWrapper(IFacilityExtension<TContract> extension)
14+
{
15+
Extension = extension;
16+
}
17+
18+
public void ConfigureServices(IServiceCollection services)
19+
{
20+
Extension.ConfigureServices(services);
21+
}
22+
}

DependencyInjection/test/AppCore.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyResolverTests.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,12 @@ public void ResolvesAllFacilityExtensions()
4949
IEnumerable<IFacilityExtension<IFacility>> facilityExtensions =
5050
((IFacilityExtensionResolver)resolver).Resolve(typeof(Facility1));
5151

52-
facilityExtensions.Select(f => f.GetType())
52+
facilityExtensions.Should()
53+
.AllBeOfType<FacilityExtensionWrapper<Facility1>>();
54+
55+
facilityExtensions.OfType<FacilityExtensionWrapper<Facility1>>()
56+
.Select(e => e.Extension.GetType())
5357
.Should()
54-
.BeEquivalentTo(new [] { typeof(Facility1Extension1), typeof(Facility1Extension2) });
58+
.BeEquivalentTo(new[] { typeof(Facility1Extension1), typeof(Facility1Extension2) });
5559
}
5660
}

DependencyInjection/test/AppCore.Extensions.DependencyInjection.Tests/Facilities/FacilityBuilderTests.cs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Copyright (c) 2018-2022 the AppCore .NET project.
33

44
using System;
5+
using System.Linq;
56
using AppCore.Extensions.DependencyInjection.Activator;
67
using FluentAssertions;
78
using Microsoft.Extensions.DependencyInjection;
@@ -30,38 +31,55 @@ public void AddExtensionGenericTypeRegistersServices()
3031
{
3132
var builder = new FacilityBuilder<TestFacility>(_services, _activator);
3233
builder.AddExtension<TestFacilityExtension>();
34+
builder.AddExtension<TestFacilityContractExtension>();
35+
_services.Select(sd => sd.ServiceType)
36+
.Should()
37+
.BeEquivalentTo(new [] { typeof(FacilityExtensionTestService), typeof(FacilityExtensionTestService) });
38+
}
39+
40+
[Fact]
41+
public void AddExtensionForContractGenericTypeRegistersServices()
42+
{
43+
var builder = new FacilityBuilder<ITestFacility>(_services, _activator);
44+
builder.AddExtension<TestFacilityContractExtension>();
3345
_services.Should()
34-
.ContainSingle(sd => sd.ServiceType == typeof(FacilityExtensionTestService));
46+
.Contain(sd => sd.ServiceType == typeof(FacilityExtensionTestService));
3547
}
3648

3749
[Fact]
3850
public void AddExtensionTypeRegistersServices()
3951
{
4052
var builder = new FacilityBuilder<TestFacility>(_services, _activator);
4153
builder.AddExtension(typeof(TestFacilityExtension));
42-
_services.Should()
43-
.ContainSingle(sd => sd.ServiceType == typeof(FacilityExtensionTestService));
54+
builder.AddExtension(typeof(TestFacilityContractExtension));
55+
_services.Select(sd => sd.ServiceType)
56+
.Should()
57+
.BeEquivalentTo(new [] { typeof(FacilityExtensionTestService), typeof(FacilityExtensionTestService) });
4458
}
4559

4660
[Fact]
4761
public void AddExtensionRegistersServices()
4862
{
4963
var builder = new FacilityBuilder<TestFacility>(_services, _activator);
5064
builder.AddExtension(new TestFacilityExtension());
51-
_services.Should()
52-
.ContainSingle(sd => sd.ServiceType == typeof(FacilityExtensionTestService));
65+
builder.AddExtension(new TestFacilityContractExtension());
66+
67+
_services.Select(sd => sd.ServiceType)
68+
.Should()
69+
.BeEquivalentTo(new [] { typeof(FacilityExtensionTestService), typeof(FacilityExtensionTestService) });
5370
}
5471

5572
[Fact]
56-
public void AddExtensionsFromRegistersServices()
73+
public void AddExtensionsFromInvokesResolver()
5774
{
5875
var resolver = Substitute.For<IFacilityExtensionResolver>();
59-
resolver.Resolve(Arg.Is(typeof(TestFacility)))
60-
.Returns(new[] { new TestFacilityExtension() });
76+
resolver.Resolve(Arg.Any<Type>())
77+
.Returns(Array.Empty<IFacilityExtension<IFacility>>());
6178

6279
var builder = new FacilityBuilder<TestFacility>(_services, _activator);
6380
builder.AddExtensionsFrom(r => r.AddResolver(resolver));
64-
_services.Should()
65-
.ContainSingle(sd => sd.ServiceType == typeof(FacilityExtensionTestService));
81+
82+
resolver.Received()
83+
.Resolve(typeof(TestFacility));
6684
}
6785
}

DependencyInjection/test/AppCore.Extensions.DependencyInjection.Tests/Facilities/FacilityServiceCollectionExtensionsTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ public void AddFacilityTypeRegistersServices()
3535
.ContainSingle(sd => sd.ServiceType == typeof(FacilityTestService));
3636
}
3737

38+
[Fact]
39+
public void AddFacilityTypeWithContractRegistersServices()
40+
{
41+
_services.AddFacility(typeof(TestFacility));
42+
43+
_services.Should()
44+
.ContainSingle(sd => sd.ServiceType == typeof(FacilityTestService));
45+
}
3846

3947
[Fact]
4048
public void AddFacilitiesFromRegistersServices()

0 commit comments

Comments
 (0)