Skip to content

Commit 23b4ac4

Browse files
committed
Added ServiceCollectionVerify example
1 parent 0756b5d commit 23b4ac4

7 files changed

+151
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Contains all of my examples from various blog posts. You can find a comprehensiv
44

55
| BlogPost | Publish Date |
66
| -------------------------------------------------------------------------------------------------------------- | ------------ |
7+
| [Verifying your DI Container](ServiceCollectionVerify/) | 30.04.2023 |
78
| [.NET 8 Performance Edition](BenchmarkDotNet8/) | 12.04.2023 |
89
| [Creating a ToolTip Component in Blazor](BlazorToolTip/) | 31.03.2023 |
910
| [C# Source Generators: How to get build information?](BuildInformation/) | 26.03.2023 |

ServiceCollectionVerify/Program.cs

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using ServiceCollectionVerify;
3+
4+
var services = new ServiceCollection();
5+
6+
services.AddScoped<TransientService>();
7+
services.AddSingleton<SingletonService>();
8+
services.AddScoped<ServiceWithMissingDependency>();
9+
10+
services.Verify();

ServiceCollectionVerify/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Verifying your DI Container
2+
3+
Microsoft's integrated dependency injection (short DI) container is very powerful, but there are also certain pitfalls. In this article, I will show you what some of the pitfalls are and how you can verify them.
4+
5+
Found [here](https://steven-giesel.com/blogPost/ce948083-974a-4c16-877f-246b8909fa6d)

ServiceCollectionVerify/Service.cs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace ServiceCollectionVerify;
2+
3+
// Ohoh - a singleton with a dependency on a scoped service -> captive dependency
4+
public class SingletonService
5+
{
6+
private readonly TransientService _transientService;
7+
8+
public SingletonService(TransientService transientService)
9+
{
10+
_transientService = transientService;
11+
}
12+
}
13+
public class TransientService { }
14+
15+
// Service with a missing dependency
16+
public class ServiceWithMissingDependency
17+
{
18+
public ServiceWithMissingDependency(MissingDependency missingDependency)
19+
{
20+
}
21+
}
22+
23+
// Does not get registered in the service collection
24+
public class MissingDependency
25+
{
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
using System.Text;
2+
using Microsoft.Extensions.DependencyInjection;
3+
4+
namespace ServiceCollectionVerify;
5+
6+
public class VerifyResult
7+
{
8+
public bool IsValid => Errors.Count == 0;
9+
public List<string> Errors { get; } = new();
10+
11+
public override string ToString()
12+
{
13+
var sb = new StringBuilder();
14+
sb.AppendLine($"Found {Errors.Count} error(s):");
15+
for (var i = 0; i < Errors.Count; i++)
16+
{
17+
sb.AppendLine($"{i + 1}. {Errors[i]}");
18+
}
19+
20+
return sb.ToString();
21+
}
22+
}
23+
24+
public static class ServiceCollectionExtensions
25+
{
26+
public static void Verify(this IServiceCollection services)
27+
{
28+
var result = new VerifyResult();
29+
using var serviceProvider = services.BuildServiceProvider();
30+
31+
result.Errors.AddRange(CheckServicesCanBeResolved(serviceProvider, services));
32+
result.Errors.AddRange(CheckForCaptiveDependencies(services));
33+
34+
if (!result.IsValid)
35+
{
36+
throw new InvalidOperationException(result.ToString());
37+
}
38+
}
39+
40+
private static List<string> CheckServicesCanBeResolved(IServiceProvider serviceProvider, IServiceCollection services)
41+
{
42+
var unresolvedTypes = new List<string>();
43+
foreach (var serviceDescriptor in services)
44+
{
45+
try
46+
{
47+
serviceProvider.GetRequiredService(serviceDescriptor.ServiceType);
48+
}
49+
catch
50+
{
51+
unresolvedTypes.Add($"Unable to resolve '{serviceDescriptor.ServiceType.FullName}'");
52+
}
53+
}
54+
55+
return unresolvedTypes;
56+
}
57+
58+
private static IEnumerable<string> CheckForCaptiveDependencies(IServiceCollection services)
59+
{
60+
var singletonServices = services
61+
.Where(descriptor => descriptor.Lifetime == ServiceLifetime.Singleton)
62+
.Select(descriptor => descriptor.ServiceType);
63+
64+
foreach (var singletonService in singletonServices)
65+
{
66+
var captiveScopedServices = singletonService
67+
.GetConstructors()
68+
.SelectMany(property => property.GetParameters())
69+
.Where(propertyType => services.Any(descriptor => descriptor.ServiceType == propertyType.ParameterType
70+
&& descriptor.Lifetime == ServiceLifetime.Scoped
71+
|| descriptor.Lifetime == ServiceLifetime.Transient));
72+
73+
foreach (var captiveService in captiveScopedServices)
74+
{
75+
yield return $"Singleton service '{singletonService.FullName}' has one or more captive dependencies: {string.Join(", ", captiveService.ParameterType.FullName)}";
76+
}
77+
}
78+
}
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>exe</OutputType>
5+
<TargetFramework>net7.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
12+
</ItemGroup>
13+
14+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceCollectionVerify", "ServiceCollectionVerify.csproj", "{A9DA8B90-C0A7-4616-811E-CA34D7D7A873}"
4+
EndProject
5+
Global
6+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7+
Debug|Any CPU = Debug|Any CPU
8+
Release|Any CPU = Release|Any CPU
9+
EndGlobalSection
10+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
11+
{A9DA8B90-C0A7-4616-811E-CA34D7D7A873}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
12+
{A9DA8B90-C0A7-4616-811E-CA34D7D7A873}.Debug|Any CPU.Build.0 = Debug|Any CPU
13+
{A9DA8B90-C0A7-4616-811E-CA34D7D7A873}.Release|Any CPU.ActiveCfg = Release|Any CPU
14+
{A9DA8B90-C0A7-4616-811E-CA34D7D7A873}.Release|Any CPU.Build.0 = Release|Any CPU
15+
EndGlobalSection
16+
EndGlobal

0 commit comments

Comments
 (0)