-
-
Notifications
You must be signed in to change notification settings - Fork 4
Description
Sometimes you don't even have the source of the libraries you want to expose as services, or it would be cumbersome to annotate a large amount of them.
In this case, the typical solution is to implement a reflection-based approach where you inspect all types and register those that can be assigned to a given marker interface (i.e. IService) or by name (i.e. those ending in Service).
Since this project is all about compile-time registrations and zero run-time impact, it makes sense to also emit compile-time registrations that are more flexible than simply annotating all types.
After going through some alternatives (i.e. an assembly-level attribute), I decided that a much more discoverable approach is to add a "pseudo-reflection" overload to AddServices that can take a type and/or a regex. The source generator can evaluate both at compile-time and emit the registrations that the existing AddServices() will register, just as if they had been annotated with [Service].
Examples from tests:
public class ConventionsTests(ITestOutputHelper Output)
{
[Fact]
public void RegisterRepositoryServices()
{
var conventions = new ServiceCollection();
conventions.AddSingleton(Output);
conventions.AddServices(typeof(IRepository));
var services = conventions.BuildServiceProvider();
var instance = services.GetServices<IRepository>().ToList();
Assert.Equal(2, instance.Count);
}
[Fact]
public void RegisterServiceByRegex()
{
var conventions = new ServiceCollection();
conventions.AddSingleton(Output);
conventions.AddServices(nameof(ConventionsTests), ServiceLifetime.Transient);
var services = conventions.BuildServiceProvider();
var instance = services.GetRequiredService<ConventionsTests>();
var instance2 = services.GetRequiredService<ConventionsTests>();
Assert.NotSame(instance, instance2);
}
[Fact]
public void RegisterGenericServices()
{
var conventions = new ServiceCollection();
conventions.AddServices(typeof(IGenericRepository<>), ServiceLifetime.Scoped);
var services = conventions.BuildServiceProvider();
var scope = services.CreateScope();
var instance = scope.ServiceProvider.GetRequiredService<IGenericRepository<string>>();
var instance2 = scope.ServiceProvider.GetRequiredService<IGenericRepository<int>>();
Assert.NotNull(instance);
Assert.NotNull(instance2);
Assert.Same(instance, scope.ServiceProvider.GetRequiredService<IGenericRepository<string>>());
Assert.Same(instance2, scope.ServiceProvider.GetRequiredService<IGenericRepository<int>>());
}
}
public interface IRepository { }
public class FooRepository : IRepository { }
public class BarRepository : IRepository { }
public interface IGenericRepository<T> { }
public class FooGenericRepository : IGenericRepository<string> { }
public class BarGenericRepository : IGenericRepository<int> { }