Created
January 22, 2020 08:45
-
-
Save mwadams/401b0cb613901e0a88e4a9d7f9e9e467 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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