@@ -19,14 +19,14 @@ namespace SolidToken.SpecFlow.DependencyInjection
19
19
{
20
20
public class DependencyInjectionPlugin : IRuntimePlugin
21
21
{
22
- private static readonly ConcurrentDictionary < IServiceProvider , IContextManager > BindMapping =
22
+ private static readonly ConcurrentDictionary < IServiceProvider , IContextManager > BindMappings =
23
23
new ConcurrentDictionary < IServiceProvider , IContextManager > ( ) ;
24
-
24
+
25
25
private static readonly ConcurrentDictionary < ISpecFlowContext , IServiceScope > ActiveServiceScopes =
26
26
new ConcurrentDictionary < ISpecFlowContext , IServiceScope > ( ) ;
27
27
28
28
private readonly object _registrationLock = new object ( ) ;
29
-
29
+
30
30
public void Initialize ( RuntimePluginEvents runtimePluginEvents , RuntimePluginParameters runtimePluginParameters , UnitTestProviderConfiguration unitTestProviderConfiguration )
31
31
{
32
32
runtimePluginEvents . CustomizeGlobalDependencies += CustomizeGlobalDependencies ;
@@ -46,7 +46,7 @@ private void CustomizeGlobalDependencies(object sender, CustomizeGlobalDependenc
46
46
args . ObjectContainer . RegisterTypeAs < ServiceCollectionFinder , IServiceCollectionFinder > ( ) ;
47
47
}
48
48
49
- // We store the service provider in the global container, we create it only once
49
+ // We store the (MS) service provider in the global (BoDi) container, we create it only once.
50
50
// It must be lazy (hence factory) because at this point we still don't have the bindings mapped.
51
51
args . ObjectContainer . RegisterFactoryAs < RootServiceProviderContainer > ( ( ) =>
52
52
{
@@ -70,7 +70,7 @@ private void CustomizeGlobalDependencies(object sender, CustomizeGlobalDependenc
70
70
args . ObjectContainer . Resolve < IServiceCollectionFinder > ( ) ;
71
71
}
72
72
}
73
-
73
+
74
74
private static void CustomizeFeatureDependenciesEventHandler ( object sender , CustomizeFeatureDependenciesEventArgs args )
75
75
{
76
76
// At this point we have the bindings, we can resolve the service provider, which will build it if it's the first time.
@@ -84,13 +84,22 @@ private static void CustomizeFeatureDependenciesEventHandler(object sender, Cust
84
84
args . ObjectContainer . RegisterFactoryAs < IServiceProvider > ( ( ) =>
85
85
{
86
86
var scope = serviceProvider . CreateScope ( ) ;
87
- BindMapping . TryAdd ( scope . ServiceProvider , args . ObjectContainer . Resolve < IContextManager > ( ) ) ;
87
+ BindMappings . TryAdd ( scope . ServiceProvider , args . ObjectContainer . Resolve < IContextManager > ( ) ) ;
88
88
ActiveServiceScopes . TryAdd ( args . ObjectContainer . Resolve < FeatureContext > ( ) , scope ) ;
89
89
return scope . ServiceProvider ;
90
90
} ) ;
91
91
}
92
92
}
93
93
94
+ private static void AfterFeaturePluginLifecycleEventHandler ( object sender , RuntimePluginAfterFeatureEventArgs eventArgs )
95
+ {
96
+ if ( ActiveServiceScopes . TryRemove ( eventArgs . ObjectContainer . Resolve < FeatureContext > ( ) , out var serviceScope ) )
97
+ {
98
+ BindMappings . TryRemove ( serviceScope . ServiceProvider , out _ ) ;
99
+ serviceScope . Dispose ( ) ;
100
+ }
101
+ }
102
+
94
103
private static void CustomizeScenarioDependenciesEventHandler ( object sender , CustomizeScenarioDependenciesEventArgs args )
95
104
{
96
105
// At this point we have the bindings, we can resolve the service provider, which will build it if it's the first time.
@@ -103,26 +112,18 @@ private static void CustomizeScenarioDependenciesEventHandler(object sender, Cus
103
112
args . ObjectContainer . RegisterFactoryAs < IServiceProvider > ( ( ) =>
104
113
{
105
114
var scope = serviceProvider . CreateScope ( ) ;
115
+ BindMappings . TryAdd ( scope . ServiceProvider , args . ObjectContainer . Resolve < IContextManager > ( ) ) ;
106
116
ActiveServiceScopes . TryAdd ( args . ObjectContainer . Resolve < ScenarioContext > ( ) , scope ) ;
107
117
return scope . ServiceProvider ;
108
118
} ) ;
109
119
}
110
120
}
111
-
121
+
112
122
private static void AfterScenarioPluginLifecycleEventHandler ( object sender , RuntimePluginAfterScenarioEventArgs eventArgs )
113
123
{
114
124
if ( ActiveServiceScopes . TryRemove ( eventArgs . ObjectContainer . Resolve < ScenarioContext > ( ) , out var serviceScope ) )
115
125
{
116
- BindMapping . TryRemove ( serviceScope . ServiceProvider , out _ ) ;
117
- serviceScope . Dispose ( ) ;
118
- }
119
- }
120
-
121
- private static void AfterFeaturePluginLifecycleEventHandler ( object sender , RuntimePluginAfterFeatureEventArgs eventArgs )
122
- {
123
- if ( ActiveServiceScopes . TryRemove ( eventArgs . ObjectContainer . Resolve < FeatureContext > ( ) , out var serviceScope ) )
124
- {
125
- BindMapping . TryRemove ( serviceScope . ServiceProvider , out _ ) ;
126
+ BindMappings . TryRemove ( serviceScope . ServiceProvider , out _ ) ;
126
127
serviceScope . Dispose ( ) ;
127
128
}
128
129
}
@@ -132,7 +133,7 @@ private static void RegisterProxyBindings(IObjectContainer objectContainer, ISer
132
133
// Required for DI of binding classes that want container injections
133
134
// While they can (and should) use the method params for injection, we can support it.
134
135
// Note that in Feature mode, one can't inject "ScenarioContext", this can only be done from method params.
135
-
136
+
136
137
// Bases on this: https://docs.specflow.org/projects/specflow/en/latest/Extend/Available-Containers-%26-Registrations.html
137
138
// Might need to add more...
138
139
@@ -157,7 +158,7 @@ private static void RegisterProxyBindings(IObjectContainer objectContainer, ISer
157
158
158
159
services . AddTransient ( sp =>
159
160
{
160
- var container = BindMapping . TryGetValue ( sp , out var ctx )
161
+ var container = BindMappings . TryGetValue ( sp , out var ctx )
161
162
? ctx . ScenarioContext ? . ScenarioContainer ??
162
163
ctx . FeatureContext ? . FeatureContainer ??
163
164
ctx . TestThreadContext ? . TestThreadContainer ??
@@ -166,15 +167,15 @@ private static void RegisterProxyBindings(IObjectContainer objectContainer, ISer
166
167
167
168
return container . Resolve < ISpecFlowOutputHelper > ( ) ;
168
169
} ) ;
169
-
170
- services . AddTransient ( sp => BindMapping [ sp ] ) ;
171
- services . AddTransient ( sp => BindMapping [ sp ] . TestThreadContext ) ;
172
- services . AddTransient ( sp => BindMapping [ sp ] . FeatureContext ) ;
173
- services . AddTransient ( sp => BindMapping [ sp ] . ScenarioContext ) ;
174
- services . AddTransient ( sp => BindMapping [ sp ] . TestThreadContext . TestThreadContainer . Resolve < ITestRunner > ( ) ) ;
175
- services . AddTransient ( sp => BindMapping [ sp ] . TestThreadContext . TestThreadContainer . Resolve < ITestExecutionEngine > ( ) ) ;
176
- services . AddTransient ( sp => BindMapping [ sp ] . TestThreadContext . TestThreadContainer . Resolve < IStepArgumentTypeConverter > ( ) ) ;
177
- services . AddTransient ( sp => BindMapping [ sp ] . TestThreadContext . TestThreadContainer . Resolve < IStepDefinitionMatchService > ( ) ) ;
170
+
171
+ services . AddTransient ( sp => BindMappings [ sp ] ) ;
172
+ services . AddTransient ( sp => BindMappings [ sp ] . TestThreadContext ) ;
173
+ services . AddTransient ( sp => BindMappings [ sp ] . FeatureContext ) ;
174
+ services . AddTransient ( sp => BindMappings [ sp ] . ScenarioContext ) ;
175
+ services . AddTransient ( sp => BindMappings [ sp ] . TestThreadContext . TestThreadContainer . Resolve < ITestRunner > ( ) ) ;
176
+ services . AddTransient ( sp => BindMappings [ sp ] . TestThreadContext . TestThreadContainer . Resolve < ITestExecutionEngine > ( ) ) ;
177
+ services . AddTransient ( sp => BindMappings [ sp ] . TestThreadContext . TestThreadContainer . Resolve < IStepArgumentTypeConverter > ( ) ) ;
178
+ services . AddTransient ( sp => BindMappings [ sp ] . TestThreadContext . TestThreadContainer . Resolve < IStepDefinitionMatchService > ( ) ) ;
178
179
}
179
180
180
181
private class RootServiceProviderContainer
0 commit comments