11using System ;
2+ using System . Collections . Generic ;
3+ using System . Linq ;
24using Autofac . Core ;
35using Autofac . Specification . Test . Features . CircularDependency ;
46using Xunit ;
@@ -7,6 +9,48 @@ namespace Autofac.Specification.Test.Features
79{
810 public class CircularDependencyTests
911 {
12+ private interface IPlugin
13+ {
14+ }
15+
16+ private interface IService
17+ {
18+ }
19+
20+ private interface IUnavailableComponent
21+ {
22+ }
23+
24+ [ Fact ]
25+ public void ActivationStackResetsOnFailedLambdaResolve ( )
26+ {
27+ // Issue #929
28+ var builder = new ContainerBuilder ( ) ;
29+ builder . RegisterType < ServiceImpl > ( ) . AsSelf ( ) ;
30+ builder . Register < IService > ( c =>
31+ {
32+ try
33+ {
34+ // This will fail because ServiceImpl needs a Guid ctor
35+ // parameter and it's not provided.
36+ return c . Resolve < ServiceImpl > ( ) ;
37+ }
38+ catch ( Exception )
39+ {
40+ // This is where the activation stack isn't getting reset.
41+ }
42+
43+ return new ServiceImpl ( Guid . Empty ) ;
44+ } ) ;
45+ builder . RegisterType < Dependency > ( ) . AsSelf ( ) ;
46+ builder . RegisterType < ComponentConsumer > ( ) . AsSelf ( ) ;
47+ var container = builder . Build ( ) ;
48+
49+ // This throws a circular dependency exception if the activation stack
50+ // doesn't get reset.
51+ container . Resolve < ComponentConsumer > ( ) ;
52+ }
53+
1054 [ Fact ]
1155 public void DetectsCircularDependencies ( )
1256 {
@@ -20,6 +64,32 @@ public void DetectsCircularDependencies()
2064 var de = Assert . Throws < DependencyResolutionException > ( ( ) => container . Resolve < ID > ( ) ) ;
2165 }
2266
67+ [ Fact ]
68+ public void InstancePerDependencyDoesNotAllowCircularDependencies_ConstructorOwnerResolved ( )
69+ {
70+ var cb = new ContainerBuilder ( ) ;
71+ var ac = 0 ;
72+ cb . RegisterType < DependsByCtor > ( ) . OnActivating ( e => { ac = 2 ; } ) ;
73+ cb . RegisterType < DependsByProp > ( ) . OnActivating ( e => { ac = 1 ; } )
74+ . PropertiesAutowired ( PropertyWiringOptions . AllowCircularDependencies ) ;
75+
76+ var c = cb . Build ( ) ;
77+ Assert . Throws < DependencyResolutionException > ( ( ) => c . Resolve < DependsByCtor > ( ) ) ;
78+
79+ Assert . Equal ( 2 , ac ) ;
80+ }
81+
82+ [ Fact ]
83+ public void InstancePerDependencyDoesNotAllowCircularDependencies_PropertyOwnerResolved ( )
84+ {
85+ var cb = new ContainerBuilder ( ) ;
86+ cb . RegisterType < DependsByCtor > ( ) ;
87+ cb . RegisterType < DependsByProp > ( ) . PropertiesAutowired ( PropertyWiringOptions . AllowCircularDependencies ) ;
88+
89+ var c = cb . Build ( ) ;
90+ Assert . Throws < DependencyResolutionException > ( ( ) => c . Resolve < DependsByProp > ( ) ) ;
91+ }
92+
2393 [ Fact ]
2494 public void InstancePerLifetimeScopeServiceCannotCreateSecondInstanceOfSelfDuringConstruction ( )
2595 {
@@ -31,6 +101,77 @@ public void InstancePerLifetimeScopeServiceCannotCreateSecondInstanceOfSelfDurin
31101 var exception = Assert . Throws < DependencyResolutionException > ( ( ) => container . Resolve < AThatDependsOnB > ( ) ) ;
32102 }
33103
104+ [ Fact ]
105+ public void ManualEnumerableRegistrationDoesNotCauseCircularDependency ( )
106+ {
107+ var builder = new ContainerBuilder ( ) ;
108+ builder . RegisterType < RootViewModel > ( ) . AsSelf ( ) . SingleInstance ( ) ;
109+ builder . RegisterType < PluginsViewModel > ( ) . AsSelf ( ) . SingleInstance ( ) ;
110+
111+ builder . RegisterType ( typeof ( Plugin1 ) ) . Named < IPlugin > ( nameof ( Plugin1 ) ) ;
112+ builder . RegisterType ( typeof ( Plugin2 ) ) . Named < IPlugin > ( nameof ( Plugin2 ) ) ;
113+ builder . Register (
114+ ctx => new [ ] { nameof ( Plugin1 ) , nameof ( Plugin2 ) }
115+ . Select ( name => SafeResolvePlugin ( name , ctx ) )
116+ . Where ( p => p != null )
117+ . ToArray ( ) )
118+ . As < IEnumerable < IPlugin > > ( )
119+ . SingleInstance ( ) ;
120+
121+ var container = builder . Build ( ) ;
122+
123+ // From issue 648, this resolve call was getting a circular dependency
124+ // detection exception. It shouldn't be getting anything because the "safe resolve"
125+ // eats the dependency resolution issue for Plugin2 and Plugin1 should be
126+ // properly resolved.
127+ Assert . NotNull ( container . Resolve < RootViewModel > ( ) ) ;
128+ Assert . Single ( container . Resolve < IEnumerable < IPlugin > > ( ) ) ;
129+ }
130+
131+ [ Fact ]
132+ public void SingleInstanceAllowsCircularDependencies_ConstructorOwnerResolved ( )
133+ {
134+ var cb = new ContainerBuilder ( ) ;
135+ cb . RegisterType < DependsByCtor > ( ) . SingleInstance ( ) ;
136+ cb . RegisterType < DependsByProp > ( ) . SingleInstance ( ) . PropertiesAutowired ( PropertyWiringOptions . AllowCircularDependencies ) ;
137+
138+ var c = cb . Build ( ) ;
139+ var dbc = c . Resolve < DependsByCtor > ( ) ;
140+
141+ Assert . NotNull ( dbc . Dep ) ;
142+ Assert . NotNull ( dbc . Dep . Dep ) ;
143+ Assert . Same ( dbc , dbc . Dep . Dep ) ;
144+ }
145+
146+ [ Fact ]
147+ public void SingleInstanceAllowsCircularDependencies_PropertyOwnerResolved ( )
148+ {
149+ var cb = new ContainerBuilder ( ) ;
150+ cb . RegisterType < DependsByCtor > ( ) . SingleInstance ( ) ;
151+ cb . RegisterType < DependsByProp > ( ) . SingleInstance ( ) . PropertiesAutowired ( PropertyWiringOptions . AllowCircularDependencies ) ;
152+
153+ var c = cb . Build ( ) ;
154+ var dbp = c . Resolve < DependsByProp > ( ) ;
155+
156+ Assert . NotNull ( dbp . Dep ) ;
157+ Assert . NotNull ( dbp . Dep . Dep ) ;
158+ Assert . Same ( dbp , dbp . Dep . Dep ) ;
159+ }
160+
161+ private static IPlugin SafeResolvePlugin ( string pluginName , IComponentContext core )
162+ {
163+ try
164+ {
165+ // Plugin2 will get filtered out because it has an
166+ // unavailable dependency.
167+ return core . ResolveNamed < IPlugin > ( pluginName ) ;
168+ }
169+ catch ( DependencyResolutionException )
170+ {
171+ return null ;
172+ }
173+ }
174+
34175 private class AThatDependsOnB
35176 {
36177 public AThatDependsOnB ( BThatCreatesA bThatCreatesA )
@@ -45,5 +186,72 @@ public BThatCreatesA(Func<BThatCreatesA, AThatDependsOnB> factory)
45186 factory ( this ) ;
46187 }
47188 }
189+
190+ // Issue #929
191+ // When a resolve operation fails in a lambda registration the activation stack
192+ // doesn't get reset and incorrectly causes a circular dependency exception.
193+ //
194+ // The ComponentConsumer takes IService (ServiceImpl) and a Dependency; the
195+ // Dependency also takes an IService. Normally this wouldn't cause an issue, but
196+ // if the registration for IService is a lambda that has a try/catch around a failing
197+ // resolve, the activation stack won't reset and the IService will be seen as a
198+ // circular dependency.
199+ private class ComponentConsumer
200+ {
201+ private Dependency _dependency ;
202+
203+ private IService _service ;
204+
205+ public ComponentConsumer ( IService service , Dependency dependency )
206+ {
207+ this . _service = service ;
208+ this . _dependency = dependency ;
209+ }
210+ }
211+
212+ private class Dependency
213+ {
214+ private IService _service ;
215+
216+ public Dependency ( IService service )
217+ {
218+ this . _service = service ;
219+ }
220+ }
221+
222+ private class Plugin1 : IPlugin
223+ {
224+ }
225+
226+ private class Plugin2 : IPlugin
227+ {
228+ public Plugin2 ( IUnavailableComponent unavailableComponent )
229+ {
230+ }
231+ }
232+
233+ private class PluginsViewModel
234+ {
235+ public PluginsViewModel ( IEnumerable < IPlugin > plugins )
236+ {
237+ }
238+ }
239+
240+ private class RootViewModel
241+ {
242+ public RootViewModel ( IEnumerable < IPlugin > plugins , PluginsViewModel pluginsViewModel )
243+ {
244+ }
245+ }
246+
247+ private class ServiceImpl : IService
248+ {
249+ private Guid _id ;
250+
251+ public ServiceImpl ( Guid id )
252+ {
253+ this . _id = id ;
254+ }
255+ }
48256 }
49257}
0 commit comments