https://github.com/ipjohnson/DependencyModules
https://github.com/ipjohnson/DependencyModules
Last synced: about 2 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/ipjohnson/DependencyModules
- Owner: ipjohnson
- License: mit
- Created: 2024-12-30T18:41:51.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2025-02-13T00:40:34.000Z (2 months ago)
- Last Synced: 2025-02-13T01:30:00.155Z (2 months ago)
- Language: C#
- Size: 180 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
- RSCG_Examples - https://github.com/ipjohnson/DependencyModules
README
# DependencyModules
DependencyModules is a C# source generator package that uses attributes to create
dependency injection registration modules. These modules can then be used to populate
an IServiceCollection instance.## Installation
```csharp
dotnet add package DependencyModules.Runtime
dotnet add package DependencyModules.SourceGenerator
```## Service Attributes
* `[DependencyModule]` - used to attribute class that will become dependency module (must be partial)
* `[SingletonService]` - registers service as `AddSingleton`
* `[ScopedService]` - registers service as `AdddScoped`
* `[TransientService]` - registers service as `AddTransient`
* `[CrossWireService]` - register implementation and interfaces with the same lifetime```csharp
// Registration example
[DependencyModule]
public partial class MyModule { }// registers SomeClass implementation for ISomeService
[SingletonService]
public class SomeClass : ISomeService
{
public string SomeProp => "SomeString";
}// registers OtherSerice implementation
[TransientService]
public class OtherService
{
public OtherService(ISomeService service)
{
SomeProp = service.SomeProp;
}
public string SomeProp { get; }
}
```
## Container Instantiation`AddModule` - method adds modules to service collection
```csharp
var serviceCollection = new ServiceCollection();serviceCollection.AddModule();
var provider = serviceCollection.BuildServiceProvider();
var service = provider.GetService();
```## Factories
Sometimes it's not possible to construct all types through normal registration.
Factories can be registered with a module using the registration attributes.```csharp
public class SomeClass : ISomeInterface {
public SomeClass(IDep one, IDepTwo two, DateTime dateTime) { ... }
[SingletonService]
public static ISomeInterface Factory(IDep one, IDepTwo two) {
return new SomeClass(one, two, DateTime.Now());
}
}
```
## Module Re-useDependencyModules creates an `Attribute` class that can be used to apply sub dependencies.
```csharp
// Modules can be re-used with the generated attributes
[DependencyModule]
[MyModule.Attribute]
public partial class AnotherModule { }
```## Parameters
Sometimes you want to provide extra registration for your module.
This can be achieved by adding a constructor to your module or optional properties.
Note these parameters and properties will be correspondingly implemented in the module attribute.```csharp
[DependencyModule]
public partial class SomeModule : IServiceCollectionConfiguration
{
private bool _someFlag;
public SomeModule(bool someFlag = false)
{
_someFlag = someFlag;
}
public string OptionalString { get; set; } = "";
public void ConfigureServices(IServiceCollection services)
{
if (_someFlag)
{
// custom registration
}
}
}[DependencyModule]
[SomeModule.Attribute(true, OptionalString = "otherString")]
public partial class SomeOtherModule
{}
```## Managing duplicate registration
By default a module will only be loaded once, assuming attributes are used or the modules are specified in the same `AddModules` call. Seperate calls to `AddModule` will result in modules being loaded multiple times. If a module uses parameters it can be useful to load a module more than once. That can be accompilished by overriding the `Equals` and `GetHashcode` methods to allow for multiple loads.
Services will be registered using an `Add` method by default. This can be overriden using the `With` property on individual service or at the `DepedencyModule` level.
```csharp
[SingletonService(With = RegistrationType.Try)]
public class SomeService { }[DependencyModule(With = RegistrationType.Try)]
public partial class SomeModule { }
```## Realm
By default, all dependencies are registered in all modules within the same assembly.
The realm allows the developer to scope down the registration within a given module.```csharp
// register only dependencies specifically marked for this realm
[DependencyModule(OnlyRealm = true)]
public partial class AnotherModule { }[SingletonService(ServiceType = typeof(ISomeInterface),
Realm = typeof(AnotherModule))]
public class SomeDep : ISomeInterface { }
```## Keyed Registration
Registration attributes have a `Key` property that allows for specifying the key at registration time.
```csharp
[SingletonService(Key = "SomeKey")]
public class KeyService : IKeyService { }// yields this registration line
services.AddKeyedSingleton(typeof(IKeyService), "SomeKey", typeof(KeyService));
```## Unit testing & Mocking
DependencyModules provides an xUnit extension to make testing much easier.
It handles the population and construction of a service provider using specified modules.```csharp
> dotnet add package DependencyModules.xUnit
> dotnet add package DependencyModules.xUnit.NSubstitute// applies module & nsubstitute support to all tests.
// test attributes can be applied at the assembly, class, and test method level
[assemlby: MyModule.Attribute]
[assembly: NSubstituteSupport]public class OtherServiceTests
{
[ModuleTest]
public void SomeTest(OtherService test, [Mock]ISomeService service)
{
service.SomeProp.Returns("some mock value");
Assert.Equals("some mock value", test.SomeProp);
}
}```
## ImplementationBehind the scenes the library generates registration code that can be used with any `IServiceCollection` compatible DI container.
Example generated code for [SutModule.cs](integ-tests/SutProject/SutModule.cs)
```csharp
// SutModule.Dependencies.g.cs
public partial class SutModule
{
private static int moduleField = DependencyRegistry.Add(ModuleDependencies);private static void ModuleDependencies(IServiceCollection services)
{
services.AddTransient(typeof(IDependencyOne), typeof(DependencyOne));
services.AddSingleton(typeof(IGenericInterface<>), typeof(GenericClass<>));
services.AddScoped(typeof(IScopedService), typeof(ScopedService));
services.AddSingleton(typeof(ISingletonService), typeof(SingletonService));
services.AddSingleton(typeof(IGenericInterface), typeof(StringGeneric));
}
}// SutModule.Modules.g.cs
public partial class SutModule : IDependencyModule
{
static SutModule()
{
}// this method loads all dependencies into IServiceCollection.
public void PopulateServiceCollection(IServiceCollection services)
{
DependencyRegistry.LoadModules(services, this);
}void IDependencyModule.InternalApplyServices(IServiceCollection services)
{
DependencyRegistry.ApplyServices(services);
}public override bool Equals(object? obj)
{
return obj is SutModule;
}public override int GetHashCode()
{
return HashCode.Combine(base.GetHashCode());
}public class Attribute : System.Attribute, IDependencyModuleProvider
{
public IDependencyModule GetModule()
{
var newModule = new SutModule();
return newModule;
}
}
}
```