Skip to content

Commit

Permalink
Merge pull request #12 from Tim-Maes/feature/register-for-all-assemblies
Browse files Browse the repository at this point in the history
Rename to AddAutowiringForAssembly
  • Loading branch information
Tim-Maes authored Oct 16, 2023
2 parents 82aefb2 + 238be7e commit bd76653
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 72 deletions.
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,25 @@ dotnet add package Bindicate
```
## Usage

### Add Bindicate
### Autowire dependencies

Register Bindicate inside your startup class, or inside your project's `ServiceCollectionExtension`
**Register Services in a Single Assembly**

Add the following code snippet in your project's `Startup` class or within a `ServiceCollectionExtensions` class:`

```csharp
services.AddBindicate(Assembly.GetExecutingAssembly());
services.AddAutowiringForAssembly(Assembly.GetExecutingAssembly());
```
This will scan all types in the current executing assembly and register them according to their respective attributes.

**Register Services Across Multiple Assemblies**

If you want to scan and register services across all loaded assemblies, you can do so by adding the following line in your hosting project:

```csharp
services.AddAutowiring();
```

### Decorate your services:

## Basic usage
Expand Down Expand Up @@ -99,7 +111,7 @@ public interface IMyTaskRunner

**Define a generic interface:**

Decorate the generic interface with the [RegisterGenericInterface] attribute.
Decorate the generic interface with the `[RegisterGenericInterface]` attribute.

```csharp
[RegisterGenericInterface]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public void ShouldRegisterGenericRepository_ForCustomerAndProduct_AsTransient()
var assembly = typeof(TransientRepository<>).Assembly;

// Act
services.AddBindicate(assembly);
services.AddAutowiringForAssembly(assembly);
var serviceProvider = services.BuildServiceProvider();

// Assert
Expand All @@ -38,7 +38,7 @@ public void ShouldRegisterGenericRepository_ForCustomer_AsScoped()
var assembly = typeof(Repository<>).Assembly;

// Act
services.AddBindicate(assembly);
services.AddAutowiringForAssembly(assembly);
var serviceProvider = services.BuildServiceProvider();

IRepository<Customer> instance1, instance2, instance3;
Expand Down Expand Up @@ -73,7 +73,7 @@ public void ShouldRegisterGenericRepository_ForCustomer_AsTransient()
var assembly = typeof(TransientRepository<>).Assembly;

// Act
services.AddBindicate(assembly);
services.AddAutowiringForAssembly(assembly);
var serviceProvider = services.BuildServiceProvider();

// Assert
Expand Down
6 changes: 2 additions & 4 deletions src/Bindicate.Tests/Scoped/AddScopedAttributeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
using Bindicate.Configuration;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Reflection;
using Xunit;
using Xunit.Sdk;

namespace Bindicate.Tests.ScopedTests;

Expand All @@ -17,7 +15,7 @@ public class AddScopedAttributeTests
public void AddScoped_WithInterface_RegistersCorrectly()
{
var services = new ServiceCollection();
services.AddBindicate(_testAssembly);
services.AddAutowiringForAssembly(_testAssembly);
var serviceProvider = services.BuildServiceProvider();

using (var scope = serviceProvider.CreateScope())
Expand All @@ -32,7 +30,7 @@ public void AddScoped_WithInterface_RegistersCorrectly()
public void AddScoped_RegistersCorrectly()
{
var services = new ServiceCollection();
services.AddBindicate(_testAssembly);
services.AddAutowiringForAssembly(_testAssembly);
var serviceProvider = services.BuildServiceProvider();

using (var scope = serviceProvider.CreateScope())
Expand Down
6 changes: 3 additions & 3 deletions src/Bindicate.Tests/Singleton/AddSingletonAttributeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class AddSingletonAttributeTests
public void AddSingleton_AlwaysReturnsSame()
{
var services = new ServiceCollection();
services.AddBindicate(_testAssembly);
services.AddAutowiringForAssembly(_testAssembly);
var serviceProvider = services.BuildServiceProvider();

var instance1 = serviceProvider.GetService<ISingletonInterface>();
Expand All @@ -29,7 +29,7 @@ public void AddSingleton_AlwaysReturnsSame()
public void AddSingleton_WithInterface_RegistersCorrectly()
{
var services = new ServiceCollection();
services.AddBindicate(_testAssembly);
services.AddAutowiringForAssembly(_testAssembly);
var serviceProvider = services.BuildServiceProvider();

using (var scope = serviceProvider.CreateScope())
Expand All @@ -44,7 +44,7 @@ public void AddSingleton_WithInterface_RegistersCorrectly()
public void AddSingleton_RegistersCorrectly()
{
var services = new ServiceCollection();
services.AddBindicate(_testAssembly);
services.AddAutowiringForAssembly(_testAssembly);
var serviceProvider = services.BuildServiceProvider();

using (var scope = serviceProvider.CreateScope())
Expand Down
7 changes: 3 additions & 4 deletions src/Bindicate.Tests/Transient/AddTransientAttributeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
using Xunit;
using Xunit.Sdk;

namespace Bindicate.Tests.Transient;

Expand All @@ -17,7 +16,7 @@ public class AddTransientAttributeTests
public void AddTransient_AlwaysReturnsNewInstance()
{
var services = new ServiceCollection();
services.AddBindicate(_testAssembly);
services.AddAutowiringForAssembly(_testAssembly);
var serviceProvider = services.BuildServiceProvider();

var instance1 = serviceProvider.GetService<ITransientInterface>();
Expand All @@ -30,7 +29,7 @@ public void AddTransient_AlwaysReturnsNewInstance()
public void AddTransient_WithInterface_RegistersCorrectly()
{
var services = new ServiceCollection();
services.AddBindicate(_testAssembly);
services.AddAutowiringForAssembly(_testAssembly);
var serviceProvider = services.BuildServiceProvider();

using (var scope = serviceProvider.CreateScope())
Expand All @@ -45,7 +44,7 @@ public void AddTransient_WithInterface_RegistersCorrectly()
public void AddSingleton_RegistersCorrectly()
{
var services = new ServiceCollection();
services.AddBindicate(_testAssembly);
services.AddAutowiringForAssembly(_testAssembly);
var serviceProvider = services.BuildServiceProvider();

using (var scope = serviceProvider.CreateScope())
Expand Down
6 changes: 3 additions & 3 deletions src/Bindicate/Bindicate.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
<Nullable>enable</Nullable>
<Title>Bindicate</Title>
<Authors>Tim Maes</Authors>
<Description>Easily add services to DI container by decorating your service with an attribute</Description>
<Description>Autowiring library. Easily add services to DI container by decorating your service with an attribute</Description>
<PackageProjectUrl>https://www.github.com/Tim-Maes/Bindicate</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://www.github.com/Tim-Maes/Bindicate</RepositoryUrl>
<PackageTags>di, ioc, service, collection, extensions, attribute</PackageTags>
<PackageReleaseNotes>GenericInterface support</PackageReleaseNotes>
<PackageReleaseNotes>Can scan all assemblies</PackageReleaseNotes>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<Version>1.1.7</Version>
<Version>1.1.8</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
126 changes: 75 additions & 51 deletions src/Bindicate/Configuration/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,74 +4,98 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Reflection;

namespace Bindicate.Configuration
namespace Bindicate.Configuration;

public static class ServiceCollectionExtensions
{
public static class ServiceCollectionExtensions
/// <summary>
/// Registers services using autowiring for all loaded assemblies.
/// This will scan through all types in each loaded assembly and
/// register them according to their attributes.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <returns>The same service collection so that multiple calls can be chained.</returns>
public static IServiceCollection AddAutowiring(this IServiceCollection services)
{
public static IServiceCollection AddBindicate(this IServiceCollection services, Assembly assembly)
// Iterate over all loaded assemblies and call AddAutowiringForAssembly for each one
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in assembly.GetTypes().Where(t => t.IsClass && !t.IsAbstract))
services.AddAutowiringForAssembly(assembly);
}
return services;
}

/// <summary>
/// Registers services using autowiring for a specific assembly.
/// This will scan through all types in the given assembly and
/// register them according to their attributes.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <param name="assembly">The assembly to scan for types.</param>
/// <returns>The same service collection so that multiple calls can be chained.</returns>
public static IServiceCollection AddAutowiringForAssembly(this IServiceCollection services, Assembly assembly)
{
foreach (var type in assembly.GetTypes().Where(t => t.IsClass && !t.IsAbstract))
{
var registerAttributes = type.GetCustomAttributes(typeof(BaseServiceAttribute), false)
.Cast<BaseServiceAttribute>();

foreach (var attr in registerAttributes)
{
var registerAttributes = type.GetCustomAttributes(typeof(BaseServiceAttribute), false)
.Cast<BaseServiceAttribute>();
var serviceType = attr.ServiceType ?? type;
var registrationMethod = GetRegistrationMethod(services, attr.Lifetime);

foreach (var attr in registerAttributes)
if (serviceType.IsDefined(typeof(RegisterGenericInterfaceAttribute), false))
{
var serviceType = attr.ServiceType ?? type;
var registrationMethod = GetRegistrationMethod(services, attr.Lifetime);

if (serviceType.IsDefined(typeof(RegisterGenericInterfaceAttribute), false))
if (serviceType.IsGenericType)
{
if (serviceType.IsGenericType)
{
services.Add(ServiceDescriptor.Describe(
serviceType.GetGenericTypeDefinition(),
type.GetGenericTypeDefinition(),
attr.Lifetime.ConvertToServiceLifetime())
);
}
else
services.Add(ServiceDescriptor.Describe(
serviceType.GetGenericTypeDefinition(),
type.GetGenericTypeDefinition(),
attr.Lifetime.ConvertToServiceLifetime())
);
}
else
{
// Handle non-generic services with generic interfaces
foreach (var iface in type.GetInterfaces())
{
// Handle non-generic services with generic interfaces
foreach (var iface in type.GetInterfaces())
if (iface.IsGenericType && iface.GetGenericTypeDefinition().IsDefined(typeof(RegisterGenericInterfaceAttribute), false))
{
if (iface.IsGenericType && iface.GetGenericTypeDefinition().IsDefined(typeof(RegisterGenericInterfaceAttribute), false))
{
var genericInterface = iface.GetGenericTypeDefinition();
services.Add(ServiceDescriptor.Describe(genericInterface, type, attr.Lifetime.ConvertToServiceLifetime()));
}
var genericInterface = iface.GetGenericTypeDefinition();
services.Add(ServiceDescriptor.Describe(genericInterface, type, attr.Lifetime.ConvertToServiceLifetime()));
}
}
}
else if (type.GetInterfaces().Contains(serviceType) || type == serviceType)
{
RegisterService(serviceType, type, registrationMethod);
}
else
{
throw new InvalidOperationException($"Type {type.FullName} does not implement {serviceType.FullName}");
}
}
else if (type.GetInterfaces().Contains(serviceType) || type == serviceType)
{
RegisterService(serviceType, type, registrationMethod);
}
else
{
throw new InvalidOperationException($"Type {type.FullName} does not implement {serviceType.FullName}");
}
}

return services;
}

private static Action<Type, Type> GetRegistrationMethod(IServiceCollection services, Lifetime.Lifetime lifetime)
=> lifetime switch
{
Lifetime.Lifetime.Scoped => (s, t) => services.AddScoped(s, t),
Lifetime.Lifetime.Singleton => (s, t) => services.AddSingleton(s, t),
Lifetime.Lifetime.Transient => (s, t) => services.AddTransient(s, t),
Lifetime.Lifetime.TryAddScoped => (s, t) => services.TryAddScoped(s, t),
Lifetime.Lifetime.TryAddSingleton => (s, t) => services.TryAddSingleton(s, t),
Lifetime.Lifetime.TryAddTransient => (s, t) => services.TryAddTransient(s, t),
_ => throw new ArgumentOutOfRangeException(nameof(lifetime), "Unsupported lifetime.")
};
return services;
}

private static void RegisterService(Type serviceType, Type implementationType, Action<Type, Type> registrationMethod)
private static Action<Type, Type> GetRegistrationMethod(IServiceCollection services, Lifetime.Lifetime lifetime)
=> lifetime switch
{
registrationMethod(serviceType, implementationType);
}
Lifetime.Lifetime.Scoped => (s, t) => services.AddScoped(s, t),
Lifetime.Lifetime.Singleton => (s, t) => services.AddSingleton(s, t),
Lifetime.Lifetime.Transient => (s, t) => services.AddTransient(s, t),
Lifetime.Lifetime.TryAddScoped => (s, t) => services.TryAddScoped(s, t),
Lifetime.Lifetime.TryAddSingleton => (s, t) => services.TryAddSingleton(s, t),
Lifetime.Lifetime.TryAddTransient => (s, t) => services.TryAddTransient(s, t),
_ => throw new ArgumentOutOfRangeException(nameof(lifetime), "Unsupported lifetime.")
};

private static void RegisterService(Type serviceType, Type implementationType, Action<Type, Type> registrationMethod)
{
registrationMethod(serviceType, implementationType);
}
}

0 comments on commit bd76653

Please sign in to comment.