Skip to content

Instantly share code, notes, and snippets.

@mwadams
Created January 22, 2020 08:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mwadams/401b0cb613901e0a88e4a9d7f9e9e467 to your computer and use it in GitHub Desktop.
Save mwadams/401b0cb613901e0a88e4a9d7f9e9e467 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
namespace DITest
{
public static class ServiceCollectionCheckerExtensions
{
public static void CheckMyDependenciesArentBonkers(this IServiceCollection services)
{
_ = services.Aggregate(ImmutableDictionary<Type, ServiceLifetime>.Empty, (d, sd) => CharacteriseService(services, sd, d));
}
private static ImmutableDictionary<Type, ServiceLifetime> CharacteriseService(IServiceCollection services, ServiceDescriptor sd, ImmutableDictionary<Type, ServiceLifetime> alreadyCharacterised)
{
if (alreadyCharacterised.ContainsKey(sd.ServiceType))
{
return alreadyCharacterised;
}
if (IsLeaf(sd))
{
return alreadyCharacterised.Add(sd.ServiceType, sd.Lifetime);
}
IEnumerable<Type> dependencyServiceTypes =
(from ctor in sd.ImplementationType.GetConstructors()
from ctorParam in ctor.GetParameters()
select ctorParam.ParameterType)
.Distinct();
IEnumerable<ServiceDescriptor> dependencies =
from dependencyServiceType in dependencyServiceTypes
from descriptor in GetServiceDescriptorsForDependencyServiceType(services, dependencyServiceType)
select descriptor;
alreadyCharacterised = dependencies.Aggregate(alreadyCharacterised, (d, dependentServiceDescriptor) => CharacteriseService(services, dependentServiceDescriptor, d));
var invalidDependencies = dependencies
.Where(dependentServiceDescriptor => !DependencyIsValid(sd.Lifetime, alreadyCharacterised[dependentServiceDescriptor.ServiceType]))
.ToList();
if (invalidDependencies.Count > 0)
{
var invalidMessages =
from invalidDependency in invalidDependencies
select $"{sd.ImplementationType} ({sd.Lifetime}) depends on {invalidDependency.ServiceType} ({invalidDependency.Lifetime})";
throw new InvalidOperationException("One or more invalid dependencies: " + string.Join(", ", invalidMessages));
}
return alreadyCharacterised;
}
private static bool DependencyIsValid(ServiceLifetime consumerLifetime, ServiceLifetime dependencyLifetime)
{
return (consumerLifetime, dependencyLifetime) switch
{
(ServiceLifetime.Scoped, _) => true,
(ServiceLifetime.Transient, ServiceLifetime.Transient) => true,
(ServiceLifetime.Transient, ServiceLifetime.Singleton) => true,
(ServiceLifetime.Singleton, ServiceLifetime.Singleton) => true,
_ => false
};
}
private static bool IsLeaf(ServiceDescriptor sd)
{
return sd.ImplementationInstance != null ||
sd.ImplementationType.GetConstructors().All(ctor => ctor.GetParameters().Length == 0);
}
private static IEnumerable<ServiceDescriptor> GetServiceDescriptorsForDependencyServiceType(IServiceCollection services, Type serviceType) =>
serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)
? GetServiceDescriptorsForDependencyServiceTypeCore(services, serviceType.GetGenericArguments()[0]) // Not recursing because we don't want to flatten IEnumerable<IEnumerable<T>> out to T
: GetServiceDescriptorsForDependencyServiceTypeCore(services, serviceType);
private static IEnumerable<ServiceDescriptor> GetServiceDescriptorsForDependencyServiceTypeCore(IServiceCollection services, Type serviceType) =>
services.Where(sd => sd.ServiceType == serviceType);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment