diff --git a/README.md b/README.md
index 87a2966..10aa511 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,25 @@ services.AddServicesFrom("MyCompany.ProjectName.Repositories.Concrete"); // <--
- That's it! DotNurse can find your namespace from any assembly. You don't need to send any Assembly parameter.
+***
+
+## Using Lazy Proxy
+DotNurse Injector supports lazy proxies for better performance at runtime. When it used, injected object won't be created until first usage of it.
+Visit the github page of [lazy-proxy](https://github.com/servicetitan/lazy-proxy) for better understanding.
+
+
+### Usage
+Configuring `UseLazyProxy` as **true** provides registering services with LazyProxy:
+
+
+```csharp
+services.AddServicesFrom("MyProject.Namespace", ServiceLifetime.Transient, opts => opts.UseLazyProxy = true);
+
+// OR
+
+services.AddServicesByAttributes(useLazyProxy: true);
+```
+
***
## Property/Field Injection
diff --git a/src/DotNurse.Injector/DotNurse.Injector.csproj b/src/DotNurse.Injector/DotNurse.Injector.csproj
index 91d927a..8ff0d18 100644
--- a/src/DotNurse.Injector/DotNurse.Injector.csproj
+++ b/src/DotNurse.Injector/DotNurse.Injector.csproj
@@ -11,6 +11,7 @@
+
diff --git a/src/DotNurse.Injector/DotNurseInjectorContext.cs b/src/DotNurse.Injector/DotNurseInjectorContext.cs
new file mode 100644
index 0000000..1093df3
--- /dev/null
+++ b/src/DotNurse.Injector/DotNurseInjectorContext.cs
@@ -0,0 +1,12 @@
+using DotNurse.Injector.Registration;
+using DotNurse.Injector.Services;
+
+namespace DotNurse.Injector
+{
+ public class DotNurseInjectorContext
+ {
+ public ITypeExplorer TypeExplorer { get; set; } = new DotNurseTypeExplorer();
+
+ public ILazyServiceDescriptorCreator LazyServiceDescriptorCreator { get; set; } = new DotNurseInjectorLazyServiceDescriptorCreator();
+ }
+}
diff --git a/src/DotNurse.Injector/DotNurseInjectorOptions.cs b/src/DotNurse.Injector/DotNurseInjectorOptions.cs
index 5432277..5dd2972 100644
--- a/src/DotNurse.Injector/DotNurseInjectorOptions.cs
+++ b/src/DotNurse.Injector/DotNurseInjectorOptions.cs
@@ -11,6 +11,11 @@ public class DotNurseInjectorOptions
///
public Assembly Assembly { get; set; }
+ ///
+ /// Uses lazy proxy if set true. By default it's .
+ ///
+ public bool UseLazyProxy { get; set; }
+
///
/// Filter objects by name with given algorithm.
///
diff --git a/src/DotNurse.Injector/Registration/DotNurseInjectorLazyServiceDescriptorCreator.cs b/src/DotNurse.Injector/Registration/DotNurseInjectorLazyServiceDescriptorCreator.cs
new file mode 100644
index 0000000..deda927
--- /dev/null
+++ b/src/DotNurse.Injector/Registration/DotNurseInjectorLazyServiceDescriptorCreator.cs
@@ -0,0 +1,19 @@
+using LazyProxy;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+
+namespace DotNurse.Injector.Registration
+{
+ public class DotNurseInjectorLazyServiceDescriptorCreator : ILazyServiceDescriptorCreator
+ {
+ public virtual ServiceDescriptor Create(Type serviceType, Type implementationType, ServiceLifetime lifetime)
+ {
+ var factory = ActivatorUtilities.CreateFactory(implementationType, Array.Empty());
+
+ return new ServiceDescriptor(
+ serviceType,
+ (s) => LazyProxyBuilder.CreateInstance(serviceType, () => factory(s, null)),
+ lifetime);
+ }
+ }
+}
diff --git a/src/DotNurse.Injector/Registration/ILazyServiceRegistrar.cs b/src/DotNurse.Injector/Registration/ILazyServiceRegistrar.cs
new file mode 100644
index 0000000..8ec3dcd
--- /dev/null
+++ b/src/DotNurse.Injector/Registration/ILazyServiceRegistrar.cs
@@ -0,0 +1,10 @@
+using Microsoft.Extensions.DependencyInjection;
+using System;
+
+namespace DotNurse.Injector.Registration
+{
+ public interface ILazyServiceDescriptorCreator
+ {
+ ServiceDescriptor Create(Type serviceType, Type implementationType, ServiceLifetime lifetime);
+ }
+}
diff --git a/src/DotNurse.Injector/Startup.cs b/src/DotNurse.Injector/Startup.cs
index 1d5ab27..f5f2796 100644
--- a/src/DotNurse.Injector/Startup.cs
+++ b/src/DotNurse.Injector/Startup.cs
@@ -1,5 +1,7 @@
using DotNurse.Injector.Attributes;
+using DotNurse.Injector.Registration;
using DotNurse.Injector.Services;
+using LazyProxy;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
@@ -10,7 +12,7 @@ namespace DotNurse.Injector;
public static class Startup
{
- private static ITypeExplorer TypeExplorer { get; } = new DotNurseTypeExplorer();
+ private static DotNurseInjectorContext Context { get; } = new DotNurseInjectorContext();
public static IServiceCollection AddServicesFrom(this IServiceCollection services,
string @namespace,
@@ -20,7 +22,7 @@ public static IServiceCollection AddServicesFrom(this IServiceCollection service
var options = new DotNurseInjectorOptions();
configAction?.Invoke(options);
- var types = TypeExplorer.FindTypesInNamespace(@namespace, options.Assembly);
+ var types = Context.TypeExplorer.FindTypesInNamespace(@namespace, options.Assembly);
services.RegisterTypes(types, defaultLifetime, options);
@@ -35,13 +37,14 @@ public static IServiceCollection AddServicesFrom(this IServiceCollection service
public static IServiceCollection AddServicesByAttributes(
this IServiceCollection services,
ServiceLifetime defaultServiceLifetime = ServiceLifetime.Transient,
+ bool useLazyProxy = false,
Assembly assembly = null)
{
- var types = TypeExplorer.FindTypesWithAttribute(assembly);
+ var types = Context.TypeExplorer.FindTypesWithAttribute(assembly);
foreach (var type in types)
foreach (var injectAsAttribute in type.GetCustomAttributes())
- services.Add(new ServiceDescriptor(injectAsAttribute.ServiceType, type, injectAsAttribute.ServiceLifetime ?? defaultServiceLifetime));
+ services.Add(CreateServiceDescriptor(injectAsAttribute.ServiceType, type, injectAsAttribute.ServiceLifetime ?? defaultServiceLifetime, useLazyProxy));
return services;
}
@@ -55,15 +58,17 @@ public static IServiceCollection AddServicesFrom(
var options = new DotNurseInjectorOptions();
configAction?.Invoke(options);
- var types = TypeExplorer.FindTypesByExpression(expression, options.Assembly);
+ var types = Context.TypeExplorer.FindTypesByExpression(expression, options.Assembly);
services.RegisterTypes(types, defaultServiceLifetime, options);
return services;
}
- public static IServiceCollection AddDotNurseInjector(this IServiceCollection services)
+ public static IServiceCollection AddDotNurseInjector(this IServiceCollection services, Action contextAction = null)
{
- services.AddSingleton();
+ contextAction?.Invoke(Context);
+ services.Add(new ServiceDescriptor(typeof(ITypeExplorer), Context.TypeExplorer.GetType(), ServiceLifetime.Singleton));
+ services.Add(new ServiceDescriptor(typeof(ILazyServiceDescriptorCreator), Context.LazyServiceDescriptorCreator.GetType(), ServiceLifetime.Transient));
return services;
}
@@ -94,12 +99,12 @@ public static IServiceCollection RegisterTypes(
var interfaces = type.GetInterfaces();
- services.Add(new ServiceDescriptor(type, type, lifetime));
+ services.Add(CreateServiceDescriptor(type, type, lifetime, options.UseLazyProxy));
if (interfaces.Length == 1)
{
var inheritFrom = interfaces.FirstOrDefault();
- services.Add(new ServiceDescriptor(inheritFrom, type, lifetime));
+ services.Add(CreateServiceDescriptor(inheritFrom, type, lifetime, options.UseLazyProxy));
continue;
}
@@ -109,17 +114,33 @@ public static IServiceCollection RegisterTypes(
{
foreach (var injectAsAttribute in registerAsAttribute)
if (!services.Any(a => a.ServiceType == injectAsAttribute.ServiceType))
- services.Add(new ServiceDescriptor(injectAsAttribute.ServiceType, type, injectAsAttribute.ServiceLifetime ?? lifetime));
+ services.Add(CreateServiceDescriptor(injectAsAttribute.ServiceType, type, injectAsAttribute.ServiceLifetime ?? lifetime, options.UseLazyProxy));
continue;
}
if (interfaces.Length > 1)
{
- services.Add(new ServiceDescriptor(options.SelectInterface(interfaces), type, lifetime));
+ services.Add(CreateServiceDescriptor(options.SelectInterface(interfaces), type, lifetime, options.UseLazyProxy));
continue;
}
}
return services;
}
+
+ private static ServiceDescriptor CreateServiceDescriptor(
+ Type serviceType,
+ Type implementationType,
+ ServiceLifetime lifetime = ServiceLifetime.Transient,
+ bool withLazyProxy = false)
+ {
+ if (withLazyProxy && serviceType.IsAbstract)
+ {
+ return Context.LazyServiceDescriptorCreator.Create(serviceType, implementationType, lifetime);
+ }
+ else
+ {
+ return new ServiceDescriptor(serviceType, implementationType, lifetime);
+ }
+ }
}
diff --git a/tests/DotNurse.Injector.Tests/StartupTests.cs b/tests/DotNurse.Injector.Tests/StartupTests.cs
index f5d6afe..bed8cc5 100644
--- a/tests/DotNurse.Injector.Tests/StartupTests.cs
+++ b/tests/DotNurse.Injector.Tests/StartupTests.cs
@@ -180,4 +180,56 @@ public void AddServicesFrom_RegisterRecursive_ShouldMatchCount()
Console.WriteLine(services.Count + " >= " + allTypesCount);
Assert.True(services.Count >= types.Count());
}
+
+ [Fact]
+ public void AddServicesFrom_ShouldAddCorrectCount_WithSingleInheritanceWithLazyProxy()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var nameSpace = "DotNurse.Injector.Tests.Environment.NamespaceSingle";
+
+ // Act
+ services.AddServicesFrom(nameSpace, configAction: opts => opts.UseLazyProxy = true);
+
+ // Assert
+ Assert.Contains(services, x => x.ImplementationType == null && x.ImplementationFactory != null);
+ }
+
+ [Fact]
+ public void AddServicesByAttributes_ShouldAddImplementationFactoryForAbstracts_WithLazyProxy()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+
+ // Registered Services: typeof(IComputer), typeof(ILaptop), typeof(MyLaptop)
+
+ // Act
+ services.AddServicesByAttributes(useLazyProxy: true);
+
+ // Assert
+ var serviceComputer = services.FirstOrDefault(x => x.ServiceType == typeof(IComputer));
+
+ Assert.Null(serviceComputer.ImplementationType);
+ Assert.NotNull(serviceComputer.ImplementationFactory);
+ }
+
+ [Fact]
+ public void AddServicesByAttributes_ShouldAddImplementationTypeForNonAbstracts_WithLazyProxy()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+
+ // Registered Services: typeof(IComputer), typeof(ILaptop), typeof(MyLaptop)
+
+ // Act
+ services.AddServicesByAttributes(useLazyProxy: true);
+
+ // Assert
+ Assert.Contains(services, x => x.ImplementationType == typeof(MyLaptop) && x.ServiceType == typeof(MyLaptop));
+
+ var serviceComputer = services.FirstOrDefault(x => x.ImplementationType == typeof(MyLaptop));
+
+ Assert.Null(serviceComputer.ImplementationFactory);
+ Assert.NotNull(serviceComputer.ImplementationType);
+ }
}