From f424c0f7ce8b1453d7124a3def8cf44a4ff42ead Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Tue, 10 Jan 2023 13:57:11 -0600 Subject: [PATCH] Remove RequiresDynamicCode from Microsoft.Extensions.DependencyInjection (#79425) * Remove RequiresDynamicCode from Microsoft.Extensions.DependencyInjection We need a better approach in order to support applications that use DependencyInjection and publish for NativeAOT. DependencyInjection needs to have reliable behavior before and after publishing for NativeAOT. The application can't work successfully at development-time, but then fail after publishing with PublishAot=true. We will resolve the 2 NativeAOT warnings above by adding a runtime check that is behind the new AppContext switch added in https://github.com/dotnet/runtime/pull/80246 (`System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported`). The runtime check ensures the Types being used with Enumerable and Open Generic services are only Reference Types. If an application tries to create an Enumerable or Closed Generic service of a ValueType, DependencyInjection will throw an exception. The check is enabled by default when PublishAot=true. Fix #79286 --- ...icrosoft.Extensions.DependencyInjection.cs | 4 - ...soft.Extensions.DependencyInjection.csproj | 4 - .../src/DefaultServiceProviderFactory.cs | 2 - .../src/Resources/Strings.resx | 8 +- ...iceCollectionContainerBuilderExtensions.cs | 7 -- .../src/ServiceLookup/CallSiteFactory.cs | 38 ++++++++- .../ServiceLookup/CallSiteRuntimeResolver.cs | 13 ++- .../DynamicServiceProviderEngine.cs | 4 +- .../Expressions/ExpressionResolverBuilder.cs | 13 ++- .../ExpressionsServiceProviderEngine.cs | 1 - .../src/ServiceLookup/IEnumerableCallSite.cs | 14 +++- .../RuntimeServiceProviderEngine.cs | 1 - .../src/ServiceProvider.cs | 20 +++-- .../ServiceLookup/CallSiteFactoryTest.cs | 81 ++++++++++++++++++- .../ref/Microsoft.Extensions.Hosting.cs | 11 --- .../ref/Microsoft.Extensions.Hosting.csproj | 4 - .../Microsoft.Extensions.Hosting/src/Host.cs | 7 -- .../src/HostApplicationBuilder.cs | 4 - .../src/HostBuilder.cs | 1 - .../src/HostingHostBuilderExtensions.cs | 3 - .../src/Microsoft.Extensions.Hosting.csproj | 4 - .../ref/Microsoft.Extensions.Logging.cs | 1 - .../ref/Microsoft.Extensions.Logging.csproj | 4 - .../src/LoggerFactory.cs | 1 - .../src/Microsoft.Extensions.Logging.csproj | 4 - 25 files changed, 171 insertions(+), 83 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.cs index 92a159382c79f..0b85156d60885 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.cs @@ -6,7 +6,6 @@ namespace Microsoft.Extensions.DependencyInjection { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")] public partial class DefaultServiceProviderFactory : Microsoft.Extensions.DependencyInjection.IServiceProviderFactory { public DefaultServiceProviderFactory() { } @@ -16,11 +15,8 @@ public DefaultServiceProviderFactory(Microsoft.Extensions.DependencyInjection.Se } public static partial class ServiceCollectionContainerBuilderExtensions { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")] public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")] public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.Extensions.DependencyInjection.ServiceProviderOptions options) { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")] public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, bool validateScopes) { throw null; } } public sealed partial class ServiceProvider : System.IAsyncDisposable, System.IDisposable, System.IServiceProvider diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.csproj b/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.csproj index 1be6963319f1b..522569cbc5a0e 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.csproj +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.csproj @@ -12,10 +12,6 @@ - - - - diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/DefaultServiceProviderFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/DefaultServiceProviderFactory.cs index 13863a4618a7d..7fe53e3aa8551 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/DefaultServiceProviderFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/DefaultServiceProviderFactory.cs @@ -2,14 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Diagnostics.CodeAnalysis; namespace Microsoft.Extensions.DependencyInjection { /// /// Default implementation of . /// - [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] public class DefaultServiceProviderFactory : IServiceProviderFactory { private readonly ServiceProviderOptions _options; diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/Resources/Strings.resx b/src/libraries/Microsoft.Extensions.DependencyInjection/src/Resources/Strings.resx index efbb7cc4a7451..a81b637ecf6b2 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/Resources/Strings.resx +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/Resources/Strings.resx @@ -180,4 +180,10 @@ Generic implementation type '{0}' has a DefaultConstructorConstraint ('new()' constraint), but the generic service type '{1}' doesn't. - + + Unable to create an Enumerable service of type '{0}' because it is a ValueType. Native code to support creating Enumerable services might not be available with native AOT. + + + Unable to create a generic service for type '{0}' because '{1}' is a ValueType. Native code to support creating generic services might not be available with native AOT. + + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceCollectionContainerBuilderExtensions.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceCollectionContainerBuilderExtensions.cs index 1cde6c4c69029..5b12c82b9d6e6 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceCollectionContainerBuilderExtensions.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceCollectionContainerBuilderExtensions.cs @@ -2,9 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using Microsoft.Extensions.DependencyInjection.ServiceLookup; namespace Microsoft.Extensions.DependencyInjection { @@ -18,8 +15,6 @@ public static class ServiceCollectionContainerBuilderExtensions /// /// The containing service descriptors. /// The . - - [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] public static ServiceProvider BuildServiceProvider(this IServiceCollection services) { return BuildServiceProvider(services, ServiceProviderOptions.Default); @@ -34,7 +29,6 @@ public static ServiceProvider BuildServiceProvider(this IServiceCollection servi /// true to perform check verifying that scoped services never gets resolved from root provider; otherwise false. /// /// The . - [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] public static ServiceProvider BuildServiceProvider(this IServiceCollection services, bool validateScopes) { return services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = validateScopes }); @@ -49,7 +43,6 @@ public static ServiceProvider BuildServiceProvider(this IServiceCollection servi /// Configures various service provider behaviors. /// /// The . - [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options) { if (services is null) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index 22b3d4fd153ad..2e8527fb5f7e2 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -11,7 +11,6 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { - [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] internal sealed class CallSiteFactory : IServiceProviderIsService { private const int DefaultSlot = 0; @@ -244,8 +243,14 @@ private static bool AreCompatible(DynamicallyAccessedMemberTypes serviceDynamica serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) { Type itemType = serviceType.GenericTypeArguments[0]; - CallSiteResultCacheLocation cacheLocation = CallSiteResultCacheLocation.Root; + if (ServiceProvider.VerifyAotCompatibility && itemType.IsValueType) + { + // NativeAOT apps are not able to make Enumerable of ValueType services + // since there is no guarantee the ValueType[] code has been generated. + throw new InvalidOperationException(SR.Format(SR.AotCannotCreateEnumerableValueType, itemType)); + } + CallSiteResultCacheLocation cacheLocation = CallSiteResultCacheLocation.Root; var callSites = new List(); // If item type is not generic we can safely use descriptor cache @@ -350,6 +355,9 @@ private static CallSiteResultCacheLocation GetCommonCacheLocation(CallSiteResult Justification = "MakeGenericType here is used to create a closed generic implementation type given the closed service type. " + "Trimming annotations on the generic types are verified when 'Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability' is set, which is set by default when PublishTrimmed=true. " + "That check informs developers when these generic types don't have compatible trimming annotations.")] + [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", + Justification = "When ServiceProvider.VerifyAotCompatibility is true, which it is by default when PublishAot=true, " + + "this method ensures the generic types being created aren't using ValueTypes.")] private ServiceCallSite? TryCreateOpenGeneric(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot, bool throwOnConstraintViolation) { if (serviceType.IsConstructedGenericType && @@ -366,7 +374,13 @@ private static CallSiteResultCacheLocation GetCommonCacheLocation(CallSiteResult Type closedType; try { - closedType = descriptor.ImplementationType.MakeGenericType(serviceType.GenericTypeArguments); + Type[] genericTypeArguments = serviceType.GenericTypeArguments; + if (ServiceProvider.VerifyAotCompatibility) + { + VerifyOpenGenericAotCompatibility(serviceType, genericTypeArguments); + } + + closedType = descriptor.ImplementationType.MakeGenericType(genericTypeArguments); } catch (ArgumentException) { @@ -524,6 +538,24 @@ private ServiceCallSite CreateConstructorCallSite( return parameterCallSites; } + /// + /// Verifies none of the generic type arguments are ValueTypes. + /// + /// + /// NativeAOT apps are not guaranteed that the native code for the closed generic of ValueType + /// has been generated. To catch these problems early, this verification is enabled at development-time + /// to inform the developer early that this scenario will not work once AOT'd. + /// + private static void VerifyOpenGenericAotCompatibility(Type serviceType, Type[] genericTypeArguments) + { + foreach (Type typeArg in genericTypeArguments) + { + if (typeArg.IsValueType) + { + throw new InvalidOperationException(SR.Format(SR.AotCannotCreateGenericValueType, serviceType, typeArg)); + } + } + } public void Add(Type type, ServiceCallSite serviceCallSite) { diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteRuntimeResolver.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteRuntimeResolver.cs index f41e18238bcf6..08c74b10b26b4 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteRuntimeResolver.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteRuntimeResolver.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.ExceptionServices; @@ -10,7 +11,6 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { - [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] internal sealed class CallSiteRuntimeResolver : CallSiteVisitor { public static CallSiteRuntimeResolver Instance { get; } = new(); @@ -162,7 +162,7 @@ protected override object VisitServiceProvider(ServiceProviderCallSite servicePr protected override object VisitIEnumerable(IEnumerableCallSite enumerableCallSite, RuntimeResolverContext context) { - var array = Array.CreateInstance( + Array array = CreateArray( enumerableCallSite.ItemType, enumerableCallSite.ServiceCallSites.Length); @@ -172,6 +172,15 @@ protected override object VisitIEnumerable(IEnumerableCallSite enumerableCallSit array.SetValue(value, index); } return array; + + [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", + Justification = "VerifyAotCompatibility ensures elementType is not a ValueType")] + static Array CreateArray(Type elementType, int length) + { + Debug.Assert(!ServiceProvider.VerifyAotCompatibility || !elementType.IsValueType, "VerifyAotCompatibility=true will throw during building the IEnumerableCallSite if elementType is a ValueType."); + + return Array.CreateInstance(elementType, length); + } } protected override object VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/DynamicServiceProviderEngine.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/DynamicServiceProviderEngine.cs index 45fe2c39ddf24..d7aa44d8487fb 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/DynamicServiceProviderEngine.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/DynamicServiceProviderEngine.cs @@ -8,12 +8,12 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { - [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] internal sealed class DynamicServiceProviderEngine : CompiledServiceProviderEngine { private readonly ServiceProvider _serviceProvider; - public DynamicServiceProviderEngine(ServiceProvider serviceProvider): base(serviceProvider) + [RequiresDynamicCode("Creates DynamicMethods")] + public DynamicServiceProviderEngine(ServiceProvider serviceProvider) : base(serviceProvider) { _serviceProvider = serviceProvider; } diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionResolverBuilder.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionResolverBuilder.cs index c8d4b7a6c57d8..af1cf5714c84e 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionResolverBuilder.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionResolverBuilder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; @@ -11,7 +12,6 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { - [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] internal sealed class ExpressionResolverBuilder : CallSiteVisitor { private static readonly ParameterExpression ScopeParameter = Expression.Parameter(typeof(ServiceProviderEngineScope)); @@ -114,10 +114,19 @@ protected override Expression VisitFactory(FactoryCallSite factoryCallSite, obje protected override Expression VisitIEnumerable(IEnumerableCallSite callSite, object? context) { + [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", + Justification = "VerifyAotCompatibility ensures elementType is not a ValueType")] + static MethodInfo GetArrayEmptyMethodInfo(Type elementType) + { + Debug.Assert(!ServiceProvider.VerifyAotCompatibility || !elementType.IsValueType, "VerifyAotCompatibility=true will throw during building the IEnumerableCallSite if elementType is a ValueType."); + + return ServiceLookupHelpers.GetArrayEmptyMethodInfo(elementType); + } + if (callSite.ServiceCallSites.Length == 0) { return Expression.Constant( - ServiceLookupHelpers.GetArrayEmptyMethodInfo(callSite.ItemType) + GetArrayEmptyMethodInfo(callSite.ItemType) .Invoke(obj: null, parameters: Array.Empty())); } diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionsServiceProviderEngine.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionsServiceProviderEngine.cs index d179cb6f36a4e..8298bd30be3a3 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionsServiceProviderEngine.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionsServiceProviderEngine.cs @@ -10,7 +10,6 @@ internal sealed class ExpressionsServiceProviderEngine : ServiceProviderEngine { private readonly ExpressionResolverBuilder _expressionResolverBuilder; - [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] public ExpressionsServiceProviderEngine(ServiceProvider serviceProvider) { _expressionResolverBuilder = new ExpressionResolverBuilder(serviceProvider); diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/IEnumerableCallSite.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/IEnumerableCallSite.cs index 5774a2c3212fc..75657584fd75f 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/IEnumerableCallSite.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/IEnumerableCallSite.cs @@ -3,11 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { - [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] internal sealed class IEnumerableCallSite : ServiceCallSite { internal Type ItemType { get; } @@ -15,12 +15,22 @@ internal sealed class IEnumerableCallSite : ServiceCallSite public IEnumerableCallSite(ResultCache cache, Type itemType, ServiceCallSite[] serviceCallSites) : base(cache) { + Debug.Assert(!ServiceProvider.VerifyAotCompatibility || !itemType.IsValueType, "If VerifyAotCompatibility=true, an IEnumerableCallSite should not be created with a ValueType."); + ItemType = itemType; ServiceCallSites = serviceCallSites; } + [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", + Justification = "When ServiceProvider.VerifyAotCompatibility is true, which it is by default when PublishAot=true, " + + "CallSiteFactory ensures ItemType is not a ValueType.")] public override Type ServiceType => typeof(IEnumerable<>).MakeGenericType(ItemType); - public override Type ImplementationType => ItemType.MakeArrayType(); + + [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", + Justification = "When ServiceProvider.VerifyAotCompatibility is true, which it is by default when PublishAot=true, " + + "CallSiteFactory ensures ItemType is not a ValueType.")] + public override Type ImplementationType => ItemType.MakeArrayType(); + public override CallSiteKind Kind { get; } = CallSiteKind.IEnumerable; } } diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/RuntimeServiceProviderEngine.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/RuntimeServiceProviderEngine.cs index b25e7f8216d0b..c8d099c6f4ce7 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/RuntimeServiceProviderEngine.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/RuntimeServiceProviderEngine.cs @@ -6,7 +6,6 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { - [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] internal sealed class RuntimeServiceProviderEngine : ServiceProviderEngine { public static RuntimeServiceProviderEngine Instance { get; } = new RuntimeServiceProviderEngine(); diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs index dd9b1af11a55a..5954d086072db 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs @@ -16,8 +16,6 @@ namespace Microsoft.Extensions.DependencyInjection /// public sealed class ServiceProvider : IServiceProvider, IDisposable, IAsyncDisposable { - internal const string RequiresDynamicCodeMessage = "Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services."; - private readonly CallSiteValidator? _callSiteValidator; private readonly Func> _createServiceAccessor; @@ -36,7 +34,13 @@ public sealed class ServiceProvider : IServiceProvider, IDisposable, IAsyncDispo internal static bool VerifyOpenGenericServiceTrimmability { get; } = AppContext.TryGetSwitch("Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability", out bool verifyOpenGenerics) ? verifyOpenGenerics : false; - [RequiresDynamicCode(RequiresDynamicCodeMessage)] + internal static bool VerifyAotCompatibility => +#if NETFRAMEWORK || NETSTANDARD2_0 + false; +#else + !RuntimeFeature.IsDynamicCodeSupported; +#endif + internal ServiceProvider(ICollection serviceDescriptors, ServiceProviderOptions options) { // note that Root needs to be set before calling GetEngine(), because the engine may need to access Root @@ -157,7 +161,6 @@ private void ValidateService(ServiceDescriptor descriptor) } } - [RequiresDynamicCode(RequiresDynamicCodeMessage)] private Func CreateServiceAccessor(Type serviceType) { ServiceCallSite? callSite = CallSiteFactory.GetCallSite(serviceType, new CallSiteChain()); @@ -194,17 +197,16 @@ internal IServiceScope CreateScope() return new ServiceProviderEngineScope(this, isRootScope: false); } - [RequiresDynamicCode(RequiresDynamicCodeMessage)] private ServiceProviderEngine GetEngine() { ServiceProviderEngine engine; #if NETFRAMEWORK || NETSTANDARD2_0 - engine = new DynamicServiceProviderEngine(this); + engine = CreateDynamicEngine(); #else if (RuntimeFeature.IsDynamicCodeCompiled) { - engine = new DynamicServiceProviderEngine(this); + engine = CreateDynamicEngine(); } else { @@ -213,6 +215,10 @@ private ServiceProviderEngine GetEngine() } #endif return engine; + + [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", + Justification = "CreateDynamicEngine won't be called when using NativeAOT.")] // see also https://github.com/dotnet/linker/issues/2715 + ServiceProviderEngine CreateDynamicEngine() => new DynamicServiceProviderEngine(this); } } } diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs index c304f1911db38..bef850f489fba 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs @@ -919,6 +919,84 @@ public void VerifyOpenGenericTrimmabilityChecks() }, options); } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] // RuntimeConfigurationOptions are not supported on .NET Framework (and neither is NativeAOT) + public void VerifyDynamicCodeNotSupportedChecks() + { + Func CreateAotCompatibilityCallSiteFactory() + { + ServiceDescriptor[] descriptors = new[] + { + new ServiceDescriptor(typeof(IFakeOpenGenericService<>), typeof(ClassWithNoConstraints<>), ServiceLifetime.Transient), + new ServiceDescriptor(typeof(IServiceWithTwoGenerics<,>), typeof(ServiceWithTwoGenericsValid<,>), ServiceLifetime.Transient), + + new ServiceDescriptor(typeof(Struct1), new Struct1(1)), + new ServiceDescriptor(typeof(Struct1), new Struct1(2)), + }; + + return GetCallSiteFactory(descriptors); + } + + RemoteInvokeOptions options = new RemoteInvokeOptions(); + options.RuntimeConfigurationOptions.Add("System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported", "false"); + + using RemoteInvokeHandle remoteHandle = RemoteExecutor.Invoke(() => + { + Func callSiteFactory = CreateAotCompatibilityCallSiteFactory(); + + // Verify open generics throw when passing ValueTypes + Assert.Throws(() => callSiteFactory(typeof(IFakeOpenGenericService))); + Assert.Throws(() => callSiteFactory(typeof(IFakeOpenGenericService))); + Assert.Throws(() => callSiteFactory(typeof(IServiceWithTwoGenerics))); + Assert.Throws(() => callSiteFactory(typeof(IServiceWithTwoGenerics))); + + ServiceCallSite callSite = callSiteFactory(typeof(IFakeOpenGenericService)); + Assert.Equal(CallSiteKind.Constructor, callSite.Kind); + Assert.Equal(typeof(ClassWithNoConstraints), callSite.ImplementationType); + + callSite = callSiteFactory(typeof(IServiceWithTwoGenerics)); + Assert.Equal(CallSiteKind.Constructor, callSite.Kind); + Assert.Equal(typeof(ServiceWithTwoGenericsValid), callSite.ImplementationType); + + // Verify Enumerable services throw when passing ValueTypes + Assert.Throws(() => callSiteFactory(typeof(IEnumerable))); + + callSite = callSiteFactory(typeof(Struct1)); + Assert.Equal(CallSiteKind.Constant, callSite.Kind); + Assert.Equal(2, ((Struct1)callSite.Value).Value); + }, options); + + // Verify the above scenarios work when IsDynamicCodeSupported is not set + Func callSiteFactory = CreateAotCompatibilityCallSiteFactory(); + + // Open Generics + ServiceCallSite callSite = callSiteFactory(typeof(IFakeOpenGenericService)); + Assert.Equal(CallSiteKind.Constructor, callSite.Kind); + Assert.Equal(typeof(ClassWithNoConstraints), callSite.ImplementationType); + + callSite = callSiteFactory(typeof(IFakeOpenGenericService)); + Assert.Equal(CallSiteKind.Constructor, callSite.Kind); + Assert.Equal(typeof(ClassWithNoConstraints), callSite.ImplementationType); + + callSite = callSiteFactory(typeof(IServiceWithTwoGenerics)); + Assert.Equal(CallSiteKind.Constructor, callSite.Kind); + Assert.Equal(typeof(ServiceWithTwoGenericsValid), callSite.ImplementationType); + + callSite = callSiteFactory(typeof(IServiceWithTwoGenerics)); + Assert.Equal(CallSiteKind.Constructor, callSite.Kind); + Assert.Equal(typeof(ServiceWithTwoGenericsValid), callSite.ImplementationType); + + // Enumerable + callSite = callSiteFactory(typeof(IEnumerable)); + Assert.Equal(CallSiteKind.IEnumerable, callSite.Kind); + IEnumerableCallSite enumerableCallSite = (IEnumerableCallSite)callSite; + Assert.Equal(2, enumerableCallSite.ServiceCallSites.Length); + Assert.Equal(CallSiteKind.Constant, enumerableCallSite.ServiceCallSites[0].Kind); + Assert.Equal(1, ((Struct1)enumerableCallSite.ServiceCallSites[0].Value).Value); + Assert.Equal(CallSiteKind.Constant, enumerableCallSite.ServiceCallSites[1].Kind); + Assert.Equal(2, ((Struct1)enumerableCallSite.ServiceCallSites[1].Value).Value); + } + private static Func GetCallSiteFactory(params ServiceDescriptor[] descriptors) { var collection = new ServiceCollection(); @@ -944,13 +1022,14 @@ private static ConstructorInfo GetConstructor(Type type, Type[] parameterTypes) c.GetParameters().Select(p => p.ParameterType), parameterTypes)); - private class Class1 { public Class1(Class2 c2) { } } private class Class2 { public Class2(Class3 c3) { } } private class Class3 { } private class Class4 { public Class4(Class3 c3) { } } private class Class5 { public Class5(Class2 c2) { } } + private record struct Struct1(int Value) { } + // Open generic private class ClassA { public ClassA(ClassB cb) { } } private class ClassB { public ClassB(ClassC cc) { } } diff --git a/src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.cs b/src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.cs index 7910670857169..cc1d7d84b9bd1 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.cs @@ -25,22 +25,15 @@ public ConsoleLifetimeOptions() { } } public static partial class Host { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")] public static Microsoft.Extensions.Hosting.HostApplicationBuilder CreateApplicationBuilder() { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")] public static Microsoft.Extensions.Hosting.HostApplicationBuilder CreateApplicationBuilder(string[]? args) { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")] public static Microsoft.Extensions.Hosting.IHostBuilder CreateDefaultBuilder() { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")] public static Microsoft.Extensions.Hosting.IHostBuilder CreateDefaultBuilder(string[]? args) { throw null; } } public sealed partial class HostApplicationBuilder { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")] public HostApplicationBuilder() { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")] public HostApplicationBuilder(Microsoft.Extensions.Hosting.HostApplicationBuilderSettings? settings) { } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")] public HostApplicationBuilder(string[]? args) { } public Microsoft.Extensions.Configuration.ConfigurationManager Configuration { get { throw null; } } public Microsoft.Extensions.Hosting.IHostEnvironment Environment { get { throw null; } } @@ -61,7 +54,6 @@ public HostApplicationBuilderSettings() { } } public partial class HostBuilder : Microsoft.Extensions.Hosting.IHostBuilder { - [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")] public HostBuilder() { } public System.Collections.Generic.IDictionary Properties { get { throw null; } } public Microsoft.Extensions.Hosting.IHost Build() { throw null; } @@ -76,7 +68,6 @@ public static partial class HostingHostBuilderExtensions { public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureAppConfiguration(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action configureDelegate) { throw null; } public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureContainer(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action configureDelegate) { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")] public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureDefaults(this Microsoft.Extensions.Hosting.IHostBuilder builder, string[]? args) { throw null; } public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureHostOptions(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action configureOptions) { throw null; } public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureHostOptions(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action configureOptions) { throw null; } @@ -104,9 +95,7 @@ public static partial class HostingHostBuilderExtensions [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] public static Microsoft.Extensions.Hosting.IHostBuilder UseConsoleLifetime(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action configureOptions) { throw null; } public static Microsoft.Extensions.Hosting.IHostBuilder UseContentRoot(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, string contentRoot) { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")] public static Microsoft.Extensions.Hosting.IHostBuilder UseDefaultServiceProvider(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action configure) { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")] public static Microsoft.Extensions.Hosting.IHostBuilder UseDefaultServiceProvider(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action configure) { throw null; } public static Microsoft.Extensions.Hosting.IHostBuilder UseEnvironment(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, string environment) { throw null; } } diff --git a/src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.csproj b/src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.csproj index 663242e657617..634b13f75c45d 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.csproj +++ b/src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.csproj @@ -13,10 +13,6 @@ - - - - diff --git a/src/libraries/Microsoft.Extensions.Hosting/src/Host.cs b/src/libraries/Microsoft.Extensions.Hosting/src/Host.cs index 37c16325988f7..a836ec1c2276e 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/src/Host.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/src/Host.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; using System.IO; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -13,8 +12,6 @@ namespace Microsoft.Extensions.Hosting /// public static class Host { - internal const string RequiresDynamicCodeMessage = "Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime."; - /// /// Initializes a new instance of the class with pre-configured defaults. /// @@ -31,7 +28,6 @@ public static class Host /// /// /// The initialized . - [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static IHostBuilder CreateDefaultBuilder() => CreateDefaultBuilder(args: null); @@ -54,7 +50,6 @@ public static IHostBuilder CreateDefaultBuilder() => /// /// The command line args. /// The initialized . - [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static IHostBuilder CreateDefaultBuilder(string[]? args) { HostBuilder builder = new(); @@ -78,7 +73,6 @@ public static IHostBuilder CreateDefaultBuilder(string[]? args) /// enables scope validation on the dependency injection container when is 'Development' /// /// - [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static HostApplicationBuilder CreateApplicationBuilder() => new HostApplicationBuilder(); /// @@ -99,7 +93,6 @@ public static IHostBuilder CreateDefaultBuilder(string[]? args) /// /// /// The command line args. - [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static HostApplicationBuilder CreateApplicationBuilder(string[]? args) => new HostApplicationBuilder(args); } } diff --git a/src/libraries/Microsoft.Extensions.Hosting/src/HostApplicationBuilder.cs b/src/libraries/Microsoft.Extensions.Hosting/src/HostApplicationBuilder.cs index 9962638c49d6a..f24e864c187ac 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/src/HostApplicationBuilder.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/src/HostApplicationBuilder.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.IO; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -46,7 +45,6 @@ public sealed class HostApplicationBuilder /// enables scope validation on the dependency injection container when is 'Development' /// /// - [RequiresDynamicCode(Host.RequiresDynamicCodeMessage)] public HostApplicationBuilder() : this(args: null) { @@ -70,7 +68,6 @@ public HostApplicationBuilder() /// /// /// The command line args. - [RequiresDynamicCode(Host.RequiresDynamicCodeMessage)] public HostApplicationBuilder(string[]? args) : this(new HostApplicationBuilderSettings { Args = args }) { @@ -80,7 +77,6 @@ public HostApplicationBuilder(string[]? args) /// Initializes a new instance of the . /// /// Settings controlling initial configuration and whether default settings should be used. - [RequiresDynamicCode(Host.RequiresDynamicCodeMessage)] public HostApplicationBuilder(HostApplicationBuilderSettings? settings) { settings ??= new HostApplicationBuilderSettings(); diff --git a/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs b/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs index 4b1aa7d2952dd..4c015c536097b 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs @@ -41,7 +41,6 @@ public partial class HostBuilder : IHostBuilder /// /// Initializes a new instance of . /// - [RequiresDynamicCode(Host.RequiresDynamicCodeMessage)] public HostBuilder() { _serviceProviderFactory = new ServiceFactoryAdapter(new DefaultServiceProviderFactory()); diff --git a/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs b/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs index a22f5bf31ac8b..c3f949a74db68 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs @@ -69,7 +69,6 @@ public static IHostBuilder UseContentRoot(this IHostBuilder hostBuilder, string /// The to configure. /// The delegate that configures the . /// The . - [RequiresDynamicCode(Host.RequiresDynamicCodeMessage)] public static IHostBuilder UseDefaultServiceProvider(this IHostBuilder hostBuilder, Action configure) => hostBuilder.UseDefaultServiceProvider((context, options) => configure(options)); @@ -79,7 +78,6 @@ public static IHostBuilder UseDefaultServiceProvider(this IHostBuilder hostBuild /// The to configure. /// The delegate that configures the . /// The . - [RequiresDynamicCode(Host.RequiresDynamicCodeMessage)] public static IHostBuilder UseDefaultServiceProvider(this IHostBuilder hostBuilder, Action configure) { return hostBuilder.UseServiceProviderFactory(context => @@ -192,7 +190,6 @@ public static IHostBuilder ConfigureContainer(this IHostBuild /// The existing builder to configure. /// The command line args. /// The same instance of the for chaining. - [RequiresDynamicCode(Host.RequiresDynamicCodeMessage)] public static IHostBuilder ConfigureDefaults(this IHostBuilder builder, string[]? args) { return builder.ConfigureHostConfiguration(config => ApplyDefaultHostConfiguration(config, args)) diff --git a/src/libraries/Microsoft.Extensions.Hosting/src/Microsoft.Extensions.Hosting.csproj b/src/libraries/Microsoft.Extensions.Hosting/src/Microsoft.Extensions.Hosting.csproj index 5c15d36edcf2f..592f9e60338f4 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/src/Microsoft.Extensions.Hosting.csproj +++ b/src/libraries/Microsoft.Extensions.Hosting/src/Microsoft.Extensions.Hosting.csproj @@ -25,10 +25,6 @@ - - - - diff --git a/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.cs b/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.cs index dd65f6f0e04a6..5b7fade6eaa43 100644 --- a/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.cs +++ b/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.cs @@ -61,7 +61,6 @@ public LoggerFactory(System.Collections.Generic.IEnumerable providers, Microsoft.Extensions.Options.IOptionsMonitor filterOption, Microsoft.Extensions.Options.IOptions? options = null, Microsoft.Extensions.Logging.IExternalScopeProvider? scopeProvider = null) { } public void AddProvider(Microsoft.Extensions.Logging.ILoggerProvider provider) { } protected virtual bool CheckDisposed() { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("LoggerFactory.Create uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")] public static Microsoft.Extensions.Logging.ILoggerFactory Create(System.Action configure) { throw null; } public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) { throw null; } public void Dispose() { } diff --git a/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.csproj b/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.csproj index 307fd0184170d..9ded83f82f6ee 100644 --- a/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.csproj +++ b/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.csproj @@ -7,10 +7,6 @@ - - - - diff --git a/src/libraries/Microsoft.Extensions.Logging/src/LoggerFactory.cs b/src/libraries/Microsoft.Extensions.Logging/src/LoggerFactory.cs index 39651be033007..315ea9115857a 100644 --- a/src/libraries/Microsoft.Extensions.Logging/src/LoggerFactory.cs +++ b/src/libraries/Microsoft.Extensions.Logging/src/LoggerFactory.cs @@ -103,7 +103,6 @@ public LoggerFactory(IEnumerable providers, IOptionsMonitor /// A delegate to configure the . /// The that was created. - [RequiresDynamicCode("LoggerFactory.Create uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")] public static ILoggerFactory Create(Action configure) { var serviceCollection = new ServiceCollection(); diff --git a/src/libraries/Microsoft.Extensions.Logging/src/Microsoft.Extensions.Logging.csproj b/src/libraries/Microsoft.Extensions.Logging/src/Microsoft.Extensions.Logging.csproj index d832ce579d99c..6d161e98cc0c6 100644 --- a/src/libraries/Microsoft.Extensions.Logging/src/Microsoft.Extensions.Logging.csproj +++ b/src/libraries/Microsoft.Extensions.Logging/src/Microsoft.Extensions.Logging.csproj @@ -30,10 +30,6 @@ - - - -