Source generator for services registrations inspired by Scrutor. Code generation allows to have AOT-compatible code, without an additional hit on startup performance due to runtime assembly scanning.
Add the NuGet Package to your project:
dotnet add package ServiceScan.SourceGenerator
ServiceScan
generates a partial method implementation based on GenerateServiceRegistrations
attribute. This attribute can be added to a partial method with IServiceCollection
parameter.
For example, based on the following partial method:
public static partial class ServicesExtensions
{
[GenerateServiceRegistrations(AssignableTo = typeof(IMyService), Lifetime = ServiceLifetime.Scoped)]
public static partial IServiceCollection AddServices(this IServiceCollection services);
}
ServiceScan
will generate the following implementation:
public static partial class ServicesExtensions
{
public static partial IServiceCollection AddServices(this IServiceCollection services)
{
return services
.AddScoped<IMyService, ServiceImplementation1>()
.AddScoped<IMyService, ServiceImplementation2>();
}
}
The only thing left is to invoke this method on your IServiceCollection
instance
services.AddServices();
Register all FluentValidation validators
Unlike using FluentValidation.DependencyInjectionExtensions
package, ServiceScan
is AOT-compatible, and doesn't affect startup performance:
[GenerateServiceRegistrations(AssignableTo = typeof(IValidator<>), Lifetime = ServiceLifetime.Singleton)]
public static partial IServiceCollection AddValidators(this IServiceCollection services);
Add MediatR handlers
public static IServiceCollection AddMediatR(this IServiceCollection services)
{
return services
.AddTransient<IMediator, Mediator>()
.AddMediatRHandlers();
}
[GenerateServiceRegistrations(AssignableTo = typeof(IRequestHandler<>), Lifetime = ServiceLifetime.Transient)]
[GenerateServiceRegistrations(AssignableTo = typeof(IRequestHandler<,>), Lifetime = ServiceLifetime.Transient)]
private static partial IServiceCollection AddMediatRHandlers(this IServiceCollection services);
It adds MediatR requests handlers, although you might need to add other types like PipelineBehaviors or NotificationHandlers.
[GenerateServiceRegistrations(
TypeNameFilter = "*Repository",
AsImplementedInterfaces = true,
Lifetime = ServiceLifetime.Scoped)]
private static partial IServiceCollection AddRepositories(this IServiceCollection services);
You can add custom type handler, if you need to do something non-trivial with that type. For example, you can automatically discover and map Minimal API endpoints:
public interface IEndpoint
{
abstract static void MapEndpoint(IEndpointRouteBuilder endpoints);
}
public class HelloWorldEndpoint : IEndpoint
{
public static void MapEndpoint(IEndpointRouteBuilder endpoints)
{
endpoints.MapGet("/", () => "Hello World!");
}
}
public static partial class ServiceCollectionExtensions
{
[GenerateServiceRegistrations(AssignableTo = typeof(IEndpoint), CustomHandler = nameof(MapEndpoint))]
public static partial IEndpointRouteBuilder MapEndpoints(this IEndpointRouteBuilder endpoints);
private static void MapEndpoint<T>(IEndpointRouteBuilder endpoints) where T : IEndpoint
{
T.MapEndpoint(endpoints);
}
}
GenerateServiceRegistrations
attribute has the following properties:
Property | Description |
---|---|
FromAssemblyOf | Set the assembly containing the given type as the source of types to register. If not specified, the assembly containing the method with this attribute will be used. |
AssignableTo | Set the type that the registered types must be assignable to. Types will be registered with this type as the service type, unless AsImplementedInterfaces or AsSelf is set. |
Lifetime | Set the lifetime of the registered services. ServiceLifetime.Transient is used if not specified. |
AsImplementedInterfaces | If true, the registered types will be registered as implemented interfaces instead of their actual type. |
AsSelf | If true, types will be registered with their actual type. It can be combined with AsImplementedInterfaces . In that case implemented interfaces will be "forwarded" to an actual implementation type |
TypeNameFilter | Set this value to filter the types to register by their full name. You can use '*' wildcards. You can also use ',' to separate multiple filters. |
KeySelector | Set this value to a static method name returning string. Returned value will be used as a key for the registration. Method should either be generic, or have a single parameter of type Type . |
CustomHandler | Set this property to a static generic method name in the current class. Set this property to a static generic method name in the current class. This property is incompatible with Lifetime , AsImplementedInterfaces , AsSelf , KeySelector properties. |