Flexible dependency injection extensions for convention-based service registration, attribute-based service registration, custom automatic service registration and decorator pattern
$ dotnet add package VDT.Core.DependencyInjectionProvides extensions for Microsoft.Extensions.DependencyInjection.IServiceCollection
The extension method ServiceCollectionExtensions.AddServices is used to register all services provided by a ServiceRegistrationOptions object built with
the provided setup action. The ServiceRegistrationOptions class provides methods to build it in a fluent way:
AddAssembly adds an assembly to scan for servicesAddAssemblies adds multiple assemblies to scan for services
AddServiceRegistrationProvider adds a method that returns service types for a given implementation type found in the assemblies
ServiceRegistrationProviderServiceRegistration which contain the service type and optionally the desired ServiceLifetimeUseDefaultServiceLifetime sets the default ServiceLifetime to use if the found ServiceRegistration does not include a ServiceLifetimeUseServiceRegistrar sets the method to use for registering services
ServiceRegistrarServiceDescriptor will be created with the found service type, implementation type and lifetimeAddServiceTypeProvider methods have been deprecated since they are being replaced by AddServiceRegistrationProviderpublic class Startup {
public void ConfigureServices(IServiceCollection services) {
services.AddServices(setupAction: options => options
// Add assemblies to scan
.AddAssembly(assembly: typeof(Startup).Assembly)
.AddAssemblies(typeof(MyService).Assembly, typeof(MyOtherService).Assembly)
// Add all project assemblies based on prefix
.AddAssemblies(entryAssembly: typeof(Startup).Assembly, assemblyPrefix: "MyCompany.MySolution")
// Add a service registration provider with a lifetime
.AddServiceRegistrationProvider(
serviceRegistrationProvider: implementationType => implementationType.GetInterfaces()
.Where(serviceType => serviceType.Assembly == implementationType.Assembly)
.Select(new ServiceRegistration(serviceType, ServiceLifetime.Scoped))
)
// Add a service registration provider without a lifetime
.AddServiceRegistrationProvider(
serviceRegistrationProvider: implementationType => implementationType.GetInterfaces()
.Where(serviceType => serviceType.Assembly == implementationType.Assembly)
.Select(new ServiceRegistration(serviceType))
)
// Optional: use a different ServiceLifetime than Scoped by default
.UseDefaultServiceLifetime(serviceLifetime: ServiceLifetime.Transient)
// Optional: provide your own method for registering services
.UseServiceRegistrar(
serviceRegistrar: (services, serviceType, implementationType, serviceLifetime) => services.Add(new ServiceDescriptor(serviceType, implementationType, serviceLifetime))
)
);
// ...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
// ...
}
}
The class DefaultServiceRegistrationProviders contains three ways to register services based on common conventions. Each method creates a
ServiceRegistrationProvider that can be added to the ServiceRegistrationOptions to find services to register, and each method takes the ServiceLifetime
of the services found by this provider as an optional parameter.
CreateSingleInterfaceProvider returns the single interface if an implementation type implements exactly one interfaceCreateInterfaceByNameProvider returns interface types found on on implementation types that follow the .NET naming guideline of naming class-interface pairs:
IMyServiceMyServiceCreateGenericInterfaceRegistrationProvider finds generic types based on a generic type definition:
IRequestHandler<TRequest>ExampleRequestHandler : IRequestHandler<ExampleRequest>public class Startup {
public void ConfigureServices(IServiceCollection services) {
services.AddServices(setupAction: options => options
// Add assemblies to scan
.AddAssembly(assembly: typeof(Startup).Assembly)
.AddAssembly(assembly: typeof(MyService).Assembly)
// Add default service registration providers
.AddServiceRegistrationProvider(
serviceRegistrationProvider: DefaultServiceRegistrationProviders.CreateSingleInterfaceProvider()
)
.AddServiceRegistrationProvider(
serviceRegistrationProvider: DefaultServiceRegistrationProviders.CreateInterfaceByNameProvider(ServiceLifetime.Scoped)
)
.AddServiceRegistrationProvider(
serviceRegistrationProvider: DefaultServiceRegistrationProviders.CreateGenericInterfaceRegistrationProvider(typeof(IRequestHandler<>))
)
);
// ...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
// ...
}
}
The extension method Attributes.ServiceRegistrationOptionsExtensions.AddAttributeServiceRegistrationProviders allows you to move registration for your
services from the Startup class to the services themselves. Simply mark your services or service implementations with the different types of service
attributes to indicate that a service should be registered and use the options extension to add the correct providers.
There are nine attributes available:
Attributes.TransientAttribute marks a service to be registered as transientAttributes.ScopedAttribute marks a service to be registered as scopedAttributes.SingletonServiceAttribute marks a service to be registered as singletonAttributes.TransientServiceAttribute marks a service to be registered as transient with the supplied implementation typeAttributes.ScopedServiceAttribute marks a service to be registered as scoped with the supplied implementation typeAttributes.SingletonScopedServiceAttribute marks a service to be registered as singleton with the supplied implementation typeAttributes.TransientServiceImplementationAttribute marks a service implementation to be registered as transient for the supplied service typeAttributes.ScopedServiceImplementationAttribute marks a service implementation to be registered as scoped for the supplied service typeAttributes.SingletonServiceImplementationAttribute marks a service implementation to be registered as singleton for the supplied service typeThe extension methods Attributes.ServiceCollectionExtensions.AddAttributeServices are convenience methods that you can use if you don't need any other
service registration providers or additional setup of service registration.
// Mark the service to be registered
[ScopedService(implementationType: typeof(Bar))]
public interface IBar {
void Foo();
}
public class Bar : IBar {
public void Foo() {
// ...
}
}
public interface IBar {
void Foo();
}
// Mark the implementation
[ScopedServiceImplementation(serviceType: typeof(IBar))]
public class Bar : IBar {
public void Foo() {
// ...
}
}
public class Startup {
public void ConfigureServices(IServiceCollection services) {
// Register using options
services.AddServices(setupAction: options => options
.AddAssembly(assembly: typeof(Startup).Assembly)
.AddAttributeServiceRegistrationProviders()
);
// Register using convenience method
services.AddAttributeServices(assembly: typeof(Startup).Assembly);
// Register using convenience method and apply decorators
services.AddAttributeServices(assembly: typeof(Startup).Assembly, decoratorSetupAction: options => options.AddAttributeDecorators());
// ...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
// ...
}
}
The extension methods Decorators.ServiceCollectionExtensions.AddTransient, Decorators.ServiceCollectionExtensions.AddScoped and
Decorators.ServiceCollectionExtensions.AddSingleton with an Action<Decorators.DecoratorOptions> parameter allow you to register services decorated with
your own implementations of the Decorators.IDecorator interface.
If you wish to use decorators when using options-based service registration as described above, it is possible to use the
Decorators.ServiceRegistrationOptionsExtensions.UseDecoratorServiceRegistrar extension method which can apply decorator options to all classes that are
registered using the provided ServiceRegistrationProvider methods.
The Decorators.IDecorator interface has three methods:
BeforeExecute allows you to execute code before execution of the decorated methodAfterExecute allows you to execute code after execution of the decorated methodOnError allows you to execute code if the decorated method throws an errorThere are two ways to apply decorators to your services: you can apply a custom attribute to the methods on either the service or service imlementation you want decorated, or you can provide predicates with decorator types when registering the services.
public class LogDecorator : IDecorator {
public void BeforeExecute(MethodExecutionContext context) {
Debug.WriteLine($"Executing '{context.TargetType.FullName}.{context.Method.Name}'");
}
public void AfterExecute(MethodExecutionContext context) {
Debug.WriteLine($"Executed '{context.TargetType.FullName}.{context.Method.Name}'");
}
public void OnError(MethodExecutionContext context, System.Exception exception) {
Debug.WriteLine($"Failed to execute '{context.TargetType.FullName}.{context.Method.Name}': {exception.Message}");
}
}
// For attribute based decoration
[AttributeUsage(AttributeTargets.Method)]
public class LogAttribute : Attribute, IDecorateAttribute<LogDecorator> {
}
public interface IExample {
// Decorate from service
[Log]
void Foo();
void Bar();
}
public class Example : IExample {
public void Foo() {
// ...
}
// Decorate from implementation
[Log]
public void Bar() {
// ...
}
}
public class Startup {
public void ConfigureServices(IServiceCollection services) {
// Register services with a provided decorator
services.AddServices(options => options
.AddAssembly(assembly: typeof(Startup).Assembly)
.AddServiceRegistrationProvider(
serviceRegistrationProvider: DefaultServiceRegistrationProviders.CreateInterfaceByNameProvider()
)
.UseDecoratorServiceRegistrar(setupAction: options => options.AddDecorator<LogDecorator>(predicate: method => method.Name == "Foo"))
);
// Register services using attributes
services.AddServices(options => options
.AddAssembly(assembly: typeof(Startup).Assembly)
.AddServiceRegistrationProvider(
serviceRegistrationProvider: DefaultServiceRegistrationProviders.CreateInterfaceByNameProvider()
)
.UseDecoratorServiceRegistrar(setupAction: options => options.AddAttributeDecorators())
);
// Register a single service with a provided decorator
services.AddScoped<IExample, Example>(setupAction: options => options.AddDecorator<LogDecorator>(predicate: method => method.Name == "Foo"));
// Register a single service using attributes
services.AddScoped<IExample, Example>(setupAction: options => options.AddAttributeDecorators());
// ...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
// ...
}
}