Last active
June 21, 2017 10:31
-
-
Save jmcd/b48abae8d3df3717205fc0615bd53bf8 to your computer and use it in GitHub Desktop.
Registration of services in .NET Core without a wall of services.AddFoo<>
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
public class Startup { | |
public void ConfigureServices(IServiceCollection services) | |
{ | |
... | |
services.AddApplicationServices(); | |
} | |
} | |
// Defines which stategies to use, and assemblies to scan, as an extension to be called from startup | |
public static class ApplicationServices | |
{ | |
public static void AddApplicationServices(this IServiceCollection services) | |
{ | |
var strategies = new DefaultTransientRegistrationStrategies() | |
.NoNotAdd<AgentHealthDataOpFactory.FetchOp>() | |
.AddSingleton<Names>() | |
.AddScoped<Bookmarker>(); | |
ServiceInterfaces.FromAssembliesContaining( | |
typeof(Startup), | |
typeof(AgencyClient.AgencyClient), | |
typeof(HealthDataClient)) | |
.Build() | |
.Select(si => strategies.Strategy(si.ServiceType, si.InterfaceType)) | |
.ForEach(strategy => strategy(services)); | |
} | |
} |
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
// A factory for strategies when most things should be transient | |
public class DefaultTransientRegistrationStrategies | |
{ | |
public readonly ISet<Type> scopedServices = new HashSet<Type>(); | |
public readonly ISet<Type> singletonServices = new HashSet<Type>(); | |
public readonly ISet<Type> nonServices = new HashSet<Type>(); | |
public DefaultTransientRegistrationStrategies AddScoped<T>() | |
{ | |
scopedServices.Add(typeof(T)); | |
return this; | |
} | |
public DefaultTransientRegistrationStrategies AddSingleton<T>() | |
{ | |
singletonServices.Add(typeof(T)); | |
return this; | |
} | |
public DefaultTransientRegistrationStrategies NoNotAdd<T>() | |
{ | |
nonServices.Add(typeof(T)); | |
return this; | |
} | |
public Action<IServiceCollection> Strategy(Type serviceType, Type interfaceType) | |
{ | |
switch (serviceType) | |
{ | |
case var _ when scopedServices.Contains(serviceType): | |
return services => services.AddScoped(interfaceType, serviceType); | |
case var _ when singletonServices.Contains(serviceType): | |
return services => services.AddSingleton(interfaceType, serviceType); | |
case var _ when nonServices.Contains(serviceType): | |
return _ => { }; | |
default: | |
return services => services.AddTransient(interfaceType, serviceType); | |
} | |
} | |
} | |
// Builds pairs of service-type and interface-type | |
public class ServiceInterfaces | |
{ | |
private readonly List<TypeInfo> allNonInterfaces = new List<TypeInfo>(); | |
private readonly List<TypeInfo> allInterfaces = new List<TypeInfo>(); | |
public static ServiceInterfaces FromAssembliesContaining(params Type[] types) | |
{ | |
var srvAsms = types.Select(t => t.GetTypeInfo().Assembly); | |
var allTypes = srvAsms.SelectMany(a => a.GetTypes()).Select(t => t.GetTypeInfo()).ToList(); | |
return new ServiceInterfaces() | |
.AddCandidateServiceTypes(allTypes) | |
.AddCandidateInterfaceTypes(allTypes); | |
} | |
public ServiceInterfaces AddCandidateServiceTypes(List<TypeInfo> typeInfos) | |
{ | |
allNonInterfaces.AddRange(typeInfos); | |
return this; | |
} | |
public ServiceInterfaces AddCandidateInterfaceTypes(List<TypeInfo> typeInfos) | |
{ | |
allInterfaces.AddRange(typeInfos); | |
return this; | |
} | |
public IEnumerable<(Type ServiceType, Type InterfaceType)> Build() | |
{ | |
var allGenericInterfaces = allInterfaces.Where(ti => ti.IsGenericTypeDefinition).ToSet(); | |
var allNonGenericInterfaces = allInterfaces.Except(allGenericInterfaces).ToSet(); | |
bool IsNonGenericInterface(TypeInfo ti) => allNonGenericInterfaces.Contains(ti); | |
bool IsClosedGenericInterface(TypeInfo ti) | |
{ | |
var isOpen = !ti.IsGenericType || ti.IsGenericTypeDefinition; | |
if (isOpen) | |
{ | |
return false; | |
} | |
var ifc = ti.GetGenericTypeDefinition().GetTypeInfo(); | |
return allGenericInterfaces.Contains(ifc); | |
} | |
bool IsRelevantInterface(TypeInfo ti) => IsNonGenericInterface(ti) || IsClosedGenericInterface(ti); | |
IList<TypeInfo> RelevantInterfaces(TypeInfo type) => type.GetInterfaces().Select(t => t.GetTypeInfo()).Where(IsRelevantInterface).ToList(); | |
return allNonInterfaces | |
.Where(t => t.IsConcrete()) | |
.Select(ti => (ServiceType: ti, InterfaceTypes: RelevantInterfaces(ti))) | |
.Where(tpl => tpl.InterfaceTypes.Any()) | |
.SelectMany(tpl => tpl.InterfaceTypes.Select(ifc => (tpl.ServiceType.AsType(), ifc.AsType()))); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment