Dependency Injection
Do you hate having to update several areas of code, just to install a dependency? Is DI hard for installing against multiple interfaces or open generics? If so, this library is for you! It uses source generation to create a single file that contains all of your dependency injection registrations. This means you can just add the attribute and it will generate the code for you.
Features
Section titled “Features”- Source generate all attributed classes to a single add file - saves you the boilerplate
- Extension methods for registering a dependency against multiple interfaces
- Extension methods for startup tasks (different to hosted services that don’t work on mobile)
- Supports multiple interfaces
- Supports open generics
- Supports keyed services
- Supports categorization of services - great for unit testing or dev vs production
The Results
Section titled “The Results”THIS:
using Microsoft.Extensions.DependencyInjection;using Shiny.Extensions.DependencyInjection;
// given the following code from a usernamespace Sample{ public interface IStandardInterface;
public interface IStandardInterface2;
[Service(ServiceLifetime.Singleton)] public class ImplementationOnly;
[Service(ServiceLifetime.Transient, "ImplOnly")] public class KeyedImplementationOnly;
[Service(ServiceLifetime.Singleton)] public class StandardImplementation : IStandardInterface;
[Service(ServiceLifetime.Scoped, "Standard")] public class KeyedStandardImplementation : IStandardInterface;
[Service(ServiceLifetime.Singleton)] public class MultipleImplementation : IStandardInterface, IStandardInterface2;
[Service(ServiceLifetime.Scoped)] public class ScopedMultipleImplementation : IStandardInterface, IStandardInterface2;
[Service(ServiceLifetime.Scoped, "KeyedGeneric")] public class TestGeneric<T1, T2> { public T1 Value1 { get; set; } public T2 Value2 { get; set; } }}
GENERATES THIS:
// <auto-generated />using global::Microsoft.Extensions.DependencyInjection;using global::Shiny.Extensions.DependencyInjection;
namespace Sample{ public static class __GeneratedRegistrations { public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddGeneratedServices( this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services ) { services.AddSingleton<global::Sample.ImplementationOnly>(); services.AddKeyedTransient<global::Sample.KeyedImplementationOnly>("ImplOnly"); services.AddSingleton<global::Sample.IStandardInterface, global::Sample.StandardImplementation>(); services.AddKeyedScoped<global::Sample.IStandardInterface, global::Sample.KeyedStandardImplementation>("Standard"); services.AddSingletonAsImplementedInterfaces<global::Sample.MultipleImplementation>(); services.AddScopedAsImplementedInterfaces<global::Sample.ScopedMultipleImplementation>(); services.AddKeyedScoped(typeof(global::Sample.TestGeneric<,>), "KeyedGeneric");
return services; } }}
- Install the NuGet package
- Add the following using directive:
// during your app startup - use your service collectionbuilder.Services.AddGeneratedServices();
- Add the
[Service(ServiceLifetime.Singleton, KeyedName = "optional key")]
attribute to your classes and specify the lifetime and optional key
Categorization
Section titled “Categorization”The problem with source generating everything is that it sees “ALL” attributes. However, when we’re doing different service registrations based on environment or testing, we’re often commenting pieces of code out or doing some sort of wild conditions everywhere. Thus, enter “categories”. Categories basically tag a service using:
[Service(ServiceLifetime.Singleton, Category = "Test")]public class TestService;
services.AddGeneratedServices("Test"); // this will register all services without ANY category and any services marked with the "Test" category
Multiple Interfaces
Section titled “Multiple Interfaces”If you place a service attribute on a class that implements multiple interfaces and is scoped or singleton, it will register to get the same instance in the lifetime specified against ALL interfaces.
If you don’t want this behaviour, you can specify the Type property in the service attribute to specify the specific interface to use.
[Service(ServiceLifetime.Singleton, Type = typeof(IStandardInterface))]// OR [Singleton(Type = typeof(IStandardInterface))]public class StandardImplementation : IStandardInterface, IStandardInterface2;
Multiple Registration Points (TryAddLifetime)
Section titled “Multiple Registration Points (TryAddLifetime)”All service attributes now have a TryAdd
property that will call the appropriate service lifetime method. This means you can use the same attribute on multiple classes and it will only register the first one.
[Service(ServiceLifetime.Singleton, TryAdd = true)]public class ImplementationOnly;
IServiceCollection services;services.TryAddSingleton<ImplementationOnly>(); // instead of just AddSingleton<ImplementationOnly>();
Additional Configuration
Section titled “Additional Configuration”You can add additional configuration to your csproj to manage how the source generation task works.
<PropertyGroup> <ShinyDIExtensionMethodName>AddMyServices</ShinyDIExtensionMethodName> <ShinyDIExtensionNamespace>My.Namespace</ShinyDIExtensionNamespace> <ShinyDIExtensionInternalAccessor>true</ShinyDIExtensionInternalAccessor></PropertyGroup>
Property | Description |
---|---|
ShinyDIExtensionMethodName | The name of the extension method to generate. Defaultsto AddGeneratedServices |
ShinyDIExtensionNamespace | The namespace to use for the generated code. Defaults to global namespace. |
ShinyDIExtensionInternalAccessor | If set to true , the generated extension method will be internal instead of public. This is useful for unit testing or if |