Skip to content

Commit b691e04

Browse files
authored
Shim NetheriteProviderFactory DI to avoid .NET8 regression (#10274)
* Shim NetheriteProviderFactory DI to avoid .NET8 regression * Add log for when DI shim occurs * Fix DI shim * Refactor dictionary add placement * Fix toReplace nullref * Remove unused using
1 parent 7fd5ca6 commit b691e04

File tree

1 file changed

+69
-0
lines changed

1 file changed

+69
-0
lines changed

src/WebJobs.Script.WebHost/DependencyInjection/JobHostScopedServiceProviderFactory.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Generic;
6+
using System.Reflection;
57
using Microsoft.Extensions.DependencyInjection;
68
using Microsoft.Extensions.Logging;
9+
using Microsoft.Extensions.Logging.Abstractions;
710

811
namespace 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

Comments
 (0)