2
2
// Licensed under the MIT License. See License.txt in the project root for license information.
3
3
4
4
using System ;
5
+ using System . Collections . Generic ;
6
+ using System . Reflection ;
5
7
using Microsoft . Extensions . DependencyInjection ;
6
8
using Microsoft . Extensions . Logging ;
9
+ using Microsoft . Extensions . Logging . Abstractions ;
7
10
8
11
namespace Microsoft . Azure . WebJobs . Script . WebHost . DependencyInjection
9
12
{
@@ -16,12 +19,14 @@ public class JobHostScopedServiceProviderFactory : IServiceProviderFactory<IServ
16
19
private readonly IServiceProvider _rootProvider ;
17
20
private readonly IServiceCollection _rootServices ;
18
21
private readonly IDependencyValidator _validator ;
22
+ private readonly ILogger _logger ;
19
23
20
24
public JobHostScopedServiceProviderFactory ( IServiceProvider rootProvider , IServiceCollection rootServices , IDependencyValidator validator )
21
25
{
22
26
_rootProvider = rootProvider ?? throw new ArgumentNullException ( nameof ( rootProvider ) ) ;
23
27
_rootServices = rootServices ?? throw new ArgumentNullException ( nameof ( rootServices ) ) ;
24
28
_validator = validator ?? throw new ArgumentNullException ( nameof ( validator ) ) ;
29
+ _logger = ( ( ILogger ) rootProvider . GetService < ILogger < JobHostScopedServiceProviderFactory > > ( ) ) ?? NullLogger . Instance ;
25
30
}
26
31
27
32
public IServiceCollection CreateBuilder ( IServiceCollection services )
@@ -57,6 +62,8 @@ public IServiceProvider CreateServiceProvider(IServiceCollection services)
57
62
throw new HostInitializationException ( "Invalid host services detected." , ex ) ;
58
63
}
59
64
65
+ ShimBreakingChange ( services ) ;
66
+
60
67
// Start from the root (web app level) as a base
61
68
var jobHostServices = _rootProvider . CreateChildContainer ( _rootServices ) ;
62
69
@@ -68,5 +75,67 @@ public IServiceProvider CreateServiceProvider(IServiceCollection services)
68
75
69
76
return jobHostServices . BuildServiceProvider ( ) ;
70
77
}
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
+ }
71
140
}
72
141
}
0 commit comments