From 51808c8804717d670e93f66f83dda10c57010ae0 Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Fri, 16 May 2025 18:14:13 +0200 Subject: [PATCH 01/15] Added AOT attributes to Activator types. --- .../Activator/ActivatorExtensions.cs | 5 ++++- .../Activator/DefaultActivator.cs | 5 ++++- .../Activator/IActivator.cs | 5 ++++- .../Activator/ServiceProviderActivator.cs | 5 ++++- DependencyInjection/src/Directory.Build.props | 1 + 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Activator/ActivatorExtensions.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Activator/ActivatorExtensions.cs index 9810b12..c3f56c2 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Activator/ActivatorExtensions.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Activator/ActivatorExtensions.cs @@ -2,6 +2,7 @@ // Copyright (c) The AppCore .NET project. using System; +using System.Diagnostics.CodeAnalysis; using AppCoreNet.Diagnostics; namespace AppCoreNet.Extensions.DependencyInjection.Activator; @@ -19,7 +20,9 @@ public static class ActivatorExtensions /// Constructor arguments not provided by the . /// An activated object of type . /// Argument is null. - public static T? CreateInstance(this IActivator activator, params object[] parameters) + public static T? CreateInstance<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( + this IActivator activator, + params object[] parameters) { Ensure.Arg.NotNull(activator); return (T?)activator.CreateInstance(typeof(T), parameters); diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Activator/DefaultActivator.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Activator/DefaultActivator.cs index 8920cbb..a456605 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Activator/DefaultActivator.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Activator/DefaultActivator.cs @@ -2,6 +2,7 @@ // Copyright (c) The AppCore .NET project. using System; +using System.Diagnostics.CodeAnalysis; using AppCoreNet.Diagnostics; namespace AppCoreNet.Extensions.DependencyInjection.Activator; @@ -17,7 +18,9 @@ public sealed class DefaultActivator : IActivator public static DefaultActivator Instance { get; } = new(); /// - public object? CreateInstance(Type instanceType, params object[] parameters) + public object? CreateInstance( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType, + params object[] parameters) { Ensure.Arg.NotNull(instanceType); Ensure.Arg.NotNull(parameters); diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Activator/IActivator.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Activator/IActivator.cs index 9b6963f..bb7f7f4 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Activator/IActivator.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Activator/IActivator.cs @@ -2,6 +2,7 @@ // Copyright (c) The AppCore .NET project. using System; +using System.Diagnostics.CodeAnalysis; namespace AppCoreNet.Extensions.DependencyInjection.Activator; @@ -17,5 +18,7 @@ public interface IActivator /// Constructor arguments not provided by the . /// An activated object of type . /// Argument or is null. - object? CreateInstance(Type instanceType, params object[] parameters); + object? CreateInstance( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType, + params object[] parameters); } \ No newline at end of file diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Activator/ServiceProviderActivator.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Activator/ServiceProviderActivator.cs index 6ec0369..6adf569 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Activator/ServiceProviderActivator.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Activator/ServiceProviderActivator.cs @@ -2,6 +2,7 @@ // Copyright (c) The AppCore .NET project. using System; +using System.Diagnostics.CodeAnalysis; using AppCoreNet.Diagnostics; using Microsoft.Extensions.DependencyInjection; @@ -26,7 +27,9 @@ public ServiceProviderActivator(IServiceProvider serviceProvider) } /// - public object? CreateInstance(Type instanceType, params object[] parameters) + public object? CreateInstance( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType, + params object[] parameters) { return ActivatorUtilities.CreateInstance(_serviceProvider, instanceType, parameters); } diff --git a/DependencyInjection/src/Directory.Build.props b/DependencyInjection/src/Directory.Build.props index 53d9690..614e662 100644 --- a/DependencyInjection/src/Directory.Build.props +++ b/DependencyInjection/src/Directory.Build.props @@ -3,6 +3,7 @@ true + true From e1f1078a0439b0c3ad4bb3d598244a23445bb8d7 Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Fri, 16 May 2025 18:14:43 +0200 Subject: [PATCH 02/15] Move test types to static class. --- .../AssemblyScannerTests.cs | 1 + ...AssemblyServicesDescriptorResolverTests.cs | 1 + .../ContractImpl1.cs | 20 ------- .../ContractImpl2.cs | 16 ------ .../ContractImplBase.cs | 16 ------ .../IContract.cs | 12 ---- .../TestContracts.cs | 55 +++++++++++++++++++ 7 files changed, 57 insertions(+), 64 deletions(-) delete mode 100644 DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/ContractImpl1.cs delete mode 100644 DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/ContractImpl2.cs delete mode 100644 DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/ContractImplBase.cs delete mode 100644 DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/IContract.cs create mode 100644 DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/TestContracts.cs diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyScannerTests.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyScannerTests.cs index fe43d80..6e7ba3d 100644 --- a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyScannerTests.cs +++ b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyScannerTests.cs @@ -4,6 +4,7 @@ using System.Reflection; using FluentAssertions; using Xunit; +using static AppCoreNet.Extensions.DependencyInjection.TestContracts; namespace AppCoreNet.Extensions.DependencyInjection; diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyServicesDescriptorResolverTests.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyServicesDescriptorResolverTests.cs index 0daa453..63b35b0 100644 --- a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyServicesDescriptorResolverTests.cs +++ b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyServicesDescriptorResolverTests.cs @@ -6,6 +6,7 @@ using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Xunit; +using static AppCoreNet.Extensions.DependencyInjection.TestContracts; namespace AppCoreNet.Extensions.DependencyInjection; diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/ContractImpl1.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/ContractImpl1.cs deleted file mode 100644 index ab5a01c..0000000 --- a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/ContractImpl1.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed under the MIT license. -// Copyright (c) The AppCore .NET project. - -namespace AppCoreNet.Extensions.DependencyInjection; - -public class ContractImplOpenGeneric : IContract -{ -} - -public class ContractImpl1 : ContractImplBase -{ -} - -public class ContractImpl1 : ContractImplBase -{ -} - -public class ContractImpl1String : ContractImplBaseString -{ -} \ No newline at end of file diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/ContractImpl2.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/ContractImpl2.cs deleted file mode 100644 index 7632e80..0000000 --- a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/ContractImpl2.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed under the MIT license. -// Copyright (c) The AppCore .NET project. - -namespace AppCoreNet.Extensions.DependencyInjection; - -public class ContractImpl2 : ContractImplBase -{ -} - -public class ContractImpl2 : ContractImplBase -{ -} - -public class ContractImpl2String : ContractImplBaseString -{ -} \ No newline at end of file diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/ContractImplBase.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/ContractImplBase.cs deleted file mode 100644 index ea16478..0000000 --- a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/ContractImplBase.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed under the MIT license. -// Copyright (c) The AppCore .NET project. - -namespace AppCoreNet.Extensions.DependencyInjection; - -public abstract class ContractImplBase : IContract -{ -} - -public abstract class ContractImplBase : IContract -{ -} - -public abstract class ContractImplBaseString : ContractImplBase -{ -} \ No newline at end of file diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/IContract.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/IContract.cs deleted file mode 100644 index cab8f6d..0000000 --- a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/IContract.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Licensed under the MIT license. -// Copyright (c) The AppCore .NET project. - -namespace AppCoreNet.Extensions.DependencyInjection; - -public interface IContract -{ -} - -public interface IContract -{ -} \ No newline at end of file diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/TestContracts.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/TestContracts.cs new file mode 100644 index 0000000..475a504 --- /dev/null +++ b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/TestContracts.cs @@ -0,0 +1,55 @@ +// Licensed under the MIT license. +// Copyright (c) The AppCore .NET project. + +namespace AppCoreNet.Extensions.DependencyInjection; + +public static class TestContracts +{ + public interface IContract + { + } + + public interface IContract + { + } + + public class ContractImplOpenGeneric : IContract + { + } + + public class ContractImpl1 : ContractImplBase + { + } + + public class ContractImpl1 : ContractImplBase + { + } + + public class ContractImpl1String : ContractImplBaseString + { + } + + public class ContractImpl2 : ContractImplBase + { + } + + public class ContractImpl2 : ContractImplBase + { + } + + public class ContractImpl2String : ContractImplBaseString + { + } + + public abstract class ContractImplBase : IContract + { + } + + public abstract class ContractImplBase : IContract + { + } + + public abstract class ContractImplBaseString : ContractImplBase + { + } +} \ No newline at end of file From 801cede33171b424a3294c47d04d7d82594084b5 Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Fri, 16 May 2025 18:20:02 +0200 Subject: [PATCH 03/15] Added AOT attributes to dynamic service registration. --- .../FacilityServiceCollectionExtensions.cs | 16 ++-- .../IServiceDescriptorReflectionBuilder.cs | 4 +- .../ReflectionServiceCollectionExtensions.cs | 1 + .../ServiceCollectionServiceProvider.cs | 87 ++++++++++++++----- .../ServiceDescriptorReflectionBuilder.cs | 5 +- 5 files changed, 85 insertions(+), 28 deletions(-) diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/FacilityServiceCollectionExtensions.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/FacilityServiceCollectionExtensions.cs index 3850e2a..019d072 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/FacilityServiceCollectionExtensions.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/FacilityServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using AppCoreNet.Diagnostics; using AppCoreNet.Extensions.DependencyInjection.Activator; using AppCoreNet.Extensions.DependencyInjection.Facilities; @@ -24,7 +25,10 @@ public static class FacilityServiceCollectionExtensions /// The delegate to configure the facility. /// The to allow chaining. /// Argument is null. - public static IServiceCollection AddFacility(this IServiceCollection services, Action>? configure = null) + public static IServiceCollection AddFacility< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( + this IServiceCollection services, + Action>? configure = null) where T : IFacility { Ensure.Arg.NotNull(services); @@ -56,12 +60,12 @@ public static IServiceCollection AddFacility(this IServiceCollection services /// Argument is not of type . public static IServiceCollection AddFacility( this IServiceCollection services, - Type facilityType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type facilityType, Action? configure = null) { Ensure.Arg.NotNull(services); Ensure.Arg.NotNull(facilityType); - Ensure.Arg.OfType(facilityType, typeof(IFacility)); + Ensure.Arg.OfType(facilityType); services.TryAddTransient(); @@ -100,8 +104,10 @@ public static IServiceCollection AddFacilitiesFrom( configure(builder); - foreach ((IFacility Facility, IReadOnlyCollection> FacilityExtensions) item in builder - .Resolve()) + IReadOnlyCollection<(IFacility Facility, IReadOnlyCollection> FacilityExtensions)> + facilities = builder.Resolve(); + + foreach ((IFacility Facility, IReadOnlyCollection> FacilityExtensions) item in facilities) { item.Facility.ConfigureServices(services); diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/IServiceDescriptorReflectionBuilder.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/IServiceDescriptorReflectionBuilder.cs index 8da922a..e09a342 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/IServiceDescriptorReflectionBuilder.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/IServiceDescriptorReflectionBuilder.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; namespace AppCoreNet.Extensions.DependencyInjection; @@ -35,6 +36,7 @@ public interface IServiceDescriptorReflectionBuilder /// The configuration delegate. /// The to allow chaining. [EditorBrowsable(EditorBrowsableState.Advanced)] - public IServiceDescriptorReflectionBuilder AddResolver(Action? configure = null) + public IServiceDescriptorReflectionBuilder AddResolver< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(Action? configure = null) where T : IServiceDescriptorResolver; } \ No newline at end of file diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/ReflectionServiceCollectionExtensions.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/ReflectionServiceCollectionExtensions.cs index d53f551..787c95c 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/ReflectionServiceCollectionExtensions.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/ReflectionServiceCollectionExtensions.cs @@ -2,6 +2,7 @@ // Copyright (c) The AppCore .NET project. using System; +using System.Diagnostics.CodeAnalysis; using AppCoreNet.Diagnostics; using AppCoreNet.Extensions.DependencyInjection.Activator; using Microsoft.Extensions.DependencyInjection; diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/ServiceCollectionServiceProvider.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/ServiceCollectionServiceProvider.cs index 747e303..e0ab31e 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/ServiceCollectionServiceProvider.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/ServiceCollectionServiceProvider.cs @@ -2,9 +2,10 @@ // Copyright (c) The AppCore .NET project. using System; -using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; namespace AppCoreNet.Extensions.DependencyInjection; @@ -12,11 +13,18 @@ namespace AppCoreNet.Extensions.DependencyInjection; /// /// Internal service provider which resolves services from an . /// -internal sealed partial class ServiceCollectionServiceProvider : IServiceProvider, IServiceProviderIsService +internal sealed class ServiceCollectionServiceProvider : IServiceProvider, IServiceProviderIsService { private readonly IServiceCollection _services; private readonly Dictionary _additionalServices = new(); + internal static bool VerifyAotCompatibility => +#if NETFRAMEWORK || NETSTANDARD2_0 + false; +#else + !RuntimeFeature.IsDynamicCodeSupported; +#endif + public ServiceCollectionServiceProvider(IServiceCollection services) { _services = services; @@ -27,6 +35,19 @@ public void AddService(Type serviceType, object instance) _additionalServices.Add(serviceType, instance); } + private static bool IsEnumerable(Type serviceType) + { + return serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>); + } + + private static bool IsServiceType(Type serviceType, Type requestedServiceType) + { + return serviceType == requestedServiceType + || (requestedServiceType.IsGenericType + && serviceType.IsGenericTypeDefinition + && serviceType == requestedServiceType.GetGenericTypeDefinition()); + } + public bool IsService(Type serviceType) { if (serviceType == typeof(IServiceProvider) || serviceType == typeof(IServiceProviderIsService)) @@ -35,17 +56,26 @@ public bool IsService(Type serviceType) if (_additionalServices.TryGetValue(serviceType, out object? _)) return true; - if (serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + if (IsEnumerable(serviceType)) return true; - return _services.Any( - sd => sd.ServiceType == serviceType - || (serviceType.IsGenericType - && sd.ServiceType == serviceType.GetGenericTypeDefinition())); + return _services.Any(sd => IsServiceType(sd.ServiceType, serviceType)); } - private IEnumerable GetServices(Type serviceType) + private object[] GetServices(Type serviceType) { + [UnconditionalSuppressMessage( + "AotAnalysis", + "IL3050:RequiresDynamicCode", + Justification = "VerifyAotCompatibility ensures that dynamic code supported")] + static Type MakeGenericType(Type type, params Type[] typeArguments) + { + if (VerifyAotCompatibility) + throw new InvalidOperationException("Cannot build generic type when using AOT."); + + return type.MakeGenericType(typeArguments); + } + object ServiceFactory(ServiceDescriptor serviceDescriptor) { object? instance = serviceDescriptor.ImplementationInstance; @@ -57,8 +87,8 @@ object ServiceFactory(ServiceDescriptor serviceDescriptor) if (instance == null && serviceDescriptor.ImplementationType != null) { Type implementationType = serviceDescriptor.ImplementationType!; - if (implementationType.IsGenericType) - implementationType = implementationType.MakeGenericType(serviceType.GenericTypeArguments); + if (implementationType.IsGenericTypeDefinition) + implementationType = MakeGenericType(implementationType, serviceType.GenericTypeArguments); instance = ActivatorUtilities.CreateInstance(this, implementationType); } @@ -66,11 +96,9 @@ object ServiceFactory(ServiceDescriptor serviceDescriptor) return instance!; } - return _services.Where( - sd => sd.ServiceType == serviceType - || (serviceType.IsGenericType - && sd.ServiceType == serviceType.GetGenericTypeDefinition())) - .Select(ServiceFactory); + return _services.Where(sd => IsServiceType(sd.ServiceType, serviceType)) + .Select(ServiceFactory) + .ToArray(); } public object? GetService(Type serviceType) @@ -81,22 +109,39 @@ object ServiceFactory(ServiceDescriptor serviceDescriptor) if (_additionalServices.TryGetValue(serviceType, out object? instance)) return instance; - if (serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + if (IsEnumerable(serviceType)) { serviceType = serviceType.GenericTypeArguments[0]; - var instances = (IList)System.Activator.CreateInstance(typeof(List<>).MakeGenericType(serviceType))!; - foreach (object service in GetServices(serviceType)) + object[] services = GetServices(serviceType); + + Array result = CreateArray(serviceType, services.Length); + for (int i = 0; i < services.Length; i++) { - instances.Add(service); + result.SetValue(services[i], i); } - instance = instances; + instance = result; } else { - instance = GetServices(serviceType).FirstOrDefault(); + object[] services = GetServices(serviceType); + instance = services.Length > 0 + ? services[0] + : null; } return instance; + + [UnconditionalSuppressMessage( + "AotAnalysis", + "IL3050:RequiresDynamicCode", + Justification = "VerifyAotCompatibility ensures elementType is not a ValueType")] + static Array CreateArray(Type elementType, int length) + { + if (VerifyAotCompatibility && elementType.IsValueType) + throw new InvalidOperationException("Cannot build array of value service types."); + + return Array.CreateInstance(elementType, length); + } } } \ No newline at end of file diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/ServiceDescriptorReflectionBuilder.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/ServiceDescriptorReflectionBuilder.cs index 29606f5..bcba7ca 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/ServiceDescriptorReflectionBuilder.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/ServiceDescriptorReflectionBuilder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using AppCoreNet.Diagnostics; using AppCoreNet.Extensions.DependencyInjection.Activator; @@ -42,7 +43,9 @@ public IServiceDescriptorReflectionBuilder AddResolver(IServiceDescriptorResolve } [EditorBrowsable(EditorBrowsableState.Advanced)] - public IServiceDescriptorReflectionBuilder AddResolver(Action? configure = null) + public IServiceDescriptorReflectionBuilder AddResolver< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + T>(Action? configure = null) where T : IServiceDescriptorResolver { var source = _activator.CreateInstance()!; From 4c5bc7ac3f6b2d282bc68d36ef38ffd523e0181d Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Fri, 16 May 2025 20:21:42 +0200 Subject: [PATCH 04/15] Added AOT compatibility to facilities. --- .../Facilities/FacilityBuilder.cs | 30 ++++--------- .../Facilities/FacilityBuilder{T}.cs | 44 +++++++++++++++---- .../FacilityExtensionReflectionBuilder.cs | 12 +++-- .../Facilities/FacilityExtensionWrapper.cs | 22 ---------- .../Facilities/FacilityReflectionBuilder.cs | 14 +++--- .../Facilities/IFacilityExtension.cs | 13 ++++-- .../IFacilityExtensionReflectionBuilder.cs | 4 +- .../Facilities/IFacilityExtensionResolver.cs | 2 +- .../Facilities/IFacilityReflectionBuilder.cs | 4 +- .../FacilityServiceCollectionExtensions.cs | 6 +-- 10 files changed, 77 insertions(+), 74 deletions(-) delete mode 100644 DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityExtensionWrapper.cs diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityBuilder.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityBuilder.cs index 89ff314..e98604a 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityBuilder.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityBuilder.cs @@ -2,7 +2,7 @@ // Copyright (c) The AppCore .NET project. using System; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using AppCoreNet.Diagnostics; using AppCoreNet.Extensions.DependencyInjection.Activator; using Microsoft.Extensions.DependencyInjection; @@ -12,7 +12,7 @@ namespace AppCoreNet.Extensions.DependencyInjection.Facilities; /// /// Provides a builder for facilities. /// -public class FacilityBuilder +public sealed class FacilityBuilder { /// /// Gets the . @@ -36,35 +36,23 @@ internal FacilityBuilder(IServiceCollection services, IActivator activator, Type FacilityType = facilityType; } - private IFacilityExtension CreateExtension(Type extensionType) - { - Type contractType = extensionType.GetInterfaces() - .First(i => i.GetGenericTypeDefinition() == typeof(IFacilityExtension<>)) - .GenericTypeArguments[0]; - - Type extensionWrapperType = typeof(FacilityExtensionWrapper<>).MakeGenericType(contractType); - object extension = Activator.CreateInstance(extensionType)!; - - return (IFacilityExtension)System.Activator.CreateInstance(extensionWrapperType, extension)!; - } - /// /// Adds an extension to the facility. /// /// - /// The type must implement with the - /// type of the facility. + /// The type must implement . /// /// The type of the extension. /// The to allow chaining. /// Argument is null. - /// Argument does not implement with the type of the facility. - public FacilityBuilder AddExtension(Type extensionType) + /// Argument does not implement with the type of the facility. + public FacilityBuilder AddExtension( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type extensionType) { Ensure.Arg.NotNull(extensionType); - Ensure.Arg.OfType(extensionType, typeof(IFacilityExtension<>).MakeGenericType(FacilityType)); + Ensure.Arg.OfType(extensionType); - IFacilityExtension extension = CreateExtension(extensionType); + var extension = (IFacilityExtension)Activator.CreateInstance(extensionType)!; extension.ConfigureServices(Services); return this; } @@ -82,7 +70,7 @@ public FacilityBuilder AddExtensionsFrom(Action extension in reflectionBuilder.Resolve(FacilityType)) + foreach (IFacilityExtension extension in reflectionBuilder.Resolve(FacilityType)) { extension.ConfigureServices(Services); } diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityBuilder{T}.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityBuilder{T}.cs index 15f0017..eaccf86 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityBuilder{T}.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityBuilder{T}.cs @@ -2,7 +2,7 @@ // Copyright (c) The AppCore .NET project. using System; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using AppCoreNet.Diagnostics; using AppCoreNet.Extensions.DependencyInjection.Activator; using Microsoft.Extensions.DependencyInjection; @@ -13,12 +13,23 @@ namespace AppCoreNet.Extensions.DependencyInjection.Facilities; /// Provides a builder for facilities. /// /// The type of the facility. -public sealed class FacilityBuilder : FacilityBuilder +public sealed class FacilityBuilder where T : IFacility { + /// + /// Gets the . + /// + public IServiceCollection Services { get; } + + /// + /// Gets the . + /// + public IActivator Activator { get; } + internal FacilityBuilder(IServiceCollection services, IActivator activator) - : base(services, activator, typeof(T)) { + Services = services; + Activator = activator; } /// @@ -32,9 +43,13 @@ internal FacilityBuilder(IServiceCollection services, IActivator activator) /// The to allow chaining. /// Argument is null. /// Argument does not implement with the type of the facility. - public new FacilityBuilder AddExtension(Type extensionType) + public FacilityBuilder AddExtension( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type extensionType) { - base.AddExtension(extensionType); + Ensure.Arg.OfType>(extensionType); + + var extension = (IFacilityExtension)Activator.CreateInstance(extensionType)!; + extension.ConfigureServices(Services); return this; } @@ -43,7 +58,8 @@ internal FacilityBuilder(IServiceCollection services, IActivator activator) /// /// The type of the extension. /// The to allow chaining. - public FacilityBuilder AddExtension() + public FacilityBuilder AddExtension< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TExtension>() where TExtension : class, IFacilityExtension { IFacilityExtension extension = Activator.CreateInstance()!; @@ -65,14 +81,24 @@ public FacilityBuilder AddExtension(IFacilityExtension extension) } /// - /// Adds facility extensions using a to the . + /// Adds facility extensions using a to the + /// . /// /// The delegate used to configure the facility extension resolvers. /// The . /// Argument is null. - public new FacilityBuilder AddExtensionsFrom(Action configure) + public FacilityBuilder AddExtensionsFrom(Action configure) { - base.AddExtensionsFrom(configure); + Ensure.Arg.NotNull(configure); + + var reflectionBuilder = new FacilityExtensionReflectionBuilder(Activator); + configure(reflectionBuilder); + + foreach (IFacilityExtension extension in reflectionBuilder.Resolve(typeof(T))) + { + extension.ConfigureServices(Services); + } + return this; } } \ No newline at end of file diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityExtensionReflectionBuilder.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityExtensionReflectionBuilder.cs index 07d2f43..32fa414 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityExtensionReflectionBuilder.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityExtensionReflectionBuilder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using AppCoreNet.Diagnostics; using AppCoreNet.Extensions.DependencyInjection.Activator; @@ -26,7 +27,8 @@ public IFacilityExtensionReflectionBuilder AddResolver(IFacilityExtensionResolve return this; } - public IFacilityExtensionReflectionBuilder AddResolver(Action? configure = null) + public IFacilityExtensionReflectionBuilder AddResolver< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(Action? configure = null) where T : IFacilityExtensionResolver { var resolver = _activator.CreateInstance()!; @@ -34,13 +36,9 @@ public IFacilityExtensionReflectionBuilder AddResolver(Action? configure = return AddResolver(resolver); } - public IReadOnlyCollection> Resolve(Type facilityType) + public IReadOnlyCollection Resolve(Type facilityType) { - Type[] facilityTypes = facilityType.GetTypesAssignableFrom() - .Where(t => t.GetInterfaces().Contains(typeof(IFacility))) - .ToArray(); - - return _resolvers.SelectMany(s => facilityTypes.SelectMany(s.Resolve)) + return _resolvers.SelectMany(s => s.Resolve(facilityType)) .ToList() .AsReadOnly(); } diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityExtensionWrapper.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityExtensionWrapper.cs deleted file mode 100644 index 57df949..0000000 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityExtensionWrapper.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed under the MIT license. -// Copyright (c) The AppCore .NET project. - -using Microsoft.Extensions.DependencyInjection; - -namespace AppCoreNet.Extensions.DependencyInjection.Facilities; - -internal sealed class FacilityExtensionWrapper : IFacilityExtension - where TContract : IFacility -{ - private readonly IFacilityExtension _extension; - - public FacilityExtensionWrapper(IFacilityExtension extension) - { - _extension = extension; - } - - public void ConfigureServices(IServiceCollection services) - { - _extension.ConfigureServices(services); - } -} \ No newline at end of file diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityReflectionBuilder.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityReflectionBuilder.cs index 9e4072c..6b5441f 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityReflectionBuilder.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/FacilityReflectionBuilder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using AppCoreNet.Diagnostics; using AppCoreNet.Extensions.DependencyInjection.Activator; @@ -28,7 +29,8 @@ public IFacilityReflectionBuilder AddResolver(IFacilityResolver resolver) return this; } - public IFacilityReflectionBuilder AddResolver(Action? configure = null) + public IFacilityReflectionBuilder AddResolver< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(Action? configure = null) where T : IFacilityResolver { var resolver = _activator.CreateInstance()!; @@ -43,7 +45,7 @@ public IFacilityReflectionBuilder AddExtensionsFrom(Action> + public IReadOnlyCollection<(IFacility Facility, IReadOnlyCollection FacilityExtensions)> Resolve() { List facilities = @@ -58,15 +60,15 @@ public IFacilityReflectionBuilder AddExtensionsFrom(Action> emptyFacilityExtensions = - new List>().AsReadOnly(); + ReadOnlyCollection emptyFacilityExtensions = + new List().AsReadOnly(); - List<(IFacility Facility, IReadOnlyCollection> FacilityExtensions)> result = + List<(IFacility Facility, IReadOnlyCollection FacilityExtensions)> result = new(); foreach (IFacility facility in facilities) { - IReadOnlyCollection> facilityExtensions = + IReadOnlyCollection facilityExtensions = extensionReflectionBuilder?.Resolve(facility.GetType()) ?? emptyFacilityExtensions; diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/IFacilityExtension.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/IFacilityExtension.cs index 71d16b7..c021b1f 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/IFacilityExtension.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/IFacilityExtension.cs @@ -8,13 +8,20 @@ namespace AppCoreNet.Extensions.DependencyInjection.Facilities; /// /// Represents an extension for a facility. /// -/// The type of the facility. -public interface IFacilityExtension - where T : IFacility +public interface IFacilityExtension { /// /// Must be implemented to register services with the . /// /// The . void ConfigureServices(IServiceCollection services); +} + +/// +/// Represents an extension for a facility. +/// +/// The type of the facility. +public interface IFacilityExtension : IFacilityExtension + where T : IFacility +{ } \ No newline at end of file diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/IFacilityExtensionReflectionBuilder.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/IFacilityExtensionReflectionBuilder.cs index 3d87408..c08c4cd 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/IFacilityExtensionReflectionBuilder.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/IFacilityExtensionReflectionBuilder.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; namespace AppCoreNet.Extensions.DependencyInjection.Facilities; @@ -27,6 +28,7 @@ public interface IFacilityExtensionReflectionBuilder /// The configuration delegate. /// The to allow chaining. [EditorBrowsable(EditorBrowsableState.Advanced)] - public IFacilityExtensionReflectionBuilder AddResolver(Action? configure = null) + public IFacilityExtensionReflectionBuilder AddResolver< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(Action? configure = null) where T : IFacilityExtensionResolver; } \ No newline at end of file diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/IFacilityExtensionResolver.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/IFacilityExtensionResolver.cs index b6141fd..cfd688f 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/IFacilityExtensionResolver.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/IFacilityExtensionResolver.cs @@ -17,5 +17,5 @@ public interface IFacilityExtensionResolver /// The type of the facility. /// The of facility extensions. /// Argument is null. - IEnumerable> Resolve(Type facilityType); + IEnumerable Resolve(Type facilityType); } \ No newline at end of file diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/IFacilityReflectionBuilder.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/IFacilityReflectionBuilder.cs index 8dad4a4..9c0d738 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/IFacilityReflectionBuilder.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/Facilities/IFacilityReflectionBuilder.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; namespace AppCoreNet.Extensions.DependencyInjection.Facilities; @@ -27,7 +28,8 @@ public interface IFacilityReflectionBuilder /// The configuration delegate. /// The to allow chaining. [EditorBrowsable(EditorBrowsableState.Advanced)] - public IFacilityReflectionBuilder AddResolver(Action? configure = null) + public IFacilityReflectionBuilder AddResolver< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(Action? configure = null) where T : IFacilityResolver; /// diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/FacilityServiceCollectionExtensions.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/FacilityServiceCollectionExtensions.cs index 019d072..e71d1aa 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/FacilityServiceCollectionExtensions.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/FacilityServiceCollectionExtensions.cs @@ -104,14 +104,14 @@ public static IServiceCollection AddFacilitiesFrom( configure(builder); - IReadOnlyCollection<(IFacility Facility, IReadOnlyCollection> FacilityExtensions)> + IReadOnlyCollection<(IFacility Facility, IReadOnlyCollection FacilityExtensions)> facilities = builder.Resolve(); - foreach ((IFacility Facility, IReadOnlyCollection> FacilityExtensions) item in facilities) + foreach ((IFacility Facility, IReadOnlyCollection FacilityExtensions) item in facilities) { item.Facility.ConfigureServices(services); - foreach (IFacilityExtension facilityExtension in item.FacilityExtensions) + foreach (IFacilityExtension facilityExtension in item.FacilityExtensions) { facilityExtension.ConfigureServices(services); } From cf881fdbbe4d4eff8b75013b8324e03b17e04c62 Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Fri, 16 May 2025 20:22:05 +0200 Subject: [PATCH 05/15] Added AOT compatibility to assembly and dependency context DI extensions. --- .../AssemblyReflectionBuilderExtensions.cs | 31 ++++++++++++++---- .../AssemblyScanner.cs | 2 ++ .../AssemblyServiceDescriptorResolver.cs | 2 ++ .../Facilities/AssemblyResolver.cs | 32 ++++++++++--------- .../Facilities/FacilityExtensionWrapper.cs | 22 ------------- ...dencyContextReflectionBuilderExtensions.cs | 12 +++++++ ...endencyContextServiceDescriptorResolver.cs | 2 ++ .../Facilities/DependencyContextResolver.cs | 4 ++- 8 files changed, 63 insertions(+), 44 deletions(-) delete mode 100644 DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/Facilities/FacilityExtensionWrapper.cs diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyReflectionBuilderExtensions.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyReflectionBuilderExtensions.cs index 844c3dc..c6ced0d 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyReflectionBuilderExtensions.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyReflectionBuilderExtensions.cs @@ -2,6 +2,7 @@ // Copyright (c) The AppCore .NET project. using System; +using System.Diagnostics.CodeAnalysis; using AppCoreNet.Diagnostics; using AppCoreNet.Extensions.DependencyInjection.Facilities; @@ -10,6 +11,7 @@ namespace AppCoreNet.Extensions.DependencyInjection; /// /// Provides extension methods to resolve service descriptors and facilities by reflection. /// +[RequiresUnreferencedCode("Uses reflection to discover services.")] public static class AssemblyReflectionBuilderExtensions { /// @@ -33,12 +35,21 @@ public static IServiceDescriptorReflectionBuilder Assemblies( /// Adds service descriptors from the calling assembly by reflection. /// /// The . + /// The delegate to configure the . /// The to allow chaining. /// Argument is null. - public static IServiceDescriptorReflectionBuilder Assembly(this IServiceDescriptorReflectionBuilder builder) + public static IServiceDescriptorReflectionBuilder Assembly( + this IServiceDescriptorReflectionBuilder builder, + Action? configure = null) { var assembly = System.Reflection.Assembly.GetCallingAssembly(); - return Assemblies(builder, c => c.Add(assembly)); + return Assemblies( + builder, + c => + { + c.Add(assembly); + configure?.Invoke(c); + }); } /// @@ -67,10 +78,14 @@ public static IFacilityReflectionBuilder Assemblies( /// Argument or is null. public static IFacilityReflectionBuilder Assembly( this IFacilityReflectionBuilder builder, - Action configure) + Action? configure = null) { var assembly = System.Reflection.Assembly.GetCallingAssembly(); - return Assemblies(builder, c => c.Add(assembly)); + return Assemblies(builder, c => + { + c.Add(assembly); + configure?.Invoke(c); + }); } /// @@ -99,9 +114,13 @@ public static IFacilityExtensionReflectionBuilder Assemblies( /// Argument or is null. public static IFacilityExtensionReflectionBuilder Assembly( this IFacilityExtensionReflectionBuilder builder, - Action configure) + Action? configure = null) { var assembly = System.Reflection.Assembly.GetCallingAssembly(); - return Assemblies(builder, c => c.Add(assembly)); + return Assemblies(builder, c => + { + c.Add(assembly); + configure?.Invoke(c); + }); } } \ No newline at end of file diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyScanner.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyScanner.cs index 431e702..5ca1113 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyScanner.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyScanner.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using AppCoreNet.Diagnostics; @@ -12,6 +13,7 @@ namespace AppCoreNet.Extensions.DependencyInjection; /// /// Represents a type used to scan assemblies for exported types. /// +[RequiresUnreferencedCode("Uses reflection to discover types.")] public class AssemblyScanner { private static readonly List _defaultExcludedAssemblies = diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyServiceDescriptorResolver.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyServiceDescriptorResolver.cs index ef8f7eb..fe60fc5 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyServiceDescriptorResolver.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyServiceDescriptorResolver.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using AppCoreNet.Diagnostics; using Microsoft.Extensions.DependencyInjection; @@ -12,6 +13,7 @@ namespace AppCoreNet.Extensions.DependencyInjection; /// /// Builds an of by scanning assemblies. /// +[RequiresUnreferencedCode("Uses reflection to discover services.")] public class AssemblyServiceDescriptorResolver : IServiceDescriptorResolver { private readonly List _assemblies = new(); diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/Facilities/AssemblyResolver.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/Facilities/AssemblyResolver.cs index c1e639b..ccb4f22 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/Facilities/AssemblyResolver.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/Facilities/AssemblyResolver.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using AppCoreNet.Diagnostics; @@ -13,6 +14,7 @@ namespace AppCoreNet.Extensions.DependencyInjection.Facilities; /// /// Builds an of by scanning assemblies. /// +[RequiresUnreferencedCode("Uses reflection to discover types.")] public class AssemblyResolver : IFacilityResolver, IFacilityExtensionResolver { private readonly IActivator _activator; @@ -120,24 +122,12 @@ IEnumerable IFacilityResolver.Resolve() .Select(facilityType => (IFacility)_activator.CreateInstance(facilityType)!); } - private IFacilityExtension CreateExtension(Type extensionType) - { - Type contractType = extensionType.GetInterfaces() - .First(i => i.GetGenericTypeDefinition() == typeof(IFacilityExtension<>)) - .GenericTypeArguments[0]; - - Type extensionWrapperType = typeof(FacilityExtensionWrapper<>).MakeGenericType(contractType); - object extension = _activator.CreateInstance(extensionType)!; - - return (IFacilityExtension)System.Activator.CreateInstance(extensionWrapperType, extension)!; - } - /// - IEnumerable> IFacilityExtensionResolver.Resolve(Type facilityType) + IEnumerable IFacilityExtensionResolver.Resolve(Type facilityType) { Ensure.Arg.NotNull(facilityType); - var scanner = new AssemblyScanner(typeof(IFacilityExtension<>).MakeGenericType(facilityType), _assemblies) + var scanner = new AssemblyScanner(typeof(IFacilityExtension<>), _assemblies) { IncludePrivateTypes = _withPrivateTypes, }; @@ -149,6 +139,18 @@ IEnumerable> IFacilityExtensionResolver.Resolve(Ty scanner.Filters.Add(filter); return scanner.ScanAssemblies() - .Select(CreateExtension); + .Where(t => IsCompatibleExtensionType(t, facilityType)) + .Select(t => (IFacilityExtension)_activator.CreateInstance(t)!); + + static bool IsCompatibleExtensionType(Type extensionType, Type facilityType) + { + IEnumerable extensionInterfaces = + extensionType.GetInterfaces() + .Where(t => t.IsGenericType + && t.GetGenericTypeDefinition() == typeof(IFacilityExtension<>)); + + IEnumerable extendedFacilityTypes = extensionInterfaces.Select(t => t.GenericTypeArguments[0]); + return extendedFacilityTypes.Any(t => t.IsAssignableFrom(facilityType)); + } } } \ No newline at end of file diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/Facilities/FacilityExtensionWrapper.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/Facilities/FacilityExtensionWrapper.cs deleted file mode 100644 index 09eb6fc..0000000 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/Facilities/FacilityExtensionWrapper.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed under the MIT license. -// Copyright (c) The AppCore .NET project. - -using Microsoft.Extensions.DependencyInjection; - -namespace AppCoreNet.Extensions.DependencyInjection.Facilities; - -internal sealed class FacilityExtensionWrapper : IFacilityExtension - where TContract : IFacility -{ - internal IFacilityExtension Extension { get; } - - public FacilityExtensionWrapper(IFacilityExtension extension) - { - Extension = extension; - } - - public void ConfigureServices(IServiceCollection services) - { - Extension.ConfigureServices(services); - } -} \ No newline at end of file diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/DependencyContextReflectionBuilderExtensions.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/DependencyContextReflectionBuilderExtensions.cs index 767bcb5..1969ef1 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/DependencyContextReflectionBuilderExtensions.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/DependencyContextReflectionBuilderExtensions.cs @@ -2,6 +2,7 @@ // Copyright (c) The AppCore .NET project. using System; +using System.Diagnostics.CodeAnalysis; using AppCoreNet.Diagnostics; using AppCoreNet.Extensions.DependencyInjection.Facilities; using DependencyModel = Microsoft.Extensions.DependencyModel; @@ -11,8 +12,13 @@ namespace AppCoreNet.Extensions.DependencyInjection; /// /// Provides extension methods to resolve service descriptors and facilities by reflection. /// +[RequiresUnreferencedCode("Uses reflection to discover services.")] public static class DependencyContextReflectionBuilderExtensions { + private const string RequiresAssemblyFilesMessage = + "DependencyContext for an assembly from a application published as single-file is not supported."; + + [RequiresAssemblyFiles(RequiresAssemblyFilesMessage)] private static DependencyModel.DependencyContext ResolveDependencyContext(DependencyModel.DependencyContext? context) { context ??= DependencyModel.DependencyContext.Default; @@ -33,6 +39,7 @@ private static DependencyModel.DependencyContext ResolveDependencyContext(Depend /// The delegate to configure the . /// The to allow chaining. /// Argument is null. + [RequiresAssemblyFiles(RequiresAssemblyFilesMessage)] public static IServiceDescriptorReflectionBuilder DependencyContext( this IServiceDescriptorReflectionBuilder builder, Action configure) @@ -51,6 +58,7 @@ public static IServiceDescriptorReflectionBuilder DependencyContext( /// The delegate to configure the . /// The to allow chaining. /// Argument is null. + [RequiresAssemblyFiles(RequiresAssemblyFilesMessage)] public static IServiceDescriptorReflectionBuilder DependencyContext( this IServiceDescriptorReflectionBuilder builder, DependencyModel.DependencyContext? context = null, @@ -74,6 +82,7 @@ public static IServiceDescriptorReflectionBuilder DependencyContext( /// The delegate to configure the . /// The to allow chaining. /// Argument or is null. + [RequiresAssemblyFiles(RequiresAssemblyFilesMessage)] public static IFacilityReflectionBuilder DependencyContext( this IFacilityReflectionBuilder builder, Action configure) @@ -92,6 +101,7 @@ public static IFacilityReflectionBuilder DependencyContext( /// The delegate to configure the . /// The to allow chaining. /// Argument or is null. + [RequiresAssemblyFiles(RequiresAssemblyFilesMessage)] public static IFacilityReflectionBuilder DependencyContext( this IFacilityReflectionBuilder builder, DependencyModel.DependencyContext? context = null, @@ -115,6 +125,7 @@ public static IFacilityReflectionBuilder DependencyContext( /// The delegate to configure the . /// The to allow chaining. /// Argument or is null. + [RequiresAssemblyFiles(RequiresAssemblyFilesMessage)] public static IFacilityExtensionReflectionBuilder DependencyContext( this IFacilityExtensionReflectionBuilder builder, Action configure) @@ -134,6 +145,7 @@ public static IFacilityExtensionReflectionBuilder DependencyContext( /// The to allow chaining. /// Argument or is null. /// Argument is null and no default DependencyContext was available. + [RequiresAssemblyFiles(RequiresAssemblyFilesMessage)] public static IFacilityExtensionReflectionBuilder DependencyContext( this IFacilityExtensionReflectionBuilder builder, DependencyModel.DependencyContext? context = null, diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/DependencyContextServiceDescriptorResolver.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/DependencyContextServiceDescriptorResolver.cs index d3b917f..41709a2 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/DependencyContextServiceDescriptorResolver.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/DependencyContextServiceDescriptorResolver.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using AppCoreNet.Diagnostics; @@ -15,6 +16,7 @@ namespace AppCoreNet.Extensions.DependencyInjection; /// Builds an of by scanning assemblies in a /// . /// +[RequiresUnreferencedCode("Uses reflection to discover services.")] public class DependencyContextServiceDescriptorResolver : IServiceDescriptorResolver { private readonly AssemblyServiceDescriptorResolver _source; diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/Facilities/DependencyContextResolver.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/Facilities/DependencyContextResolver.cs index 05f07c4..2a72f1b 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/Facilities/DependencyContextResolver.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/Facilities/DependencyContextResolver.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using AppCoreNet.Diagnostics; @@ -16,6 +17,7 @@ namespace AppCoreNet.Extensions.DependencyInjection.Facilities; /// Builds an of by scanning assemblies in a /// . /// +[RequiresUnreferencedCode("Uses reflection to discover services.")] public class DependencyContextResolver : IFacilityResolver, IFacilityExtensionResolver { private readonly AssemblyResolver _resolver; @@ -97,7 +99,7 @@ IEnumerable IFacilityResolver.Resolve() } /// - IEnumerable> IFacilityExtensionResolver.Resolve(Type facilityType) + IEnumerable IFacilityExtensionResolver.Resolve(Type facilityType) { return ((IFacilityExtensionResolver)_resolver).Resolve(facilityType); } From 2b1513d86389760c26544defd7c1175141914829 Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Fri, 16 May 2025 20:22:27 +0200 Subject: [PATCH 06/15] Adjust unit tests for AOT compatibility. --- .../AssemblyResolverTests.cs | 17 +++++++++-------- .../Facility1.cs | 3 +-- .../Facility1Extension2.cs | 2 +- .../Facility2.cs | 3 +-- .../Facility2Extension1.cs | 14 ++++++++++++++ .../Facility2Extension2.cs | 14 ++++++++++++++ .../IFacility1.cs | 10 ++++++++++ .../IFacility2.cs | 10 ++++++++++ .../Facilities/FacilityBuilderTests.cs | 2 +- 9 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility2Extension1.cs create mode 100644 DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility2Extension2.cs create mode 100644 DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/IFacility1.cs create mode 100644 DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/IFacility2.cs diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyResolverTests.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyResolverTests.cs index 3ad1933..42406a0 100644 --- a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyResolverTests.cs +++ b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyResolverTests.cs @@ -46,15 +46,16 @@ public void ResolvesAllFacilityExtensions() .Add(typeof(AssemblyResolverTests).Assembly) .ClearDefaultFilters(); - IEnumerable> facilityExtensions = - ((IFacilityExtensionResolver)resolver).Resolve(typeof(Facility1)); + IFacilityExtension[] facilityExtensions = + ((IFacilityExtensionResolver)resolver).Resolve(typeof(Facility1)) + .ToArray(); - facilityExtensions.Should() - .AllBeOfType>(); + facilityExtensions[0] + .Should() + .BeOfType(); - facilityExtensions.OfType>() - .Select(e => e.Extension.GetType()) - .Should() - .BeEquivalentTo(new[] { typeof(Facility1Extension1), typeof(Facility1Extension2) }); + facilityExtensions[1] + .Should() + .BeOfType(); } } \ No newline at end of file diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility1.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility1.cs index 7076687..262ae55 100644 --- a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility1.cs +++ b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility1.cs @@ -1,12 +1,11 @@ // Licensed under the MIT license. // Copyright (c) The AppCore .NET project. -using AppCoreNet.Extensions.DependencyInjection.Facilities; using Microsoft.Extensions.DependencyInjection; namespace AppCoreNet.Extensions.DependencyInjection; -public class Facility1 : IFacility +public class Facility1 : IFacility1 { public void ConfigureServices(IServiceCollection services) { diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility1Extension2.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility1Extension2.cs index 6eb36c7..a62bc32 100644 --- a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility1Extension2.cs +++ b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility1Extension2.cs @@ -6,7 +6,7 @@ namespace AppCoreNet.Extensions.DependencyInjection; -public class Facility1Extension2 : IFacilityExtension +public class Facility1Extension2 : IFacilityExtension { public void ConfigureServices(IServiceCollection services) { diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility2.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility2.cs index b224b6a..4f0d5ca 100644 --- a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility2.cs +++ b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility2.cs @@ -1,12 +1,11 @@ // Licensed under the MIT license. // Copyright (c) The AppCore .NET project. -using AppCoreNet.Extensions.DependencyInjection.Facilities; using Microsoft.Extensions.DependencyInjection; namespace AppCoreNet.Extensions.DependencyInjection; -public class Facility2 : IFacility +public class Facility2 : IFacility2 { public void ConfigureServices(IServiceCollection services) { diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility2Extension1.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility2Extension1.cs new file mode 100644 index 0000000..13c3d41 --- /dev/null +++ b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility2Extension1.cs @@ -0,0 +1,14 @@ +// Licensed under the MIT license. +// Copyright (c) The AppCore .NET project. + +using AppCoreNet.Extensions.DependencyInjection.Facilities; +using Microsoft.Extensions.DependencyInjection; + +namespace AppCoreNet.Extensions.DependencyInjection; + +public class Facility2Extension1 : IFacilityExtension +{ + public void ConfigureServices(IServiceCollection services) + { + } +} \ No newline at end of file diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility2Extension2.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility2Extension2.cs new file mode 100644 index 0000000..637cd0c --- /dev/null +++ b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/Facility2Extension2.cs @@ -0,0 +1,14 @@ +// Licensed under the MIT license. +// Copyright (c) The AppCore .NET project. + +using AppCoreNet.Extensions.DependencyInjection.Facilities; +using Microsoft.Extensions.DependencyInjection; + +namespace AppCoreNet.Extensions.DependencyInjection; + +public class Facility2Extension2 : IFacilityExtension +{ + public void ConfigureServices(IServiceCollection services) + { + } +} \ No newline at end of file diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/IFacility1.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/IFacility1.cs new file mode 100644 index 0000000..78e5030 --- /dev/null +++ b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/IFacility1.cs @@ -0,0 +1,10 @@ +// Licensed under the MIT license. +// Copyright (c) The AppCore .NET project. + +using AppCoreNet.Extensions.DependencyInjection.Facilities; + +namespace AppCoreNet.Extensions.DependencyInjection; + +public interface IFacility1 : IFacility +{ +} \ No newline at end of file diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/IFacility2.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/IFacility2.cs new file mode 100644 index 0000000..1791d51 --- /dev/null +++ b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/IFacility2.cs @@ -0,0 +1,10 @@ +// Licensed under the MIT license. +// Copyright (c) The AppCore .NET project. + +using AppCoreNet.Extensions.DependencyInjection.Facilities; + +namespace AppCoreNet.Extensions.DependencyInjection; + +public interface IFacility2 : IFacility +{ +} \ No newline at end of file diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.Tests/Facilities/FacilityBuilderTests.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.Tests/Facilities/FacilityBuilderTests.cs index 2896631..5afc98b 100644 --- a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.Tests/Facilities/FacilityBuilderTests.cs +++ b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.Tests/Facilities/FacilityBuilderTests.cs @@ -74,7 +74,7 @@ public void AddExtensionsFromInvokesResolver() { var resolver = Substitute.For(); resolver.Resolve(Arg.Any()) - .Returns(Array.Empty>()); + .Returns(Array.Empty()); var builder = new FacilityBuilder(_services, _activator); builder.AddExtensionsFrom(r => r.AddResolver(resolver)); From c033c9cc91675ebe65dc286d5e9173e064ab1848 Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Fri, 16 May 2025 21:57:45 +0200 Subject: [PATCH 07/15] Added AOT compatibility to IStartupTask's. --- .../StartupTaskServiceCollectionExtensions.cs | 14 ++++++++++---- Hosting/src/Directory.Build.props | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting.Abstractions/StartupTaskServiceCollectionExtensions.cs b/Hosting/src/AppCoreNet.Extensions.Hosting.Abstractions/StartupTaskServiceCollectionExtensions.cs index 39a0f89..337ba47 100644 --- a/Hosting/src/AppCoreNet.Extensions.Hosting.Abstractions/StartupTaskServiceCollectionExtensions.cs +++ b/Hosting/src/AppCoreNet.Extensions.Hosting.Abstractions/StartupTaskServiceCollectionExtensions.cs @@ -2,6 +2,7 @@ // Copyright (c) The AppCore .NET project. using System; +using System.Diagnostics.CodeAnalysis; using AppCoreNet.Diagnostics; using AppCoreNet.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; @@ -22,13 +23,14 @@ public static class StartupTaskServiceCollectionExtensions /// The implementation of the . /// The passed to allow chaining. /// Argument is null. - public static IServiceCollection AddStartupTask(this IServiceCollection services, Type startupTaskType) + public static IServiceCollection AddStartupTask( + this IServiceCollection services, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type startupTaskType) { Ensure.Arg.NotNull(startupTaskType); Ensure.Arg.OfType(startupTaskType); services.TryAddEnumerable(ServiceDescriptor.Transient(typeof(IStartupTask), startupTaskType)); - return services; } @@ -38,7 +40,9 @@ public static IServiceCollection AddStartupTask(this IServiceCollection services /// The implementation of the . /// The . /// The passed to allow chaining. - public static IServiceCollection AddStartupTask(this IServiceCollection services) + public static IServiceCollection AddStartupTask< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( + this IServiceCollection services) where T : IStartupTask { return AddStartupTask(services, typeof(T)); @@ -50,7 +54,9 @@ public static IServiceCollection AddStartupTask(this IServiceCollection servi /// The . /// The configuration delegate. /// The passed to allow chaining. - public static IServiceCollection AddStartupTasksFrom(this IServiceCollection services, Action configure) + public static IServiceCollection AddStartupTasksFrom( + this IServiceCollection services, + Action configure) { return services.TryAddEnumerableFrom(configure); } diff --git a/Hosting/src/Directory.Build.props b/Hosting/src/Directory.Build.props index 53d9690..614e662 100644 --- a/Hosting/src/Directory.Build.props +++ b/Hosting/src/Directory.Build.props @@ -3,6 +3,7 @@ true + true From d2655362bd05d96d0cd492ac20cfd610f2c7057f Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Thu, 22 May 2025 09:12:31 +0200 Subject: [PATCH 08/15] Refactored ServiceCollectionServiceProvider. --- .../ServiceCollectionServiceProvider.cs | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/ServiceCollectionServiceProvider.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/ServiceCollectionServiceProvider.cs index e0ab31e..6a5c232 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/ServiceCollectionServiceProvider.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/ServiceCollectionServiceProvider.cs @@ -62,42 +62,46 @@ public bool IsService(Type serviceType) return _services.Any(sd => IsServiceType(sd.ServiceType, serviceType)); } - private object[] GetServices(Type serviceType) + private object CreateService(ServiceDescriptor serviceDescriptor, Type requestedType) { + object? instance = serviceDescriptor.ImplementationInstance; + if (instance == null && serviceDescriptor.ImplementationFactory != null) + { + instance = serviceDescriptor.ImplementationFactory(this); + } + + if (instance == null && serviceDescriptor.ImplementationType != null) + { + Type implementationType = serviceDescriptor.ImplementationType!; + if (implementationType.IsGenericTypeDefinition) + implementationType = MakeGenericType(implementationType, requestedType.GenericTypeArguments); + + instance = ActivatorUtilities.CreateInstance(this, implementationType); + } + + return instance!; + [UnconditionalSuppressMessage( "AotAnalysis", "IL3050:RequiresDynamicCode", - Justification = "VerifyAotCompatibility ensures that dynamic code supported")] + Justification = "VerifyAotCompatibility ensures that dynamic code is supported")] + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] static Type MakeGenericType(Type type, params Type[] typeArguments) { if (VerifyAotCompatibility) - throw new InvalidOperationException("Cannot build generic type when using AOT."); - - return type.MakeGenericType(typeArguments); - } - - object ServiceFactory(ServiceDescriptor serviceDescriptor) - { - object? instance = serviceDescriptor.ImplementationInstance; - if (instance == null && serviceDescriptor.ImplementationFactory != null) { - instance = serviceDescriptor.ImplementationFactory(this); + throw new InvalidOperationException( + $"Cannot build generic type '{type.GetDisplayName()}' when using AOT"); } - if (instance == null && serviceDescriptor.ImplementationType != null) - { - Type implementationType = serviceDescriptor.ImplementationType!; - if (implementationType.IsGenericTypeDefinition) - implementationType = MakeGenericType(implementationType, serviceType.GenericTypeArguments); - - instance = ActivatorUtilities.CreateInstance(this, implementationType); - } - - return instance!; + return type.MakeGenericType(typeArguments); } + } + private object[] GetServices(Type serviceType) + { return _services.Where(sd => IsServiceType(sd.ServiceType, serviceType)) - .Select(ServiceFactory) + .Select(sd => CreateService(sd, serviceType)) .ToArray(); } @@ -139,7 +143,7 @@ object ServiceFactory(ServiceDescriptor serviceDescriptor) static Array CreateArray(Type elementType, int length) { if (VerifyAotCompatibility && elementType.IsValueType) - throw new InvalidOperationException("Cannot build array of value service types."); + throw new InvalidOperationException("Cannot build array of value service types when using AOT"); return Array.CreateInstance(elementType, length); } From f1a4a510149eaf12e6436d26c5acf7c9ea4c5de5 Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Thu, 22 May 2025 18:16:39 +0200 Subject: [PATCH 09/15] Updated AOT attributes. --- .../AssemblyReflectionBuilderExtensions.cs | 2 +- .../DependencyContextReflectionBuilderExtensions.cs | 2 +- DependencyInjection/src/Directory.Build.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyReflectionBuilderExtensions.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyReflectionBuilderExtensions.cs index c6ced0d..2401e3c 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyReflectionBuilderExtensions.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyReflectionBuilderExtensions.cs @@ -11,7 +11,7 @@ namespace AppCoreNet.Extensions.DependencyInjection; /// /// Provides extension methods to resolve service descriptors and facilities by reflection. /// -[RequiresUnreferencedCode("Uses reflection to discover services.")] +[RequiresUnreferencedCode("Uses reflection to discover types.")] public static class AssemblyReflectionBuilderExtensions { /// diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/DependencyContextReflectionBuilderExtensions.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/DependencyContextReflectionBuilderExtensions.cs index 1969ef1..d751402 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/DependencyContextReflectionBuilderExtensions.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/DependencyContextReflectionBuilderExtensions.cs @@ -12,7 +12,7 @@ namespace AppCoreNet.Extensions.DependencyInjection; /// /// Provides extension methods to resolve service descriptors and facilities by reflection. /// -[RequiresUnreferencedCode("Uses reflection to discover services.")] +[RequiresUnreferencedCode("Uses reflection to discover types.")] public static class DependencyContextReflectionBuilderExtensions { private const string RequiresAssemblyFilesMessage = diff --git a/DependencyInjection/src/Directory.Build.props b/DependencyInjection/src/Directory.Build.props index 614e662..a060869 100644 --- a/DependencyInjection/src/Directory.Build.props +++ b/DependencyInjection/src/Directory.Build.props @@ -3,7 +3,7 @@ true - true + true From 7cb77292fe72b38cb76f1839ecb9cb4743c2b917 Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Thu, 22 May 2025 18:17:12 +0200 Subject: [PATCH 10/15] Added AOT attributes to plugins. --- ...ions.Hosting.Plugins.AspNetCore.Mvc.csproj | 8 ++ .../PluginsMvcBuilderExtensions.cs | 3 + .../ServiceCollectionServiceProvider.cs | 100 ------------------ ...pCoreNet.Extensions.Hosting.Plugins.csproj | 2 + .../FacilityExtensionWrapper.cs | 23 ---- .../PluginFacilityResolver.cs | 38 +++---- .../PluginReflectionBuilderExtensions.cs | 2 + .../PluginServiceCollectionExtensions.cs | 3 + .../PluginServiceDescriptorResolver.cs | 2 + .../Plugin.cs | 3 + .../PluginManager.cs | 2 + .../PluginServiceCollection.cs | 3 + Hosting/src/Directory.Build.props | 2 +- 13 files changed, 48 insertions(+), 143 deletions(-) delete mode 100644 Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc/ServiceCollectionServiceProvider.cs delete mode 100644 Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/FacilityExtensionWrapper.cs diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc.csproj b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc.csproj index f826e2d..9987992 100644 --- a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc.csproj +++ b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc.csproj @@ -3,6 +3,8 @@ net8.0 Provides MVC plugin extensions to the Microsoft.Extensions.Hosting framework. + + true @@ -13,4 +15,10 @@ + + + ServiceCollectionServiceProvider.cs + + + diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc/PluginsMvcBuilderExtensions.cs b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc/PluginsMvcBuilderExtensions.cs index 3f164e1..eea5b10 100644 --- a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc/PluginsMvcBuilderExtensions.cs +++ b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc/PluginsMvcBuilderExtensions.cs @@ -2,6 +2,7 @@ // Copyright (c) The AppCore .NET project. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using AppCoreNet.Diagnostics; using AppCoreNet.Extensions.Hosting.Plugins; @@ -14,6 +15,8 @@ namespace AppCoreNet.Extensions.DependencyInjection; /// /// Provides extension methods to register plugins with MVC. /// +[RequiresUnreferencedCode("Uses reflection to discover services.")] +[RequiresDynamicCode("Creates the generic plugin service collection.")] public static class PluginsMvcBuilderExtensions { private static void AddApplicationParts(ApplicationPartManager manager, Assembly assembly) diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc/ServiceCollectionServiceProvider.cs b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc/ServiceCollectionServiceProvider.cs deleted file mode 100644 index 553ae27..0000000 --- a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc/ServiceCollectionServiceProvider.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Licensed under the MIT license. -// Copyright (c) The AppCore .NET project. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.DependencyInjection; - -// ReSharper disable once CheckNamespace -namespace AppCoreNet.Extensions.DependencyInjection; - -internal sealed class ServiceCollectionServiceProvider : IServiceProvider, IServiceProviderIsService -{ - private readonly IServiceCollection _services; - private readonly Dictionary _additionalServices = new(); - - public ServiceCollectionServiceProvider(IServiceCollection services) - { - _services = services; - } - - public void AddService(Type serviceType, object instance) - { - _additionalServices.Add(serviceType, instance); - } - - private IEnumerable GetServices(Type serviceType) - { - object ServiceFactory(ServiceDescriptor serviceDescriptor) - { - object? instance = serviceDescriptor.ImplementationInstance; - if (instance == null && serviceDescriptor.ImplementationFactory != null) - { - instance = serviceDescriptor.ImplementationFactory(this); - } - - if (instance == null && serviceDescriptor.ImplementationType != null) - { - Type implementationType = serviceDescriptor.ImplementationType!; - if (implementationType.IsGenericType) - implementationType = implementationType.MakeGenericType(serviceType.GenericTypeArguments); - - instance = ActivatorUtilities.CreateInstance(this, implementationType); - } - - return instance!; - } - - return _services.Where( - sd => sd.ServiceType == serviceType - || (serviceType.IsGenericType - && sd.ServiceType == serviceType.GetGenericTypeDefinition())) - .Select(ServiceFactory); - } - - public object? GetService(Type serviceType) - { - if (serviceType == typeof(IServiceProvider) || serviceType == typeof(IServiceProviderIsService)) - return this; - - if (_additionalServices.TryGetValue(serviceType, out object? instance)) - return instance; - - if (serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) - { - serviceType = serviceType.GenericTypeArguments[0]; - var instances = (IList)System.Activator.CreateInstance(typeof(List<>).MakeGenericType(serviceType))!; - foreach (object service in GetServices(serviceType)) - { - instances.Add(service); - } - - instance = instances; - } - else - { - instance = GetServices(serviceType).FirstOrDefault(); - } - - return instance; - } - - public bool IsService(Type serviceType) - { - if (serviceType == typeof(IServiceProvider) || serviceType == typeof(IServiceProviderIsService)) - return true; - - if (_additionalServices.TryGetValue(serviceType, out object? _)) - return true; - - if (serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) - return true; - - return _services.Any( - sd => sd.ServiceType == serviceType - || (serviceType.IsGenericType - && sd.ServiceType == serviceType.GetGenericTypeDefinition())); - } -} \ No newline at end of file diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/AppCoreNet.Extensions.Hosting.Plugins.csproj b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/AppCoreNet.Extensions.Hosting.Plugins.csproj index 8c50816..4190083 100644 --- a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/AppCoreNet.Extensions.Hosting.Plugins.csproj +++ b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/AppCoreNet.Extensions.Hosting.Plugins.csproj @@ -3,6 +3,8 @@ net8.0 Provides plugin extensions to the Microsoft.Extensions.Hosting framework. + + true diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/FacilityExtensionWrapper.cs b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/FacilityExtensionWrapper.cs deleted file mode 100644 index 701b42c..0000000 --- a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/FacilityExtensionWrapper.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed under the MIT license. -// Copyright (c) The AppCore .NET project. - -using Microsoft.Extensions.DependencyInjection; - -// ReSharper disable once CheckNamespace -namespace AppCoreNet.Extensions.DependencyInjection.Facilities; - -internal sealed class FacilityExtensionWrapper : IFacilityExtension - where TContract : IFacility -{ - private readonly IFacilityExtension _extension; - - public FacilityExtensionWrapper(IFacilityExtension extension) - { - _extension = extension; - } - - public void ConfigureServices(IServiceCollection services) - { - _extension.ConfigureServices(services); - } -} \ No newline at end of file diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/PluginFacilityResolver.cs b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/PluginFacilityResolver.cs index a4cdf4a..4fe7f1f 100644 --- a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/PluginFacilityResolver.cs +++ b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/PluginFacilityResolver.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using AppCoreNet.Diagnostics; using AppCoreNet.Extensions.Hosting.Plugins; @@ -13,6 +14,7 @@ namespace AppCoreNet.Extensions.DependencyInjection.Facilities; /// /// Builds an of by scanning plugin assemblies. /// +[RequiresUnreferencedCode("Uses reflection to discover types.")] public class PluginFacilityResolver : IFacilityResolver, IFacilityExtensionResolver { private readonly IPluginManager _pluginManager; @@ -36,30 +38,28 @@ IEnumerable IFacilityResolver.Resolve() } } - private IFacilityExtension CreateExtension(IPluginService pluginService) - { - Type extensionType = pluginService.GetType() - .GetInterfaces() - .First(i => i.GetGenericTypeDefinition() == typeof(IPluginService<>)) - .GenericTypeArguments[0]; - - Type contractType = extensionType.GenericTypeArguments[0]; - Type extensionWrapperType = typeof(FacilityExtensionWrapper<>).MakeGenericType(contractType); - - return (IFacilityExtension)System.Activator.CreateInstance( - extensionWrapperType, - pluginService.Instance)!; - } - /// - IEnumerable> IFacilityExtensionResolver.Resolve(Type facilityType) + IEnumerable IFacilityExtensionResolver.Resolve(Type facilityType) { - IPluginServiceCollection services = _pluginManager.GetServices( - typeof(IFacilityExtension<>).MakeGenericType(facilityType)); + IPluginServiceCollection services = _pluginManager.GetServices(typeof(IFacilityExtension)); foreach (IPluginService service in services) { - yield return CreateExtension(service); + if (!IsCompatibleExtensionType(service.Instance.GetType(), facilityType)) + continue; + + yield return (IFacilityExtension)service.Instance; + } + + static bool IsCompatibleExtensionType(Type extensionType, Type facilityType) + { + IEnumerable extensionInterfaces = + extensionType.GetInterfaces() + .Where(t => t.IsGenericType + && t.GetGenericTypeDefinition() == typeof(IFacilityExtension<>)); + + IEnumerable extendedFacilityTypes = extensionInterfaces.Select(t => t.GenericTypeArguments[0]); + return extendedFacilityTypes.Any(t => t.IsAssignableFrom(facilityType)); } } } \ No newline at end of file diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/PluginReflectionBuilderExtensions.cs b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/PluginReflectionBuilderExtensions.cs index d7b8f42..24a81a0 100644 --- a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/PluginReflectionBuilderExtensions.cs +++ b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/PluginReflectionBuilderExtensions.cs @@ -2,6 +2,7 @@ // Copyright (c) The AppCore .NET project. using System; +using System.Diagnostics.CodeAnalysis; using AppCoreNet.Diagnostics; using AppCoreNet.Extensions.DependencyInjection.Facilities; @@ -11,6 +12,7 @@ namespace AppCoreNet.Extensions.DependencyInjection; /// /// Provides extension methods to register services and facilities from plugins. /// +[RequiresUnreferencedCode("Uses reflection to discover types.")] public static class PluginReflectionBuilderExtensions { /// diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/PluginServiceCollectionExtensions.cs b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/PluginServiceCollectionExtensions.cs index b009874..a41c4be 100644 --- a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/PluginServiceCollectionExtensions.cs +++ b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/PluginServiceCollectionExtensions.cs @@ -2,6 +2,7 @@ // Copyright (c) The AppCore .NET project. using System; +using System.Diagnostics.CodeAnalysis; using AppCoreNet.Diagnostics; using AppCoreNet.Extensions.DependencyInjection.Activator; using AppCoreNet.Extensions.Hosting.Plugins; @@ -15,6 +16,8 @@ namespace AppCoreNet.Extensions.DependencyInjection; /// /// Provides extension methods to register plugins. /// +[RequiresUnreferencedCode("Uses reflection to discover services.")] +[RequiresDynamicCode("Creates the generic plugin service collection.")] public static class PluginServiceCollectionExtensions { private static readonly object _pluginManagerFactorySyncRoot = new(); diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/PluginServiceDescriptorResolver.cs b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/PluginServiceDescriptorResolver.cs index 307c430..9891dfe 100644 --- a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/PluginServiceDescriptorResolver.cs +++ b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/DependencyInjection/PluginServiceDescriptorResolver.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using AppCoreNet.Diagnostics; using AppCoreNet.Extensions.Hosting.Plugins; @@ -15,6 +16,7 @@ namespace AppCoreNet.Extensions.DependencyInjection; /// /// Builds an of by scanning plugin assemblies. /// +[RequiresUnreferencedCode("Uses reflection to discover services.")] public class PluginServiceDescriptorResolver : IServiceDescriptorResolver { private readonly List> _filters = new(); diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/Plugin.cs b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/Plugin.cs index 3a70e74..b2a6772 100644 --- a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/Plugin.cs +++ b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/Plugin.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using AppCoreNet.Diagnostics; @@ -15,6 +16,8 @@ namespace AppCoreNet.Extensions.Hosting.Plugins; +[RequiresUnreferencedCode("Uses reflection to discover services.")] +[RequiresDynamicCode("Dynamically creates generic types.")] internal sealed class Plugin : IPlugin, IServiceProviderIsService { private readonly IActivator _activator; diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/PluginManager.cs b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/PluginManager.cs index f1d70b3..9b218bf 100644 --- a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/PluginManager.cs +++ b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/PluginManager.cs @@ -19,6 +19,8 @@ namespace AppCoreNet.Extensions.Hosting.Plugins; /// /// Provides default implementation of the interface. /// +[RequiresUnreferencedCode("Uses reflection to discover services.")] +[RequiresDynamicCode("Creates the generic plugin service collection.")] public class PluginManager : IPluginManager { private readonly IActivator _activator; diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/PluginServiceCollection.cs b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/PluginServiceCollection.cs index ea71a78..2a7daf9 100644 --- a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/PluginServiceCollection.cs +++ b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/PluginServiceCollection.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace AppCoreNet.Extensions.Hosting.Plugins; @@ -32,6 +33,8 @@ IEnumerator IEnumerable.GetEnumerator() } } + [RequiresUnreferencedCode("Uses reflection to discover services.")] + [RequiresDynamicCode("Creates the generic plugin service collection.")] public static IInternalPluginServiceCollection Create(Type serviceType) { Type type = _typeCache.GetOrAdd(serviceType, static t => typeof(InternalCollection<>).MakeGenericType(t)); diff --git a/Hosting/src/Directory.Build.props b/Hosting/src/Directory.Build.props index 614e662..a060869 100644 --- a/Hosting/src/Directory.Build.props +++ b/Hosting/src/Directory.Build.props @@ -3,7 +3,7 @@ true - true + true From 1852cb7bb1c45667ce4ddf52e71e42b97b1bf7e5 Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Thu, 22 May 2025 18:17:45 +0200 Subject: [PATCH 11/15] Removed explicit reference to NetAnalyzers. --- AppCoreNet.Extensions.sln | 1 + Directory.Build.props | 5 +---- Directory.Packages.props | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/AppCoreNet.Extensions.sln b/AppCoreNet.Extensions.sln index 1ad7ff6..0b9d93d 100644 --- a/AppCoreNet.Extensions.sln +++ b/AppCoreNet.Extensions.sln @@ -109,6 +109,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution stylecop.json = stylecop.json Directory.Packages.props = Directory.Packages.props NuGet.config = NuGet.config + renovate.json = renovate.json EndProjectSection EndProject Global diff --git a/Directory.Build.props b/Directory.Build.props index ed4cd5f..c0828c7 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -46,6 +46,7 @@ true + true $(MSBuildThisFileDirectory)AppCoreNet.ruleset @@ -58,10 +59,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Directory.Packages.props b/Directory.Packages.props index e025e7c..209bd13 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,6 @@ - From 86da59b61ef03c922a937d064aa2c066f1060e9d Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Thu, 22 May 2025 19:22:09 +0200 Subject: [PATCH 12/15] Added AOT compatibility to HTTP Client authentication. --- ...ttp.Authentication.OAuth.AspNetCore.csproj | 2 ++ ...icationSchemeOAuthClientOptionsResolver.cs | 7 +++- ...uthenticationSessionOAuthUserTokenStore.cs | 5 ++- .../OAuthUserHandler.cs | 25 +++++++++++-- .../OAuthUserTokenService.cs | 4 ++- ....Authentication.OAuth.OpenIdConnect.csproj | 2 ++ .../OAuthClientHandler.cs | 25 +++++++++++-- .../OAuthOptionsResolver.cs | 5 ++- .../OAuthPasswordHandler.cs | 30 ++++++++++++++-- .../AuthenticationHandler.cs | 6 ++-- .../AuthenticationScheme.cs | 19 +++++++--- .../AuthenticationSchemeHandler.cs | 18 +++++++--- ...thenticationHttpClientBuilderExtensions.cs | 4 +-- ...tpClientAuthenticationBuilderExtensions.cs | 11 +++--- .../HttpClientAuthenticationOptions.cs | 7 ++-- .../IAuthenticationSchemeHandler.cs | 6 ++-- Http/src/Directory.Build.props | 1 + .../AuthenticationSchemeProviderTests.cs | 35 +++++++++++-------- 18 files changed, 160 insertions(+), 52 deletions(-) diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore.csproj b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore.csproj index 324c386..f04f0ca 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore.csproj +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore.csproj @@ -7,6 +7,8 @@ from ASP.NET Core authentication schemes. $(PackageTags);Security;Authentication;OAuth;OAuth2;OAuth 2.0;IdentityModel;ASP.NET Core + + true diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/AuthenticationSchemeOAuthClientOptionsResolver.cs b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/AuthenticationSchemeOAuthClientOptionsResolver.cs index 16ff5d8..02e8d67 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/AuthenticationSchemeOAuthClientOptionsResolver.cs +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/AuthenticationSchemeOAuthClientOptionsResolver.cs @@ -2,6 +2,7 @@ // Copyright (c) The AppCore .NET project. using System; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using AppCoreNet.Diagnostics; using Microsoft.AspNetCore.Authentication; @@ -16,7 +17,11 @@ namespace AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore; /// The type of the . /// The type of the . /// The type of the . -public abstract class AuthenticationSchemeOAuthClientOptionsResolver : IOAuthOptionsResolver +public abstract class AuthenticationSchemeOAuthClientOptionsResolver< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TClientOptions, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOptions, + THandler> + : IOAuthOptionsResolver where TClientOptions : AuthenticationSchemeOptions where TOptions : RemoteAuthenticationOptions where THandler : IAuthenticationHandler diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/AuthenticationSessionOAuthUserTokenStore.cs b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/AuthenticationSessionOAuthUserTokenStore.cs index a476a6e..337b9e3 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/AuthenticationSessionOAuthUserTokenStore.cs +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/AuthenticationSessionOAuthUserTokenStore.cs @@ -2,6 +2,7 @@ // Copyright (c) The AppCore .NET project. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Security.Authentication; using System.Security.Claims; using System.Threading; @@ -20,7 +21,9 @@ namespace AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore; /// session. /// /// The type of the . -public abstract class AuthenticationSessionOAuthUserTokenStore : IOAuthUserTokenStore +public abstract class AuthenticationSessionOAuthUserTokenStore< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOptions> + : IOAuthUserTokenStore where TOptions : OAuthUserOptions { private readonly IHttpContextAccessor _httpContextAccessor; diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/OAuthUserHandler.cs b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/OAuthUserHandler.cs index e200e37..5a69f41 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/OAuthUserHandler.cs +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/OAuthUserHandler.cs @@ -16,7 +16,7 @@ namespace AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore; /// Provides a base class for a OAuth user authentication handler. /// /// The type of the . -public abstract class OAuthUserHandler : IAuthenticationSchemeHandler +public abstract class OAuthUserHandler : IAuthenticationSchemeHandler where TParameters : OAuthUserParameters { private readonly IHttpContextAccessor _httpContextAccessor; @@ -42,7 +42,14 @@ protected OAuthUserHandler(IHttpContextAccessor httpContextAccessor, IOAuthUserT /// The . protected abstract void EnsureCompatibleScheme(AuthenticationScheme scheme); - /// + /// + /// Authenticates a with the specified OAuth user scheme and parameters. + /// + /// The . + /// The . + /// The . + /// A . + /// The asynchronous operation. public async Task AuthenticateAsync( AuthenticationScheme scheme, HttpRequestMessage request, @@ -64,4 +71,18 @@ await _authTokenService.GetAccessTokenAsync(scheme, user, parameters, cancellati request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken); } + + async Task IAuthenticationSchemeHandler.AuthenticateAsync( + AuthenticationScheme scheme, + HttpRequestMessage request, + AuthenticationParameters? parameters, + CancellationToken cancellationToken) + { + await AuthenticateAsync( + scheme, + request, + (TParameters?)parameters, + cancellationToken) + .ConfigureAwait(false); + } } \ No newline at end of file diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/OAuthUserTokenService.cs b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/OAuthUserTokenService.cs index 5faab99..a19b831 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/OAuthUserTokenService.cs +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/OAuthUserTokenService.cs @@ -20,7 +20,9 @@ namespace AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore; /// Provides the base class for see . /// /// The type of the . -public abstract class OAuthUserTokenService : IOAuthUserTokenService +public abstract class OAuthUserTokenService< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOptions> + : IOAuthUserTokenService where TOptions : OAuthUserOptions { private static readonly ConcurrentDictionary>> _sync = new(); diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.OpenIdConnect/AppCoreNet.Extensions.Http.Authentication.OAuth.OpenIdConnect.csproj b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.OpenIdConnect/AppCoreNet.Extensions.Http.Authentication.OAuth.OpenIdConnect.csproj index bf082ac..3ba2e04 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.OpenIdConnect/AppCoreNet.Extensions.Http.Authentication.OAuth.OpenIdConnect.csproj +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.OpenIdConnect/AppCoreNet.Extensions.Http.Authentication.OAuth.OpenIdConnect.csproj @@ -7,6 +7,8 @@ from ASP.NET Core OpenID connect authentication schemes. $(PackageTags);Security;Authentication;OAuth;OAuth2;OAuth 2.0;IdentityModel;ASP.NET Core;OpenID Connect + + true diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth/OAuthClientHandler.cs b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth/OAuthClientHandler.cs index e6ded6f..4e3650d 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth/OAuthClientHandler.cs +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth/OAuthClientHandler.cs @@ -12,7 +12,7 @@ namespace AppCoreNet.Extensions.Http.Authentication.OAuth; /// /// Provides a OAuth client authentication handler. /// -public class OAuthClientHandler : IAuthenticationSchemeHandler +public class OAuthClientHandler : IAuthenticationSchemeHandler { private readonly IOAuthTokenService _authTokenService; @@ -26,7 +26,14 @@ public OAuthClientHandler(IOAuthTokenService authTokenService) _authTokenService = authTokenService; } - /// + /// + /// Authenticates a with the specified OAuth client scheme and parameters. + /// + /// The . + /// The . + /// The . + /// A . + /// The asynchronous operation. public async Task AuthenticateAsync( AuthenticationScheme scheme, HttpRequestMessage request, @@ -39,4 +46,18 @@ await _authTokenService.GetClientAccessTokenAsync(scheme, parameters, cancellati request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken); } + + async Task IAuthenticationSchemeHandler.AuthenticateAsync( + AuthenticationScheme scheme, + HttpRequestMessage request, + AuthenticationParameters? parameters, + CancellationToken cancellationToken) + { + await AuthenticateAsync( + scheme, + request, + (OAuthParameters?)parameters, + cancellationToken) + .ConfigureAwait(false); + } } \ No newline at end of file diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth/OAuthOptionsResolver.cs b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth/OAuthOptionsResolver.cs index 75a46d5..5b397ef 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth/OAuthOptionsResolver.cs +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth/OAuthOptionsResolver.cs @@ -2,12 +2,15 @@ // Copyright (c) The AppCore .NET project. using System; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Microsoft.Extensions.Options; namespace AppCoreNet.Extensions.Http.Authentication.OAuth; -internal sealed class OAuthOptionsResolver : IOAuthOptionsResolver +internal sealed class OAuthOptionsResolver< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOptions> + : IOAuthOptionsResolver where TOptions : OAuthOptions { private readonly IOptionsMonitor _options; diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth/OAuthPasswordHandler.cs b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth/OAuthPasswordHandler.cs index ebfbe34..9c3a641 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth/OAuthPasswordHandler.cs +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth/OAuthPasswordHandler.cs @@ -1,4 +1,7 @@ -using System.Net.Http; +// Licensed under the MIT license. +// Copyright (c) The AppCore .NET project. + +using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; @@ -9,7 +12,7 @@ namespace AppCoreNet.Extensions.Http.Authentication.OAuth; /// /// Provides a OAuth password authentication handler. /// -public class OAuthPasswordHandler : IAuthenticationSchemeHandler +public class OAuthPasswordHandler : IAuthenticationSchemeHandler { private readonly IOAuthTokenService _authTokenService; @@ -23,7 +26,14 @@ public OAuthPasswordHandler(IOAuthTokenService authTokenService) _authTokenService = authTokenService; } - /// + /// + /// Authenticates a with the specified OAuth password scheme and parameters. + /// + /// The . + /// The . + /// The . + /// A . + /// The asynchronous operation. public async Task AuthenticateAsync( AuthenticationScheme scheme, HttpRequestMessage request, @@ -36,4 +46,18 @@ await _authTokenService.GetPasswordAccessTokenAsync(scheme, parameters, cancella request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken); } + + async Task IAuthenticationSchemeHandler.AuthenticateAsync( + AuthenticationScheme scheme, + HttpRequestMessage request, + AuthenticationParameters? parameters, + CancellationToken cancellationToken) + { + await AuthenticateAsync( + scheme, + request, + (OAuthParameters?)parameters, + cancellationToken) + .ConfigureAwait(false); + } } \ No newline at end of file diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication/AuthenticationHandler.cs b/Http/src/AppCoreNet.Extensions.Http.Authentication/AuthenticationHandler.cs index 3420d25..02f8e08 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication/AuthenticationHandler.cs +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication/AuthenticationHandler.cs @@ -16,14 +16,14 @@ namespace AppCoreNet.Extensions.Http.Authentication; /// authentication to the request. /// /// The type of the . -/// The type of the . +/// The type of the . public class AuthenticationHandler : DelegatingHandler where TParameters : AuthenticationParameters, new() - where THandler : IAuthenticationSchemeHandler + where THandler : IAuthenticationSchemeHandler { private readonly string _scheme; private readonly IAuthenticationSchemeProvider _schemes; - private readonly IAuthenticationSchemeHandler _schemeHandler; + private readonly IAuthenticationSchemeHandler _schemeHandler; private readonly TParameters? _parameters; private readonly ILogger _logger; diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication/AuthenticationScheme.cs b/Http/src/AppCoreNet.Extensions.Http.Authentication/AuthenticationScheme.cs index 2e6636a..f2dcd01 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication/AuthenticationScheme.cs +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication/AuthenticationScheme.cs @@ -17,7 +17,7 @@ public sealed class AuthenticationScheme public string Name { get; } /// - /// Gets the type of the which handles the scheme. + /// Gets the type of the which handles the scheme. /// public Type HandlerType { get; } @@ -26,22 +26,31 @@ public sealed class AuthenticationScheme /// public Type OptionsType { get; } + /// + /// Gets the type of the . + /// + public Type ParametersType { get; } + /// /// Initializes a new instance of the class. /// /// The name of the authentication scheme. - /// The type of the which handles the scheme. + /// The type of the which handles the scheme. /// The type of the . - public AuthenticationScheme(string name, Type handlerType, Type optionsType) + /// The type of the . + public AuthenticationScheme(string name, Type handlerType, Type optionsType, Type parametersType) { Ensure.Arg.NotNull(name); Ensure.Arg.NotNull(handlerType); - Ensure.Arg.OfType(handlerType, typeof(IAuthenticationSchemeHandler<>)); + Ensure.Arg.OfType(handlerType); Ensure.Arg.NotNull(optionsType); - Ensure.Arg.OfType(optionsType, typeof(AuthenticationSchemeOptions)); + Ensure.Arg.OfType(optionsType); + Ensure.Arg.NotNull(parametersType); + Ensure.Arg.OfType(parametersType); Name = name; HandlerType = handlerType; OptionsType = optionsType; + ParametersType = parametersType; } } \ No newline at end of file diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication/AuthenticationSchemeHandler.cs b/Http/src/AppCoreNet.Extensions.Http.Authentication/AuthenticationSchemeHandler.cs index 013e93c..1c0a5bc 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication/AuthenticationSchemeHandler.cs +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication/AuthenticationSchemeHandler.cs @@ -1,6 +1,7 @@ // Licensed under the MIT license. // Copyright (c) The AppCore .NET project. +using System.Diagnostics.CodeAnalysis; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -10,11 +11,13 @@ namespace AppCoreNet.Extensions.Http.Authentication; /// -/// Provides a base class for . +/// Provides a base class for . /// /// The type of the . /// The type of the . -public abstract class AuthenticationSchemeHandler : IAuthenticationSchemeHandler +public abstract class AuthenticationSchemeHandler< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOptions, TParameters> + : IAuthenticationSchemeHandler where TOptions : AuthenticationSchemeOptions where TParameters : AuthenticationParameters, new() { @@ -46,13 +49,18 @@ protected abstract Task AuthenticateAsync( HttpRequestMessage request, CancellationToken cancellationToken); - async Task IAuthenticationSchemeHandler.AuthenticateAsync( + async Task IAuthenticationSchemeHandler.AuthenticateAsync( AuthenticationScheme scheme, HttpRequestMessage request, - TParameters? parameters, + AuthenticationParameters? parameters, CancellationToken cancellationToken) { - await AuthenticateAsync(scheme, _optionsMonitor.Get(scheme.Name), parameters, request, cancellationToken) + await AuthenticateAsync( + scheme, + _optionsMonitor.Get(scheme.Name), + (TParameters?)parameters, + request, + cancellationToken) .ConfigureAwait(false); } } \ No newline at end of file diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication/DependencyInjection/AuthenticationHttpClientBuilderExtensions.cs b/Http/src/AppCoreNet.Extensions.Http.Authentication/DependencyInjection/AuthenticationHttpClientBuilderExtensions.cs index 1c28a31..245d3b2 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication/DependencyInjection/AuthenticationHttpClientBuilderExtensions.cs +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication/DependencyInjection/AuthenticationHttpClientBuilderExtensions.cs @@ -26,14 +26,14 @@ private static string GetLoggerName(IHttpClientBuilder builder) /// The authentication scheme. /// Additional authentication parameters. /// The type of the . - /// The type of the . + /// The type of the . /// The passed . public static IHttpClientBuilder AddAuthentication( this IHttpClientBuilder builder, string scheme, TParameters? parameters) where TParameters : AuthenticationParameters, new() - where THandler : IAuthenticationSchemeHandler + where THandler : IAuthenticationSchemeHandler { Ensure.Arg.NotNull(builder); Ensure.Arg.NotNull(scheme); diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication/DependencyInjection/HttpClientAuthenticationBuilderExtensions.cs b/Http/src/AppCoreNet.Extensions.Http.Authentication/DependencyInjection/HttpClientAuthenticationBuilderExtensions.cs index 378e7b0..4d95749 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication/DependencyInjection/HttpClientAuthenticationBuilderExtensions.cs +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication/DependencyInjection/HttpClientAuthenticationBuilderExtensions.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using AppCoreNet.Diagnostics; using AppCoreNet.Extensions.Http.Authentication; using Microsoft.Extensions.DependencyInjection; @@ -25,16 +26,16 @@ public static class HttpClientAuthenticationBuilderExtensions /// The option configuration delegate. /// The type of the . /// The type of the . - /// The type of the . + /// The type of the . /// The to allow chaining the calls. [EditorBrowsable(EditorBrowsableState.Never)] - public static IHttpClientAuthenticationBuilder AddScheme( + public static IHttpClientAuthenticationBuilder AddScheme( this IHttpClientAuthenticationBuilder builder, string name, Action? configure = null) where TOptions : AuthenticationSchemeOptions where TParameters : AuthenticationParameters - where THandler : class, IAuthenticationSchemeHandler + where THandler : class, IAuthenticationSchemeHandler { Ensure.Arg.NotNull(builder); Ensure.Arg.NotNull(name); @@ -42,12 +43,12 @@ public static IHttpClientAuthenticationBuilder AddScheme(); - services.TryAddTransient>( + services.TryAddTransient( sp => sp.GetRequiredService()); services.Configure( o => o - .AddScheme(name, typeof(THandler), typeof(TOptions))); + .AddScheme(name, typeof(THandler), typeof(TOptions), typeof(TParameters))); if (configure != null) { diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication/HttpClientAuthenticationOptions.cs b/Http/src/AppCoreNet.Extensions.Http.Authentication/HttpClientAuthenticationOptions.cs index 6e6de57..d89faf8 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication/HttpClientAuthenticationOptions.cs +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication/HttpClientAuthenticationOptions.cs @@ -21,10 +21,11 @@ public sealed class HttpClientAuthenticationOptions /// Adds a authentication scheme. /// /// The name of the authentication scheme. - /// The type of the . + /// The type of the . /// The type of the . - public void AddScheme(string name, Type handlerType, Type optionsType) - => SchemeMap.Add(name, new AuthenticationScheme(name, handlerType, optionsType)); + /// The type of the . + public void AddScheme(string name, Type handlerType, Type optionsType, Type parametersType) + => SchemeMap.Add(name, new AuthenticationScheme(name, handlerType, optionsType, parametersType)); /// /// Adds a authentication scheme. diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication/IAuthenticationSchemeHandler.cs b/Http/src/AppCoreNet.Extensions.Http.Authentication/IAuthenticationSchemeHandler.cs index 5e5a1d0..c68f552 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication/IAuthenticationSchemeHandler.cs +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication/IAuthenticationSchemeHandler.cs @@ -10,9 +10,7 @@ namespace AppCoreNet.Extensions.Http.Authentication; /// /// Represents a HTTP client authentication scheme handler. /// -/// The type of the . -public interface IAuthenticationSchemeHandler - where TParameters : AuthenticationParameters +public interface IAuthenticationSchemeHandler { /// /// Authenticates a with the specified scheme and parameters. @@ -25,6 +23,6 @@ public interface IAuthenticationSchemeHandler Task AuthenticateAsync( AuthenticationScheme scheme, HttpRequestMessage request, - TParameters? parameters = null, + AuthenticationParameters? parameters = null, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Http/src/Directory.Build.props b/Http/src/Directory.Build.props index 53d9690..a060869 100644 --- a/Http/src/Directory.Build.props +++ b/Http/src/Directory.Build.props @@ -3,6 +3,7 @@ true + true diff --git a/Http/test/AppCoreNet.Extensions.Http.Authentication.Tests/AuthenticationSchemeProviderTests.cs b/Http/test/AppCoreNet.Extensions.Http.Authentication.Tests/AuthenticationSchemeProviderTests.cs index f521d20..d833e88 100644 --- a/Http/test/AppCoreNet.Extensions.Http.Authentication.Tests/AuthenticationSchemeProviderTests.cs +++ b/Http/test/AppCoreNet.Extensions.Http.Authentication.Tests/AuthenticationSchemeProviderTests.cs @@ -28,13 +28,15 @@ public async Task AddsSchemesFromOptions() { var scheme1 = new AuthenticationScheme( "scheme1", - typeof(IAuthenticationSchemeHandler<>), - typeof(AuthenticationSchemeOptions)); + typeof(IAuthenticationSchemeHandler), + typeof(AuthenticationSchemeOptions), + typeof(AuthenticationParameters)); var scheme2 = new AuthenticationScheme( "scheme2", - typeof(IAuthenticationSchemeHandler<>), - typeof(AuthenticationSchemeOptions)); + typeof(IAuthenticationSchemeHandler), + typeof(AuthenticationSchemeOptions), + typeof(AuthenticationParameters)); var options = new HttpClientAuthenticationOptions(); options.AddScheme(scheme1); @@ -54,8 +56,9 @@ public void AddSchemeThrowsOnDuplicateScheme() var scheme = new AuthenticationScheme( "scheme", - typeof(IAuthenticationSchemeHandler<>), - typeof(AuthenticationSchemeOptions)); + typeof(IAuthenticationSchemeHandler), + typeof(AuthenticationSchemeOptions), + typeof(AuthenticationParameters)); provider.AddScheme(scheme); @@ -72,13 +75,15 @@ public async Task GetAllSchemesReturnsSchemes() var scheme1 = new AuthenticationScheme( "scheme1", - typeof(IAuthenticationSchemeHandler<>), - typeof(AuthenticationSchemeOptions)); + typeof(IAuthenticationSchemeHandler), + typeof(AuthenticationSchemeOptions), + typeof(AuthenticationParameters)); var scheme2 = new AuthenticationScheme( "scheme2", - typeof(IAuthenticationSchemeHandler<>), - typeof(AuthenticationSchemeOptions)); + typeof(IAuthenticationSchemeHandler), + typeof(AuthenticationSchemeOptions), + typeof(AuthenticationParameters)); provider.AddScheme(scheme1); provider.AddScheme(scheme2); @@ -95,13 +100,15 @@ public async Task FindSchemeReturnsScheme() var scheme1 = new AuthenticationScheme( "scheme1", - typeof(IAuthenticationSchemeHandler<>), - typeof(AuthenticationSchemeOptions)); + typeof(IAuthenticationSchemeHandler), + typeof(AuthenticationSchemeOptions), + typeof(AuthenticationParameters)); var scheme2 = new AuthenticationScheme( "scheme2", - typeof(IAuthenticationSchemeHandler<>), - typeof(AuthenticationSchemeOptions)); + typeof(IAuthenticationSchemeHandler), + typeof(AuthenticationSchemeOptions), + typeof(AuthenticationParameters)); provider.AddScheme(scheme1); provider.AddScheme(scheme2); From c8b8954b21514d4b5e868c7486bad7ba5ea4bc51 Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Fri, 23 May 2025 17:45:07 +0200 Subject: [PATCH 13/15] Fixed suggestions from review. --- .../AssemblyReflectionBuilderExtensions.cs | 6 +++--- .../AppCoreNet.Extensions.Http.Authentication.Tests.csproj | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyReflectionBuilderExtensions.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyReflectionBuilderExtensions.cs index 2401e3c..be20a90 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyReflectionBuilderExtensions.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyReflectionBuilderExtensions.cs @@ -37,7 +37,7 @@ public static IServiceDescriptorReflectionBuilder Assemblies( /// The . /// The delegate to configure the . /// The to allow chaining. - /// Argument is null. + /// Argument is null. public static IServiceDescriptorReflectionBuilder Assembly( this IServiceDescriptorReflectionBuilder builder, Action? configure = null) @@ -75,7 +75,7 @@ public static IFacilityReflectionBuilder Assemblies( /// The . /// The delegate to configure the . /// The to allow chaining. - /// Argument or is null. + /// Argument is null. public static IFacilityReflectionBuilder Assembly( this IFacilityReflectionBuilder builder, Action? configure = null) @@ -111,7 +111,7 @@ public static IFacilityExtensionReflectionBuilder Assemblies( /// The . /// The delegate to configure the . /// The to allow chaining. - /// Argument or is null. + /// Argument is null. public static IFacilityExtensionReflectionBuilder Assembly( this IFacilityExtensionReflectionBuilder builder, Action? configure = null) diff --git a/Http/test/AppCoreNet.Extensions.Http.Authentication.Tests/AppCoreNet.Extensions.Http.Authentication.Tests.csproj b/Http/test/AppCoreNet.Extensions.Http.Authentication.Tests/AppCoreNet.Extensions.Http.Authentication.Tests.csproj index c245989..4c44fed 100644 --- a/Http/test/AppCoreNet.Extensions.Http.Authentication.Tests/AppCoreNet.Extensions.Http.Authentication.Tests.csproj +++ b/Http/test/AppCoreNet.Extensions.Http.Authentication.Tests/AppCoreNet.Extensions.Http.Authentication.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0;net8.0 $(TargetFrameworks);net472 AppCoreNet.Extensions.Http.Authentication From ad4ce85eb8f27364353af6cbe452dbb7434006e1 Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Tue, 3 Jun 2025 21:45:30 +0200 Subject: [PATCH 14/15] Move 'IsAotCompatible' property to project files. --- ...CoreNet.Extensions.DependencyInjection.Abstractions.csproj | 1 + ...t.Extensions.DependencyInjection.AssemblyExtensions.csproj | 1 + .../AssemblyReflectionBuilderExtensions.cs | 4 ++-- ...sions.DependencyInjection.DependencyModelExtensions.csproj | 1 + DependencyInjection/src/Directory.Build.props | 1 - .../AppCoreNet.Extensions.Hosting.Abstractions.csproj | 1 + .../AppCoreNet.Extensions.Hosting.Plugins.Abstractions.csproj | 1 + ...ppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc.csproj | 1 - .../AppCoreNet.Extensions.Hosting.Plugins.csproj | 1 - .../AppCoreNet.Extensions.Hosting.csproj | 1 + Hosting/src/Directory.Build.props | 1 - ...Net.Extensions.Http.Authentication.OAuth.AspNetCore.csproj | 1 - ....Extensions.Http.Authentication.OAuth.OpenIdConnect.csproj | 1 - .../AppCoreNet.Extensions.Http.Authentication.OAuth.csproj | 1 + .../AppCoreNet.Extensions.Http.Authentication.csproj | 1 + Http/src/Directory.Build.props | 1 - 16 files changed, 10 insertions(+), 9 deletions(-) diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/AppCoreNet.Extensions.DependencyInjection.Abstractions.csproj b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/AppCoreNet.Extensions.DependencyInjection.Abstractions.csproj index 071ba02..99ae7b7 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/AppCoreNet.Extensions.DependencyInjection.Abstractions.csproj +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.Abstractions/AppCoreNet.Extensions.DependencyInjection.Abstractions.csproj @@ -4,6 +4,7 @@ net8.0;netstandard2.0;net462 AppCoreNet.Extensions.DependencyInjection Provides extensions to the Microsoft.Extensions.DependencyInjection framework. + true diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.csproj b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.csproj index 7b2b09c..f9756dc 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.csproj +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.csproj @@ -4,6 +4,7 @@ net8.0;netstandard2.0;net462 AppCoreNet.Extensions.DependencyInjection Provides assembly reflection extensions to the Microsoft.Extensions.DependencyInjection framework. + true diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyReflectionBuilderExtensions.cs b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyReflectionBuilderExtensions.cs index be20a90..e12a166 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyReflectionBuilderExtensions.cs +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions/AssemblyReflectionBuilderExtensions.cs @@ -20,10 +20,10 @@ public static class AssemblyReflectionBuilderExtensions /// The . /// The delegate to configure the . /// The to allow chaining. - /// Argument is null. + /// Argument or is null. public static IServiceDescriptorReflectionBuilder Assemblies( this IServiceDescriptorReflectionBuilder builder, - Action? configure = null) + Action configure) { Ensure.Arg.NotNull(builder); Ensure.Arg.NotNull(configure); diff --git a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions.csproj b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions.csproj index 5020fd8..44247af 100644 --- a/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions.csproj +++ b/DependencyInjection/src/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions/AppCoreNet.Extensions.DependencyInjection.DependencyModelExtensions.csproj @@ -4,6 +4,7 @@ net8.0;netstandard2.0;net462 AppCoreNet.Extensions.DependencyInjection Provides dependency context assembly reflection extensions to the Microsoft.Extensions.DependencyInjection framework. + true diff --git a/DependencyInjection/src/Directory.Build.props b/DependencyInjection/src/Directory.Build.props index a060869..53d9690 100644 --- a/DependencyInjection/src/Directory.Build.props +++ b/DependencyInjection/src/Directory.Build.props @@ -3,7 +3,6 @@ true - true diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting.Abstractions/AppCoreNet.Extensions.Hosting.Abstractions.csproj b/Hosting/src/AppCoreNet.Extensions.Hosting.Abstractions/AppCoreNet.Extensions.Hosting.Abstractions.csproj index 9def54f..3948bd7 100644 --- a/Hosting/src/AppCoreNet.Extensions.Hosting.Abstractions/AppCoreNet.Extensions.Hosting.Abstractions.csproj +++ b/Hosting/src/AppCoreNet.Extensions.Hosting.Abstractions/AppCoreNet.Extensions.Hosting.Abstractions.csproj @@ -4,6 +4,7 @@ net8.0;netstandard2.0;net462 AppCoreNet.Extensions.Hosting Provides extensions to the Microsoft.Extensions.Hosting framework. + true diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.Abstractions/AppCoreNet.Extensions.Hosting.Plugins.Abstractions.csproj b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.Abstractions/AppCoreNet.Extensions.Hosting.Plugins.Abstractions.csproj index ca03636..9a248e4 100644 --- a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.Abstractions/AppCoreNet.Extensions.Hosting.Plugins.Abstractions.csproj +++ b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.Abstractions/AppCoreNet.Extensions.Hosting.Plugins.Abstractions.csproj @@ -4,6 +4,7 @@ net8.0;netstandard2.0;net462 AppCoreNet.Extensions.Hosting.Plugins Provides plugin extensions to the Microsoft.Extensions.Hosting framework. + true diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc.csproj b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc.csproj index 9987992..b4c0d1e 100644 --- a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc.csproj +++ b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc/AppCoreNet.Extensions.Hosting.Plugins.AspNetCore.Mvc.csproj @@ -3,7 +3,6 @@ net8.0 Provides MVC plugin extensions to the Microsoft.Extensions.Hosting framework. - true diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/AppCoreNet.Extensions.Hosting.Plugins.csproj b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/AppCoreNet.Extensions.Hosting.Plugins.csproj index 4190083..c47fc95 100644 --- a/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/AppCoreNet.Extensions.Hosting.Plugins.csproj +++ b/Hosting/src/AppCoreNet.Extensions.Hosting.Plugins/AppCoreNet.Extensions.Hosting.Plugins.csproj @@ -3,7 +3,6 @@ net8.0 Provides plugin extensions to the Microsoft.Extensions.Hosting framework. - true diff --git a/Hosting/src/AppCoreNet.Extensions.Hosting/AppCoreNet.Extensions.Hosting.csproj b/Hosting/src/AppCoreNet.Extensions.Hosting/AppCoreNet.Extensions.Hosting.csproj index 8ebdc14..5e51ac3 100644 --- a/Hosting/src/AppCoreNet.Extensions.Hosting/AppCoreNet.Extensions.Hosting.csproj +++ b/Hosting/src/AppCoreNet.Extensions.Hosting/AppCoreNet.Extensions.Hosting.csproj @@ -3,6 +3,7 @@ net8.0;netstandard2.0;net462 Provides extensions to the Microsoft.Extensions.Hosting framework. + true diff --git a/Hosting/src/Directory.Build.props b/Hosting/src/Directory.Build.props index a060869..53d9690 100644 --- a/Hosting/src/Directory.Build.props +++ b/Hosting/src/Directory.Build.props @@ -3,7 +3,6 @@ true - true diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore.csproj b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore.csproj index f04f0ca..7e2062e 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore.csproj +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore/AppCoreNet.Extensions.Http.Authentication.OAuth.AspNetCore.csproj @@ -7,7 +7,6 @@ from ASP.NET Core authentication schemes. $(PackageTags);Security;Authentication;OAuth;OAuth2;OAuth 2.0;IdentityModel;ASP.NET Core - true diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.OpenIdConnect/AppCoreNet.Extensions.Http.Authentication.OAuth.OpenIdConnect.csproj b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.OpenIdConnect/AppCoreNet.Extensions.Http.Authentication.OAuth.OpenIdConnect.csproj index 3ba2e04..989b5d8 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.OpenIdConnect/AppCoreNet.Extensions.Http.Authentication.OAuth.OpenIdConnect.csproj +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth.OpenIdConnect/AppCoreNet.Extensions.Http.Authentication.OAuth.OpenIdConnect.csproj @@ -7,7 +7,6 @@ from ASP.NET Core OpenID connect authentication schemes. $(PackageTags);Security;Authentication;OAuth;OAuth2;OAuth 2.0;IdentityModel;ASP.NET Core;OpenID Connect - true diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth/AppCoreNet.Extensions.Http.Authentication.OAuth.csproj b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth/AppCoreNet.Extensions.Http.Authentication.OAuth.csproj index f7883c1..fa8520d 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth/AppCoreNet.Extensions.Http.Authentication.OAuth.csproj +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication.OAuth/AppCoreNet.Extensions.Http.Authentication.OAuth.csproj @@ -6,6 +6,7 @@ Adds support to AppCoreNet.Extensions.Http.Authenticaton for authenticating a HttpClient using OAuth2 bearer tokens. $(PackageTags);Security;Authentication;OAuth;OAuth2;OAuth 2.0;IdentityModel + true diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication/AppCoreNet.Extensions.Http.Authentication.csproj b/Http/src/AppCoreNet.Extensions.Http.Authentication/AppCoreNet.Extensions.Http.Authentication.csproj index ebc9eab..79a3e18 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication/AppCoreNet.Extensions.Http.Authentication.csproj +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication/AppCoreNet.Extensions.Http.Authentication.csproj @@ -7,6 +7,7 @@ using different authentication standards. $(PackageTags);Security;Authentication + true diff --git a/Http/src/Directory.Build.props b/Http/src/Directory.Build.props index a060869..53d9690 100644 --- a/Http/src/Directory.Build.props +++ b/Http/src/Directory.Build.props @@ -3,7 +3,6 @@ true - true From dd0f250d2ce5cd01adf0699b45d5cfc6eaa74595 Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Fri, 20 Jun 2025 17:01:29 +0200 Subject: [PATCH 15/15] Add AOT analyzers to tests. --- .../AssemblyResolverTests.cs | 8 +++----- .../AssemblyScannerTests.cs | 5 +++-- .../AssemblyServicesDescriptorResolverTests.cs | 2 ++ .../Facilities/FacilityBuilderTests.cs | 6 +----- DependencyInjection/test/Directory.Build.props | 1 + .../PluginFacilityResolverTests.cs | 2 ++ .../PluginManagerTests.cs | 2 ++ .../PluginServiceDescriptorResolverTests.cs | 2 ++ Hosting/test/Directory.Build.props | 1 + .../AuthenticationParameters.cs | 9 +++++---- Http/test/Directory.Build.props | 1 + 11 files changed, 23 insertions(+), 16 deletions(-) diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyResolverTests.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyResolverTests.cs index 42406a0..b8c7601 100644 --- a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyResolverTests.cs +++ b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyResolverTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using AppCoreNet.Extensions.DependencyInjection.Activator; using AppCoreNet.Extensions.DependencyInjection.Facilities; @@ -12,17 +13,14 @@ namespace AppCoreNet.Extensions.DependencyInjection; +[RequiresUnreferencedCode("This test requires types that may be trimmed by the linker.")] public class AssemblyResolverTests { private readonly IActivator _activator; public AssemblyResolverTests() { - var activator = Substitute.For(); - activator.CreateInstance(Arg.Any(), Arg.Any()) - .Returns(ci => System.Activator.CreateInstance(ci.ArgAt(0))); - - _activator = activator; + _activator = DefaultActivator.Instance; } [Fact] diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyScannerTests.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyScannerTests.cs index 6e7ba3d..dae3a1b 100644 --- a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyScannerTests.cs +++ b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyScannerTests.cs @@ -1,6 +1,7 @@ // Licensed under the MIT license. // Copyright (c) The AppCore .NET project. +using System.Diagnostics.CodeAnalysis; using System.Reflection; using FluentAssertions; using Xunit; @@ -8,13 +9,13 @@ namespace AppCoreNet.Extensions.DependencyInjection; +[RequiresUnreferencedCode("This test requires types that may be trimmed by the linker.")] public class AssemblyScannerTests { [Fact] public void FindsAllImplementationsOfInterface() { - var scanner = new AssemblyScanner( - typeof(IContract)); + var scanner = new AssemblyScanner(typeof(IContract)); scanner.Filters.Clear(); scanner.Assemblies.Add(typeof(AssemblyScannerTests).GetTypeInfo().Assembly); diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyServicesDescriptorResolverTests.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyServicesDescriptorResolverTests.cs index 63b35b0..32e567c 100644 --- a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyServicesDescriptorResolverTests.cs +++ b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.AssemblyExtensions.Tests/AssemblyServicesDescriptorResolverTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -10,6 +11,7 @@ namespace AppCoreNet.Extensions.DependencyInjection; +[RequiresUnreferencedCode("This test requires types that may be trimmed by the linker.")] public class AssemblyServicesDescriptorResolverTests { [Fact] diff --git a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.Tests/Facilities/FacilityBuilderTests.cs b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.Tests/Facilities/FacilityBuilderTests.cs index 5afc98b..a3f4973 100644 --- a/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.Tests/Facilities/FacilityBuilderTests.cs +++ b/DependencyInjection/test/AppCoreNet.Extensions.DependencyInjection.Tests/Facilities/FacilityBuilderTests.cs @@ -18,11 +18,7 @@ public class FacilityBuilderTests public FacilityBuilderTests() { - var activator = Substitute.For(); - activator.CreateInstance(Arg.Any(), Arg.Any()) - .Returns(ci => System.Activator.CreateInstance(ci.ArgAt(0), ci.ArgAt(1))); - - _activator = activator; + _activator = DefaultActivator.Instance; _services = new ServiceCollection(); } diff --git a/DependencyInjection/test/Directory.Build.props b/DependencyInjection/test/Directory.Build.props index 6e1c272..e359518 100644 --- a/DependencyInjection/test/Directory.Build.props +++ b/DependencyInjection/test/Directory.Build.props @@ -4,6 +4,7 @@ false $(NoWarn);VSTHRD200 + true diff --git a/Hosting/test/AppCoreNet.Extensions.Hosting.Plugins.Tests/PluginFacilityResolverTests.cs b/Hosting/test/AppCoreNet.Extensions.Hosting.Plugins.Tests/PluginFacilityResolverTests.cs index f57489c..8a5ae99 100644 --- a/Hosting/test/AppCoreNet.Extensions.Hosting.Plugins.Tests/PluginFacilityResolverTests.cs +++ b/Hosting/test/AppCoreNet.Extensions.Hosting.Plugins.Tests/PluginFacilityResolverTests.cs @@ -2,6 +2,7 @@ // Copyright (c) The AppCore .NET project. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using AppCoreNet.Extensions.DependencyInjection; using FluentAssertions; @@ -10,6 +11,7 @@ namespace AppCoreNet.Extensions.Hosting.Plugins; +[RequiresUnreferencedCode("This test requires types that may be trimmed by the linker.")] public class PluginFacilityResolverTests { #nullable disable diff --git a/Hosting/test/AppCoreNet.Extensions.Hosting.Plugins.Tests/PluginManagerTests.cs b/Hosting/test/AppCoreNet.Extensions.Hosting.Plugins.Tests/PluginManagerTests.cs index 63ec3d9..5447ed0 100644 --- a/Hosting/test/AppCoreNet.Extensions.Hosting.Plugins.Tests/PluginManagerTests.cs +++ b/Hosting/test/AppCoreNet.Extensions.Hosting.Plugins.Tests/PluginManagerTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using AppCoreNet.Extensions.DependencyInjection.Activator; @@ -14,6 +15,7 @@ namespace AppCoreNet.Extensions.Hosting.Plugins; +[RequiresUnreferencedCode("This test requires types that may be trimmed by the linker.")] public class PluginManagerTests { [Fact] diff --git a/Hosting/test/AppCoreNet.Extensions.Hosting.Plugins.Tests/PluginServiceDescriptorResolverTests.cs b/Hosting/test/AppCoreNet.Extensions.Hosting.Plugins.Tests/PluginServiceDescriptorResolverTests.cs index 485bb3b..2923005 100644 --- a/Hosting/test/AppCoreNet.Extensions.Hosting.Plugins.Tests/PluginServiceDescriptorResolverTests.cs +++ b/Hosting/test/AppCoreNet.Extensions.Hosting.Plugins.Tests/PluginServiceDescriptorResolverTests.cs @@ -2,6 +2,7 @@ // Copyright (c) The AppCore .NET project. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using AppCoreNet.Extensions.DependencyInjection; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; @@ -9,6 +10,7 @@ namespace AppCoreNet.Extensions.Hosting.Plugins; +[RequiresUnreferencedCode("This test requires types that may be trimmed by the linker.")] public class PluginServiceDescriptorResolverTests { #nullable disable diff --git a/Hosting/test/Directory.Build.props b/Hosting/test/Directory.Build.props index 6e1c272..e359518 100644 --- a/Hosting/test/Directory.Build.props +++ b/Hosting/test/Directory.Build.props @@ -4,6 +4,7 @@ false $(NoWarn);VSTHRD200 + true diff --git a/Http/src/AppCoreNet.Extensions.Http.Authentication/AuthenticationParameters.cs b/Http/src/AppCoreNet.Extensions.Http.Authentication/AuthenticationParameters.cs index abce860..fc3444e 100644 --- a/Http/src/AppCoreNet.Extensions.Http.Authentication/AuthenticationParameters.cs +++ b/Http/src/AppCoreNet.Extensions.Http.Authentication/AuthenticationParameters.cs @@ -12,7 +12,7 @@ namespace AppCoreNet.Extensions.Http.Authentication; /// public class AuthenticationParameters { - private IDictionary _parameters; + private readonly IDictionary _parameters; /// /// Gets or sets a value indicating whether to force renewing the authentication. @@ -49,10 +49,11 @@ protected AuthenticationParameters(IDictionary parameters) public T Clone() where T : AuthenticationParameters, new() { - var clone = new T + var clone = new T(); + foreach (KeyValuePair parameter in _parameters) { - _parameters = new Dictionary(_parameters, StringComparer.Ordinal), - }; + clone._parameters[parameter.Key] = parameter.Value; + } return clone; } diff --git a/Http/test/Directory.Build.props b/Http/test/Directory.Build.props index 6e1c272..e359518 100644 --- a/Http/test/Directory.Build.props +++ b/Http/test/Directory.Build.props @@ -4,6 +4,7 @@ false $(NoWarn);VSTHRD200 + true