Skip to content

Commit 5176a5e

Browse files
committed
improving DI scope behavior
1 parent ef3a626 commit 5176a5e

File tree

4 files changed

+105
-2
lines changed

4 files changed

+105
-2
lines changed

src/WebJobs.Script.WebHost/DependencyInjection/DryIoc/Container.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ namespace DryIoc
6464
using MemberAssignmentExpr = System.Linq.Expressions.MemberAssignment;
6565
using FactoryDelegateExpr = System.Linq.Expressions.Expression<FactoryDelegate>;
6666
using global::Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection.DryIoc;
67+
using global::Microsoft.Azure.WebJobs.Script;
68+
using global::Microsoft.Azure.WebJobs.Script.Config;
6769
#endif
6870

6971
/// <summary>IoC Container. Documentation is available at https://bitbucket.org/dadhi/dryioc. </summary>
@@ -1941,7 +1943,15 @@ private Container(Rules rules, Ref<Registry> registry, IScope singletonScope,
19411943
_registry = registry;
19421944

19431945
_singletonScope = singletonScope;
1944-
_scopeContext = scopeContext ?? new AsyncScopeContext();
1946+
1947+
_scopeContext = scopeContext;
1948+
1949+
if (_scopeContext == null && !FeatureFlags.IsEnabled(ScriptConstants.FeatureFlagEnableEnhancedScopes))
1950+
{
1951+
// Enhanced scopes do not need this context.
1952+
_scopeContext = new AsyncScopeContext();
1953+
}
1954+
19451955
_ownCurrentScope = ownCurrentScope;
19461956

19471957
_disposed = disposed;

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Linq;
77
using System.Threading.Tasks;
88
using DryIoc;
9+
using Microsoft.Azure.WebJobs.Script.Config;
910
using Microsoft.Extensions.DependencyInjection;
1011

1112
namespace Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection
@@ -55,6 +56,12 @@ internal ServiceScope CreateChildScope(IServiceScopeFactory rootScopeFactory)
5556
}));
5657

5758
var scope = new ServiceScope(resolver, scopedRoot);
59+
60+
if (FeatureFlags.IsEnabled(ScriptConstants.FeatureFlagEnableEnhancedScopes))
61+
{
62+
scopedContext.UseInstance<IServiceProvider>(scope.ServiceProvider);
63+
}
64+
5865
ChildScopes.TryAdd(scope, null);
5966

6067
scope.DisposalTask.ContinueWith(t => ChildScopes.TryRemove(scope, out object _));

src/WebJobs.Script/ScriptConstants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ public static class ScriptConstants
109109
public const string FeatureFlagEnableActionResultHandling = "EnableActionResultHandling";
110110
public const string FeatureFlagAllowSynchronousIO = "AllowSynchronousIO";
111111
public const string FeatureFlagRelaxedAssemblyUnification = "RelaxedAssemblyUnification";
112+
public const string FeatureFlagEnableEnhancedScopes = "EnableEnhancedScopes";
112113

113114
public const string AdminJwtValidAudienceFormat = "https://{0}.azurewebsites.net/azurefunctions";
114115
public const string AdminJwtValidIssuerFormat = "https://{0}.scm.azurewebsites.net";

test/WebJobs.Script.Tests/DependencyInjection/JobHostServiceProviderTests.cs

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System;
55
using System.Collections.Generic;
6-
using System.Text;
76
using Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection;
87
using Microsoft.Extensions.DependencyInjection;
98
using Xunit;
@@ -12,6 +11,8 @@ namespace Microsoft.Azure.WebJobs.Script.Tests.DependencyInjection
1211
{
1312
public class JobHostServiceProviderTests
1413
{
14+
private delegate object ServiceFactory(Type type);
15+
1516
[Fact]
1617
public void Dispose_OnJobHostScope_DoesNotDisposeRootSingletonService()
1718
{
@@ -88,6 +89,90 @@ public void Dispose_OnJobHost_DoesNotDisposRootScopedService()
8889
Assert.False(rootService.Disposed);
8990
}
9091

92+
[Fact]
93+
public void Scopes_ChildScopeIsIsolated()
94+
{
95+
using (new TestScopedEnvironmentVariable(EnvironmentSettingNames.AzureWebJobsFeatureFlags, ScriptConstants.FeatureFlagEnableEnhancedScopes))
96+
{
97+
var services = new ServiceCollection();
98+
services.AddScoped<A>();
99+
100+
var rootScopeFactory = new WebHostServiceProvider(new ServiceCollection());
101+
var jobHostProvider = new JobHostServiceProvider(services, rootScopeFactory, rootScopeFactory);
102+
103+
var a1 = jobHostProvider.GetService<A>();
104+
jobHostProvider.CreateScope();
105+
var a2 = jobHostProvider.GetService<A>();
106+
Assert.NotNull(a1);
107+
Assert.NotNull(a2);
108+
Assert.Same(a1, a2);
109+
}
110+
}
111+
112+
[Fact]
113+
public void Scopes_Factories()
114+
{
115+
using (new TestScopedEnvironmentVariable(EnvironmentSettingNames.AzureWebJobsFeatureFlags, ScriptConstants.FeatureFlagEnableEnhancedScopes))
116+
{
117+
IList<IServiceProvider> serviceProviders = new List<IServiceProvider>();
118+
119+
var services = new ServiceCollection();
120+
services.AddTransient<A>(p =>
121+
{
122+
serviceProviders.Add(p);
123+
return new A();
124+
});
125+
126+
var rootScopeFactory = new WebHostServiceProvider(new ServiceCollection());
127+
var jobHostProvider = new JobHostServiceProvider(services, rootScopeFactory, rootScopeFactory);
128+
129+
// Get this service twice.
130+
// The IServiceProvider passed to the factory should be different because they are separate scopes.
131+
var scope1 = jobHostProvider.CreateScope();
132+
scope1.ServiceProvider.GetService<A>();
133+
134+
var scope2 = jobHostProvider.CreateScope();
135+
scope2.ServiceProvider.GetService<A>();
136+
137+
Assert.Equal(2, serviceProviders.Count);
138+
Assert.NotSame(serviceProviders[0], serviceProviders[1]);
139+
}
140+
}
141+
142+
[Theory]
143+
[InlineData("")]
144+
[InlineData(ScriptConstants.FeatureFlagEnableEnhancedScopes)]
145+
public void Scopes_DelegateFactory(string flag)
146+
{
147+
using (new TestScopedEnvironmentVariable(EnvironmentSettingNames.AzureWebJobsFeatureFlags, flag))
148+
{
149+
var services = new ServiceCollection();
150+
151+
services.AddScoped<A>();
152+
services.AddScoped<ServiceFactory>(provider => (type) => provider.GetRequiredService(type));
153+
154+
var rootScopeFactory = new WebHostServiceProvider(new ServiceCollection());
155+
var jobHostProvider = new JobHostServiceProvider(services, rootScopeFactory, rootScopeFactory);
156+
157+
var scope1 = jobHostProvider.CreateScope();
158+
var a1 = scope1.ServiceProvider.GetService<ServiceFactory>()(typeof(A));
159+
160+
var scope2 = jobHostProvider.CreateScope();
161+
var a2 = scope2.ServiceProvider.GetService<ServiceFactory>()(typeof(A));
162+
163+
Assert.NotNull(a1);
164+
Assert.NotNull(a2);
165+
Assert.NotSame(a1, a2);
166+
}
167+
}
168+
169+
private class A
170+
{
171+
public A()
172+
{
173+
}
174+
}
175+
91176
private class TestService : IService, IDisposable
92177
{
93178
public bool Disposed { get; private set; }

0 commit comments

Comments
 (0)