Skip to content

Commit b8d4b36

Browse files
author
maximv
committed
adding the autofac migration faq back to the docs
1 parent bfbde80 commit b8d4b36

File tree

1 file changed

+345
-0
lines changed

1 file changed

+345
-0
lines changed
+345
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
# FAQ - Migration from Autofac
2+
3+
4+
- [FAQ - Migration from Autofac](#faq---migration-from-autofac)
5+
- [Autofac version](#autofac-version)
6+
- [Separate build stage](#separate-build-stage)
7+
- [Registration order](#registration-order)
8+
- [Auto-activated components](#auto-activated-components)
9+
- [IStartable](#istartable)
10+
- [Register AsImplementedInterfaces](#register-asimplementedinterfaces)
11+
- [Owned instances](#owned-instances)
12+
- [Using constructor with most resolvable parameters](#using-constructor-with-most-resolvable-parameters)
13+
- [Modules](#modules)
14+
15+
16+
## Autofac version
17+
18+
Relevant to __Autofac v3.5.2__
19+
20+
21+
## Separate build stage
22+
23+
By default DryIoc does not have separate build stage - you may resolve and then add new registrations at any time.
24+
25+
Autofac on the other hand has `ContainerBuilder` which accumulate registrations and then produce `Container` to resolve from.
26+
27+
The __pros__ of DryIoc approach is to provide more flexibility and less API obstacles in using the container, especially when you need register later after resolve.
28+
29+
The __cons__ may be openness for later/external registrations that may override your initial setup. So you need to be careful when sharing container with plugins or other third-parties.
30+
31+
Other __cons__ may be to be aware when container is built, so to hook some additional logic to this event: e.g. [Auto-activated components](FaqAutofacMigration#markdown-header-auto-activated-).
32+
33+
__The question is:__ Does the Autofac provides the first __cons__ by guarding container from later changes? It seems no, because you may create new `ContainerBuilder`, put new registrations into it, and then update/mutate initial container.
34+
35+
On the other hand DryIoc provides the way to produce resolution-only container to share with third-parties:
36+
```cs
37+
var resolutionOnlyContainer = container.WithNoMoreRegistrationAllowed();
38+
resolutionOnlyContainer.Register<A>(); // will throw ContainerException
39+
```
40+
41+
or you may ignore later registrations:
42+
```cs
43+
var resolutionOnlyContainer = container.WithNoMoreRegistrationAllowed(ignoreInsteadOfThrow: true);
44+
resolutionOnlyContainer.Register<A>(); // ignores registration - does nothing
45+
```
46+
47+
48+
## Registration order
49+
50+
Sometimes you need to get container registrations in determined order.
51+
DryIoc has the way to get service registrations without actually resolving them:
52+
```cs
53+
IEnumerable<ServiceRegistrationInfo> registrations = container.GetServiceRegistrations();
54+
foreach (var r in registrations) { /*...*/ }
55+
```
56+
57+
`ServiceRegistrationInfo` contains:
58+
59+
- `ServiceType`
60+
- `OptionalServiceKey` is `null` for single default registration, arbitrary object for keyed registration, and `DefaultKey` object for multiple defaults.
61+
- `Factory` holds implementation details (__may be the same when single implementation registered with multiple services__). Includes:
62+
- `ImplementationType`, may be null
63+
- `Reuse`, e.g. `SingletonReuse`
64+
- `Setup`, e.g. `Setup.Metadata`
65+
- `FactoryRegistrationOrder` is relative number identifying the order of Factory registration (__may be the same for multiple services__)
66+
67+
By default the return order is undetermined (internally ordered by Type hash code + Service Key hash code). The solution is to use LINQ `OrderBy` method:
68+
```cs
69+
var registrations = container.GetServiceRegistrations()
70+
.OrderBy(r => r.FactoryRegistrationOrder);
71+
72+
foreach (var r in registrations) { /*...*/ }
73+
```
74+
75+
76+
## Auto-activated components
77+
78+
[This Autofac feature](http://docs.autofac.org/en/latest/lifetime/startup.html#auto-activated-components)
79+
allows to automatically resolve and create specific service when Container is built. DryIoc does not have separate build stage.
80+
That means at any time you can get service registrations, filter specific services and resolve them to activate.
81+
For instance, let's mark activate-able services with Metadata and then create them as following:
82+
```cs
83+
// Defining Metadata in extensible way:
84+
public abstract class Metadata
85+
{
86+
public class AutoActivated : Metadata
87+
{
88+
public static readonly AutoActivated It = new AutoActivated();
89+
}
90+
}
91+
92+
// Configure registrations:
93+
container.Register<ISpecific, Foo>(setup: Setup.With(Metadata.AutoActivated.It));
94+
container.Register<INormal, Bar>();
95+
96+
// Resolve to activate:
97+
var ignored = container.GetServiceRegistrations()
98+
.Where(r => r.Factory.Setup.Metadata is Metadata.AutoActivated)
99+
.OrderBy(r => r.FactoryRegistrationOrder)
100+
.GroupBy(r => r.FactoryRegistrationOrder, (f, r) => r.First())
101+
.Select(r => container.Resolve(r.ServiceType, r.OptionalServiceKey));
102+
```
103+
104+
__Note:__ `GroupBy` required to activate single implementation (Factory) only once, because single implementation may be registered with multiple services.
105+
106+
Metadata provides generic way to filter services. Alternatively you may go with convention, for instance auto-activate Singletons: `.Where(r => r.Factory.Reuse is SingletonReuse)`.
107+
108+
109+
## IStartable
110+
111+
[Autofac Startable components](http://docs.autofac.org/en/latest/lifetime/startup.html#startable-components) are the same __auto-activated__ services with additional `Start` action executed on activation (and never on normal resolve). It is pretty easy to modify above example to filter `IStartable` interface instead of (or in addition to) Metadata:
112+
```cs
113+
var ignored = container.GetServiceRegistrations()
114+
.Where(r => (r.Factory.ImplementationType ?? r.ServiceType).IsAssignableTo(typeof(IStartable)))
115+
.OrderBy(r => r.FactoryRegistrationOrder)
116+
.GroupBy(r => r.FactoryRegistrationOrder, (f, r) => r.First())
117+
.Select(r => ((IStartable)container.Resolve(r.ServiceType, r.OptionalServiceKey)).Start());
118+
```
119+
120+
121+
## Register AsImplementedInterfaces
122+
123+
Autofac has conventional method to register type implemented interfaces as service types.
124+
125+
DryIoc provides a set of `RegisterMany` methods to register multiple service types for possibly multiple implementation types or assemblies. To achieve Autofac behavior you need to say:
126+
```cs
127+
container.RegisterMany<FooBar>(serviceTypeCondition: type => type.IsInterface);
128+
```
129+
130+
Condition will keep interface service types and skip _public_ base types and source type itself.
131+
132+
__Note:__ General purpose interfaces like `IDisposable` will not be registered in any case.
133+
134+
To register _non-public_ service types modify previous example:
135+
```cs
136+
container.RegisterMany<FooBar>(
137+
nonPublicServiceTypes: true,
138+
serviceTypeCondition: type => type.IsInterface);
139+
```
140+
141+
142+
## Owned instances
143+
144+
[The feature](http://docs.autofac.org/en/latest/advanced/owned-instances.html) implicitly opens scope for `Owned<TService>`.
145+
Disposing owned service will dispose the scope and also dispose its disposable dependencies.
146+
Using `Owned` is closely related to the fact that Autofac tracks `IDisposable` services even if they are Transient (`InstancePerDependency`).
147+
148+
Meanwhile DryIoc [does not track disposable transients by default](ReuseAndScopes#markdown-header-disposabletransient).
149+
Instead DryIoc will throw exception on registering disposable transient.
150+
151+
To allow such registrations you need to explicitly use `allowDisposableTransient` setup option
152+
or use the container rule `WithoutThrowOnRegisteringDisposableTransient()`.
153+
This option is similar to Autofac registration as `ExternallyOwned()`.
154+
That way you do not need to use `Owned` to control disposal.
155+
156+
To match default Autofac disposable tracking you need to use DryIoc `trackDisposableTransinet` registration setup option
157+
or specify the container rule `WithTrackingDisposableTransient()`.
158+
Then instead of `Owned` in DryIoc, just wrap dependency in `Func`. This way the tracking will be prevented.
159+
Using `Func` has a slight advantage over `Owned` because `Func` is not container specific type and does not
160+
require DryIoc reference.
161+
162+
If you want the Autofac [InstancePerOwned](http://docs.autofac.org/en/stable/lifetime/instance-scope.html#instance-per-owned)
163+
to [reuse service in object sub-graph](ReuseAndScopes#markdown-header-reuseinresolutionscopeof) use:
164+
```cs
165+
container.Register<SomeService>(setup: Setup.With(openResolutionScope: true));
166+
```
167+
168+
To dispose dependencies instead of `Owned` just define `IDisposable` parameter and it will be automatically injected with current Resolution Scope.
169+
170+
Similar setups in Autofac and DryIoc:
171+
172+
In Autofac:
173+
```cs
174+
class SomeService
175+
{
176+
public SomeService(Dependency d) {}
177+
}
178+
179+
class SomeClient : IDisposable
180+
{
181+
// Owned will open scope and provide access for its disposal
182+
public SomeClient(Owned<SomeService> owned) { _owned = owned; }
183+
184+
// Will dispose scoped dependency and nested dependencies
185+
public void Dispose() { _owned.Dispose(); }
186+
}
187+
188+
// configure:
189+
builder.RegisterType<NestedDependency>().InstancePerOwned<SomeService>();
190+
builder.RegisterType<Dependency>().InstancePerOwned<SomeService>();
191+
builder.RegisterType<SomeService>();
192+
builder.RegisterType<SomeClient>();
193+
```
194+
195+
In DryIoc:
196+
197+
```cs
198+
class SomeService
199+
{
200+
public SomeService(Dependency d) {}
201+
}
202+
203+
class SomeClient : IDisposable
204+
{
205+
// No need in Owned wrapper - Scope will be injected automatically
206+
public SomeClient(SomeService s, IDisposable scope) { _scope = scope; }
207+
208+
// Will dispose scoped dependency and nested dependencies
209+
public void Dispose() { _scope.Dispose(); }
210+
}
211+
212+
// configure:
213+
container.Register<NestedDependency>(Reuse.InResolutionScopeOf<SomeService>());
214+
container.Register<Dependency>(Reuse.InResolutionScopeOf<SomeService>());
215+
container.Register<SomeService>(setup: Setup.With(openResolutionScope: true));
216+
container.Register<SomeClient>();
217+
```
218+
219+
220+
## Using constructor with most resolvable parameters
221+
222+
Autofac by default will use constructor with most resolvable parameters. So the `A` will be successfully resolved in example below:
223+
```cs
224+
public class B {}
225+
public class C {}
226+
227+
public class A
228+
{
229+
public A(B b) {}
230+
public A(C c) {}
231+
}
232+
233+
// configure:
234+
var builder = new ContainerBuilder();
235+
builder.RegisterType<A>();
236+
builder.RegisterType<B>();
237+
// C is not registered.
238+
var container = builder.Build();
239+
240+
container.Resolve<A>();
241+
```
242+
243+
DryIoc on the other hand will expect type to have a single constructor.
244+
In case of multiple constructors available DryIoc will throw `ContainerException` with corresponding message.
245+
This behavior was selected as default, because it is more deterministic - you always know the way of instantiating your service.
246+
247+
But you may enable the Autofac behavior in DryIoc:
248+
249+
- per whole Container:
250+
```cs
251+
var container = new Container(rules =>
252+
rules.With(FactoryMethod.ConstructorWithResolvableArguments));
253+
```
254+
255+
- per individual registration:
256+
257+
```cs
258+
var container = new Container();
259+
container.Register<A>(made: FactoryMethod.ConstructorWithResolvableArguments);
260+
```
261+
262+
If you enable the feature per Container but provide some parameter specs on registration level, they will completely override the Container level setting:
263+
```cs
264+
public class B {}
265+
public class C {}
266+
267+
public class A
268+
{
269+
public bool IsCreatedWithB { get; private set; }
270+
public bool IsCreatedWithC { get; private set; }
271+
272+
public A(B b) { IsCreatedWithB = true; }
273+
public A(C c) { IsCreatedWithC = true; }
274+
}
275+
276+
// configure:
277+
var container = new Container(rules =>
278+
rules.With(FactoryMethod.ConstructorWithResolvableArguments));
279+
280+
container.Register(Made.Of(() => new A(Arg.Of<C>(IfUnresolved.ReturnDefault))));
281+
container.Register<B>();
282+
// No C registered
283+
var a = container.Resolve<A>();
284+
Assert.IsTrue(a.IsCreatedWithC); // Because registration level setting override container's
285+
```
286+
287+
288+
## Modules
289+
290+
Here the docs describing [Autofac Modules feature](http://docs.autofac.org/en/latest/configuration/modules.html).
291+
292+
They major responsibility of Module to be the unit of configuration and registration. Actually the Module plays the role of [Facade](https://en.wikipedia.org/wiki/Facade_pattern) to hide some related registrations behind.
293+
294+
Here is the how defining and using the module looks like in Autofac:
295+
```cs
296+
// Here is the AModule
297+
public class AutofacModule : Module
298+
{
299+
protected override void Load(ContainerBuilder builder)
300+
{
301+
builder.RegisterType<A>().SingleInstance();
302+
}
303+
}
304+
305+
// And using it
306+
var builder = new ContainerBuilder();
307+
308+
builder.RegisterModule<AModule>(); // registers module with some registrations inside
309+
builder.RegisterType<B>();
310+
311+
var container = builder.Build();
312+
313+
var a = container.Resolve<A>();
314+
Assert.IsInstanceOf<B>(a.B);
315+
```
316+
317+
And here the equivalent in DryIoc without use of any additional abstractions except for `IModule` interface for conformity:
318+
```cs
319+
public interface IModule
320+
{
321+
// Here we are using registration role of DryIoc Container for the builder
322+
void Load(IRegistrator builder);
323+
}
324+
325+
public class DryIocModule : IModule
326+
{
327+
public void Load(IRegistrator builder)
328+
{
329+
builder.Register<BB>(Reuse.Singleton);
330+
}
331+
}
332+
333+
// And the use is straightforward
334+
var container = new Container();
335+
336+
container.RegisterMany<AModule>();
337+
container.Register<B>();
338+
339+
// Resolve all registered modules and call Load on them
340+
foreach (var module in container.ResolveMany<IModule>())
341+
module.Load(container);
342+
343+
var a = container.Resolve<A>();
344+
Assert.IsInstanceOf<B>(a.B);
345+
```

0 commit comments

Comments
 (0)