From f5806378eb44c486705173ff96494772f4907338 Mon Sep 17 00:00:00 2001 From: dadhi Date: Mon, 3 Mar 2025 12:51:58 +0100 Subject: [PATCH] @wip #679 basic RegisterAttribute and its basic registration implementation --- src/DryIoc/Container.cs | 183 +++++++++++++++--- test/DryIoc.TestRunner/Program.cs | 2 + .../RegisterAttributeTests.cs | 42 ++++ test/DryIoc.UnitTests/RulesTests.cs | 2 - 4 files changed, 197 insertions(+), 32 deletions(-) create mode 100644 test/DryIoc.UnitTests/RegisterAttributeTests.cs diff --git a/src/DryIoc/Container.cs b/src/DryIoc/Container.cs index b563556e3..aae8a81d1 100644 --- a/src/DryIoc/Container.cs +++ b/src/DryIoc/Container.cs @@ -9435,6 +9435,62 @@ public static void RegisterMapping(this IContainer IfAlreadyRegistered ifAlreadyRegistered, object serviceKey = null, object registeredServiceKey = null) => Registrator.RegisterMapping(container, typeof(TService), typeof(TRegisteredService), ifAlreadyRegistered, serviceKey, registeredServiceKey); + + /// Returns the number of the actual registrations made. + public static int RegisterByRegisterAttributes(this IRegistrator registrator, Type registrationAttributesTarget) + { + var attrs = registrationAttributesTarget.GetCustomAttributes(inherit: true); + var regCount = 0; + foreach (var attr in attrs) + { + var factory = CreateFactory(attr); + + registrator.Register(factory, attr.ServiceType, attr.ServiceKey, attr.IfAlreadyRegistered, + isStaticallyChecked: attr.TypesAreStaticallyChecked); + + ++regCount; + } + return regCount; + } + + internal static ReflectionFactory CreateFactory(RegisterAttribute attr) + { + var reuse = GetReuse(attr); + var made = Made.Default; // todo: @wip add features later + var setup = GetSetup(attr); + return ReflectionFactory.Of(attr.ImplementationType, reuse, made, setup); + } + + internal static IReuse GetReuse(RegisterAttribute attr) => + attr.ReuseType switch + { + var t when t == typeof(TransientReuse) => Reuse.Transient, + var t when t == typeof(SingletonReuse) => Reuse.Singleton, + var t when t == typeof(CurrentScopeReuse) => + attr.ReuseScopeName == null & attr.ReuseScopeNames == null ? Reuse.Scoped : + attr.ReuseScopeName != null ? Reuse.ScopedTo(attr.ReuseScopeName) : Reuse.ScopedTo(attr.ReuseScopeNames), + // todo: @wip support this stuff later + // ScopedToServiceType; + // ScopedToServiceKey; + // ScopedOrSingleton; + var t => Throw.For(Error.RegisterAttributedUnsupportedReuseType, t) + }; + + internal static Setup GetSetup(RegisterAttribute attr) + { + // todo: @wip support other stuff later + var transientTracking = attr.TrackDisposableTransient; + if (transientTracking == DisposableTracking.TrackDisposableTransient) + return Setup.With(trackDisposableTransient: true); + + if (transientTracking == DisposableTracking.AllowDisposableTransient) + return Setup.With(allowDisposableTransient: true); + + if (transientTracking == DisposableTracking.ThrowOnRegisteringDisposableTransient) + return Setup.With(allowDisposableTransient: false); + + return Setup.Default; + } } /// Extension methods for . @@ -14573,6 +14629,33 @@ public interface IReuse : IConvertibleToExpression Expression Apply(Request request, Expression serviceFactoryExpr); } +/// Transient reuse +public sealed class TransientReuse : IReuse +{ + /// + public int Lifespan => 0; + + /// + public object Name => null; + + /// + public Expression Apply(Request _, Expression serviceFactoryExpr) => serviceFactoryExpr; + + /// + public bool CanApply(Request request) => true; + + private readonly Lazy _transientReuseExpr = Lazy.Of(() => + Field(null, typeof(Reuse).GetField(nameof(Reuse.Transient)))); + + /// + public Expression ToExpression(S state, Func fallbackConverter) => + _transientReuseExpr.Value; + + /// + public override string ToString() => "TransientReuse"; +} + + /// Returns container bound scope for storing singleton objects. public sealed class SingletonReuse : IReuse { @@ -15112,30 +15195,9 @@ public static IReuse ScopedToService(object serviceKey = null) => /// Obsolete: please prefer using `Scoped` without name instead. /// The usage of the named scopes is the less performant than the unnamed ones. e.g. ASP.NET Core does not use the named scope. public static readonly IReuse InWebRequest = ScopedTo(WebRequestScopeName); +} - #region Implementation - - private sealed class TransientReuse : IReuse - { - public int Lifespan => 0; - - public object Name => null; - - public Expression Apply(Request _, Expression serviceFactoryExpr) => serviceFactoryExpr; - - public bool CanApply(Request request) => true; - - private readonly Lazy _transientReuseExpr = Lazy.Of(() => - Field(null, typeof(Reuse).GetField(nameof(Transient)))); - - public Expression ToExpression(S state, Func fallbackConverter) => - _transientReuseExpr.Value; - public override string ToString() => "TransientReuse"; - } - - #endregion -} /// Policy to handle unresolved service. public enum IfUnresolved : byte @@ -15871,7 +15933,8 @@ public static readonly int "service creation is failed with the exception and the exception was catched, but you're trying to resolve the failed service again. " + NewLine + "For all those reasons DryIoc has a timeout to prevent the infinite waiting. " + NewLine + $"You may change the default timeout via setting the static `Scope.{nameof(Scope.WaitForScopedServiceIsCreatedTimeoutMilliseconds)}`"), - ServiceTypeIsNull = Of("Registered service type is null"); + ServiceTypeIsNull = Of("Registered service type is null"), + RegisterAttributedUnsupportedReuseType = Of("Not support reuse type {0} in the RegisterAttribute."); #pragma warning restore 1591 // "Missing XML-comment" @@ -16691,15 +16754,75 @@ public class CompileTimeRegisterAttribute : Attribute { } -[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] -internal class RegisterAttribute : Attribute // todo: @wip make it public +/// Per-registration rules to handle the disposable transient +public enum DisposableTracking : byte +{ + /// Default behavior is to obey the container rules + UseContainerRules = 0, + /// Throws the Container exception when trying to register a disposable transient, overriding the container rules + ThrowOnRegisteringDisposableTransient, + /// Allow registering the disposable transient + AllowDisposableTransient, + /// Track disposable transient + TrackDisposableTransient +} + +/// Base registration attribute, +/// there may be descendant predefined and custom attributes simplifying the registration setup. +public class RegisterAttribute : Attribute { - // public Type ServiceType; + /// By default no, because the attribute operates on the + public virtual bool TypesAreStaticallyChecked => false; - // todo: @feature @wip implement the Register in the attribute - // void Register(Factory factory, Type serviceType, object serviceKey, IfAlreadyRegistered? ifAlreadyRegistered, bool isStaticallyChecked); - // public static ReflectionFactory Of(Type implementationType = null, IReuse reuse = null, Made made = null, Setup setup = null) - // todo: add RegisterMany + /// A service type + public Type ServiceType; + + /// An implementation type + public Type ImplementationType; + + /// One of the IReuse implementation types. + public Type ReuseType; // todo: @wip change to the ScopeType, move it from the AttributedModel + + /// Optional name of the bound scope for the Scoped reuse + public string ReuseScopeName; + + /// Optional names of the bound scopes fore the Scoped reuse. Maybe overridden by + public object[] ReuseScopeNames; + + public Type ScopedToServiceType; + public Type ScopedToServiceKey; + public bool ScopedOrSingleton; + + /// A service key + public object ServiceKey; + + /// Uses the global container rules by default + public IfAlreadyRegistered? IfAlreadyRegistered; + + // public Made Made; + + /// How to track the disposable transient + public DisposableTracking TrackDisposableTransient; +} + +// todo: @feature @wip implement the Register in the attribute +// void Register(Factory factory, Type serviceType, object serviceKey, IfAlreadyRegistered? ifAlreadyRegistered, bool isStaticallyChecked); +// public static ReflectionFactory Of(Type implementationType = null, IReuse reuse = null, Made made = null, Setup setup = null) +// todo: add RegisterMany +/// A single registration attribute +public sealed class RegisterAttribute : RegisterAttribute + where TReuse : IReuse +{ + /// In this case there are compile-time type constraints for the Implementation and Service types + public override bool TypesAreStaticallyChecked => true; + + /// Creates the registration attribute + public RegisterAttribute() + { + ServiceType = typeof(TService); + ImplementationType = typeof(TImplementation); + ReuseType = typeof(TReuse); + } } /// Ports some methods from .Net 4.0/4.5 diff --git a/test/DryIoc.TestRunner/Program.cs b/test/DryIoc.TestRunner/Program.cs index ef6884ad4..5a186dbc9 100644 --- a/test/DryIoc.TestRunner/Program.cs +++ b/test/DryIoc.TestRunner/Program.cs @@ -9,6 +9,8 @@ public class Program { public static void Main() { + new RegisterAttributeTests().Run(); + // new RulesTests().Run(); // new GHIssue672_Wrong_decorator_parameter_with_custom_args().Run(); diff --git a/test/DryIoc.UnitTests/RegisterAttributeTests.cs b/test/DryIoc.UnitTests/RegisterAttributeTests.cs new file mode 100644 index 000000000..9eb8a7799 --- /dev/null +++ b/test/DryIoc.UnitTests/RegisterAttributeTests.cs @@ -0,0 +1,42 @@ +using System; +using NUnit.Framework; + +namespace DryIoc.UnitTests +{ + [TestFixture] + public class RegisterAttributeTests : ITest + { + public int Run() + { + Can_register_service_with_tracking_disposable_reuse(); + + return 1; + } + + [Test] + public void Can_register_service_with_tracking_disposable_reuse() + { + var c = new Container(); + + var count = c.RegisterByRegisterAttributes(typeof(Registrations)); + Assert.AreEqual(1, count); + + var ad = c.Resolve(); + Assert.IsNotNull(ad); + + c.Dispose(); + Assert.IsTrue(((AD)ad).IsDisposed); + } + + [Register(TrackDisposableTransient = DisposableTracking.TrackDisposableTransient)] + public static class Registrations { } + + public interface ID { } + + class AD : ID, IDisposable + { + public bool IsDisposed; + public void Dispose() => IsDisposed = true; + } + } +} diff --git a/test/DryIoc.UnitTests/RulesTests.cs b/test/DryIoc.UnitTests/RulesTests.cs index 258d48ca3..9e1d453d5 100644 --- a/test/DryIoc.UnitTests/RulesTests.cs +++ b/test/DryIoc.UnitTests/RulesTests.cs @@ -14,8 +14,6 @@ public class RulesTests : ITest { public int Run() { - I_can_override_global_disposable_transient_allowance_and_prohibit_its_registration_despite_local_setting(); - Given_service_with_two_ctors_I_can_specify_what_ctor_to_choose_for_resolve(); I_should_be_able_to_add_rule_to_resolve_not_registered_service(); I_can_remove_rule_to_resolve_not_registered_service();