Best Practices and Recommendations
You can always ask for Autofac usage guidance on StackOverflow using the autofac
tag or in the discussion group, but these quick tips can help get you going.
Always Resolve Dependencies from Nested Lifetimes
Autofac is designed to track and dispose of resources for you. To ensure this happens, make sure that long-running applications are partitioned into units of work (requests or transactions) and that services are resolved through unit of work level lifetime scopes. The per-request lifetime scope support in ASP.NET is an example of this technique.
Structure Configuration with Modules
Autofac modules give structure to container configuration and allow deployment-time settings to be injected. Rather than using XML configuration alone, consider modules for a more flexible approach. Modules can always be combined with XML configuration for a best-of-both-worlds experience.
Use As<T>() in Delegate Registrations
Autofac infers implementation type from the expressions you use to register components:
builder.Register(c => new Component()).As<IComponent>();
…makes the type Component
the LimitType
of the component. These other type casting mechanisms are equivalent but don’t provide the correct LimitType
:
// Works, but avoid this
builder.Register(c => (IComponent)new Component());
// Works, but avoid this
builder.Register<IComponent>(c => new Component());
Use Constructor Injection
The concept of using constructor injection for required dependencies and property injection for optional dependencies is quite well known. An alternative, however, is to use the “Null Object” or “Special Case” pattern to provide default, do-nothing implementations for the optional service. This prevents the possibility of special-case code in the implementation of the component (e.g. if (Logger != null) Logger.Log("message");
).
Use Relationship Types, Not Service Locators
Giving components access to the container, storing it in a public static property, or making functions like Resolve()
available on a global “IoC” class defeats the purpose of using dependency injection. Such designs have more in common with the Service Locator pattern.
If components have a dependency on the container (or on a lifetime scope), look at how they’re using the container to retrieve services, and add those services to the component’s (dependency injected) constructor arguments instead.
Use relationship types for components that need to instantiate other components or interact with the container in more advanced ways.
Register Components from Least-to-Most Specific
Autofac overrides component registrations by default. This means that an application can register all of its default components, then read an associated configuration file to override any that have been customized for the deployment environment.
Use Profilers for Performance Checking
Before doing any performance optimization or making assumptions about potential memory leaks, always run a profiler like SlimTune, dotTrace, or ANTS to see where time is truly being spent. It might not be where you think.
Register Once, Resolve Many
Don’t register components during units of work if you can avoid it; it is more expensive to register a component than resolve one. Use nested lifetime scopes and appropriate instance scopes to keep per-unit-of-work instances separate.
Register Frequently-Used Components with Lambdas
If you do need to squeeze extra performance out of Autofac, your best bet is to identify the most frequently-created components and register them using an expression rather than by type, e.g.:
builder.RegisterType<Component>();
Becomes:
builder.Register(c => new Component());
This can yield an improvement of up to 10x faster Resolve()
calls, but only makes sense for components that appear in many object graphs. See the registration documentation for more on lambda components.
Consider a Container as Immutable
Starting from Autofac 5.x the container is immutable. There are several potential risks associated with updating a container after it has been built. Some examples include:
Auto-start components will have already run and potentially used registrations you’ve overridden during update. These auto-start components will not re-run.
Services that have already been resolved may have references to incorrect dependencies based on the additions made.
Disposable components may have already been resolved and will stick around until their owning lifetime scope is disposed - even if the new registrations would imply the disposable component shouldn’t be used.
Component registrations that subscribe to lifetime events may be subscribed to the wrong events after the update - events don’t all get re-initialized during update.
In order to prevent any of these risks from becoming a problem, it is no longer an option to update the container after construction.
Instead of updating the container, consider registering updates or changes in a child lifetime scope. There are examples of this in the lifetime scope documentation.
Optimize or Avoid Diagnostics
The System.Diagnostics.DiagnosticSource
diagnostics integration is reasonably performant, but tracers attached may affect overall performance. For example, any tracer (e.g., DefaultDiagnosticTracer
) that tracks full resolve operation chains will be allocating memory and using resources to keep the operation data until the full resolve operation is complete.
The overall performance will be better if you don’t have any listeners for diagnostics, but if you do, consider using diagnostics listeners that are very fast and low allocation. For example, no additional memory or tracking needs to happen to log a message when an operation occurs. That might be lower overhead than a listener that builds a robust trace before completing.