22// Licensed under the MIT License. See License.txt in the project root for license information.
33
44using System ;
5+ using System . Collections . Generic ;
6+ using System . Reflection ;
57using Microsoft . Extensions . DependencyInjection ;
68using Microsoft . Extensions . Logging ;
9+ using Microsoft . Extensions . Logging . Abstractions ;
710
811namespace Microsoft . Azure . WebJobs . Script . WebHost . DependencyInjection
912{
@@ -16,12 +19,14 @@ public class JobHostScopedServiceProviderFactory : IServiceProviderFactory<IServ
1619 private readonly IServiceProvider _rootProvider ;
1720 private readonly IServiceCollection _rootServices ;
1821 private readonly IDependencyValidator _validator ;
22+ private readonly ILogger _logger ;
1923
2024 public JobHostScopedServiceProviderFactory ( IServiceProvider rootProvider , IServiceCollection rootServices , IDependencyValidator validator )
2125 {
2226 _rootProvider = rootProvider ?? throw new ArgumentNullException ( nameof ( rootProvider ) ) ;
2327 _rootServices = rootServices ?? throw new ArgumentNullException ( nameof ( rootServices ) ) ;
2428 _validator = validator ?? throw new ArgumentNullException ( nameof ( validator ) ) ;
29+ _logger = ( ( ILogger ) rootProvider . GetService < ILogger < JobHostScopedServiceProviderFactory > > ( ) ) ?? NullLogger . Instance ;
2530 }
2631
2732 public IServiceCollection CreateBuilder ( IServiceCollection services )
@@ -57,6 +62,8 @@ public IServiceProvider CreateServiceProvider(IServiceCollection services)
5762 throw new HostInitializationException ( "Invalid host services detected." , ex ) ;
5863 }
5964
65+ ShimBreakingChange ( services ) ;
66+
6067 // Start from the root (web app level) as a base
6168 var jobHostServices = _rootProvider . CreateChildContainer ( _rootServices ) ;
6269
@@ -68,5 +75,67 @@ public IServiceProvider CreateServiceProvider(IServiceCollection services)
6875
6976 return jobHostServices . BuildServiceProvider ( ) ;
7077 }
78+
79+ /// <summary>
80+ /// .NET 8 has a breaking change regarding <see cref="ActivatorUtilitiesConstructorAttribute"/> no longer functioning as expected.
81+ /// We have some known extension types which are impacted by this. To avoid a regression, we are manually shimming those types.
82+ /// </summary>
83+ /// <param name="services">The service collection.</param>
84+ private void ShimBreakingChange ( IServiceCollection services )
85+ {
86+ Dictionary < ServiceDescriptor , ServiceDescriptor > toReplace = null ;
87+ static bool HasPreferredCtor ( Type type )
88+ {
89+ foreach ( ConstructorInfo c in type . GetConstructors ( ) )
90+ {
91+ if ( c . IsDefined ( typeof ( ActivatorUtilitiesConstructorAttribute ) , false ) )
92+ {
93+ return true ;
94+ }
95+ }
96+
97+ return false ;
98+ }
99+
100+ bool TryCreateReplacement ( ServiceDescriptor descriptor , out ServiceDescriptor replacement )
101+ {
102+ if ( ! HasPreferredCtor ( descriptor . ImplementationType ) )
103+ {
104+ replacement = null ;
105+ return false ;
106+ }
107+
108+ _logger . LogInformation ( "Shimming DI constructor for {ImplementationType}." , descriptor . ImplementationType ) ;
109+ ObjectFactory factory = ActivatorUtilities . CreateFactory ( descriptor . ImplementationType , Type . EmptyTypes ) ;
110+
111+ replacement = ServiceDescriptor . Describe (
112+ descriptor . ServiceType , sp => factory . Invoke ( sp , Type . EmptyTypes ) , descriptor . Lifetime ) ;
113+ return true ;
114+ }
115+
116+ // NetheriteProviderFactory uses ActivatorUtilitiesConstructorAttribute. We will replace this implementation with an explicit delegate.
117+ Type netheriteProviderFactory = Type . GetType ( "DurableTask.Netherite.AzureFunctions.NetheriteProviderFactory, DurableTask.Netherite.AzureFunctions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ef8c4135b1b4225a" ) ;
118+ foreach ( ServiceDescriptor descriptor in services )
119+ {
120+ if ( netheriteProviderFactory is not null
121+ && descriptor . ImplementationType == netheriteProviderFactory
122+ && TryCreateReplacement ( descriptor , out ServiceDescriptor replacement ) )
123+ {
124+ toReplace ??= new Dictionary < ServiceDescriptor , ServiceDescriptor > ( ) ;
125+ toReplace . Add ( descriptor , replacement ) ;
126+ }
127+ }
128+
129+ if ( toReplace is null )
130+ {
131+ return ;
132+ }
133+
134+ foreach ( ( ServiceDescriptor key , ServiceDescriptor value ) in toReplace )
135+ {
136+ services . Remove ( key ) ;
137+ services . Add ( value ) ;
138+ }
139+ }
71140 }
72141}
0 commit comments