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); + } }