Skip to content

Instantly share code, notes, and snippets.

@jmcd
Last active June 21, 2017 10:31
Show Gist options
  • Save jmcd/b48abae8d3df3717205fc0615bd53bf8 to your computer and use it in GitHub Desktop.
Save jmcd/b48abae8d3df3717205fc0615bd53bf8 to your computer and use it in GitHub Desktop.
Registration of services in .NET Core without a wall of services.AddFoo<>
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));
}
}
// 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