Though using library like Scrutor or SimpleInjector is better option, due to constraints I had to hand-roll auto registeration with reflection. I've tried to filter types by their namespace like @Beej's answer, but I have since learned that it is more effective to explicitly mark classes with attributes after editing Startup.cs
3 times in a row in order to tweak lifetime.
Here is example to collect classes marked with attributes:
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class TransientAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class ScopedAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class SingletonAttribute : Attribute {}
/*
Mark your classes with [Transient], [Scoped] or [Singleton] above then register like:
services.AddServicesByAttributes(typeof(Startup).Assembly);
*/
public static class AddByAttributeServiceCollectionExtensions
{
public static IServiceCollection AddServicesByAttributes(this IServiceCollection services, Assembly assembly)
{
foreach (ServiceDescriptor d in assembly.GetTypes().SelectMany(MakeDescriptors).Distinct())
{
services.Add(d);
}
return services;
}
private static ServiceLifetime? FindLifetime(Type type)
{
return (
type.IsDefined(typeof(TransientAttribute), false),
type.IsDefined(typeof(ScopedAttribute), false),
type.IsDefined(typeof(SingletonAttribute), false)
) switch
{
(false, false, false) => null,
(true, false, false) => ServiceLifetime.Transient,
(false, true, false) => ServiceLifetime.Scoped,
(false, false, true) => ServiceLifetime.Singleton,
_ => throw new ArgumentException($"Lifetime attribute specified more than once for {type}", nameof(type)),
};
}
private static IEnumerable<ServiceDescriptor> MakeDescriptors(Type type)
{
if (FindLifetime(type) is not ServiceLifetime lifetime) { yield break; }
if (type.IsGenericType)
{
// Handle Generic interfaces. Nested types not supported
Type impl = type.GetGenericTypeDefinition();
foreach (Type ifType in type.GetInterfaces())
{
// Strip type arguments
Type service = ifType.IsGenericType ? ifType.GetGenericTypeDefinition() : ifType;
yield return ServiceDescriptor.Describe(service, impl, lifetime);
}
}
else
{
Type impl = type;
yield return ServiceDescriptor.Describe(impl, impl, lifetime);
// Alias for interfaces
// NB. Dispose() may be called more than once for shared instances
Func<IServiceProvider, object> factory = (sp) => sp.GetRequiredService(impl);
foreach (Type service in type.GetInterfaces())
{
yield return ServiceDescriptor.Describe(service, factory, lifetime);
}
}
}
}
I recommend this book by @ploeh
and @Steven that shows how to cope with ASP.NET Dependency injection.