Autofac 7.0 has just been released, and I wanted to take a moment to call out two of the cool new features in this release.
Required Properties
.NET 7 introduced the idea of “required” properties. These are properties that must be set when constructing an object; this is enforced by the compiler:
public class MyComponent
{
public required string RequiredProperty { get; set; }
}
// Compiler error: CS9035
// Required member 'MyComponent.RequiredProperty' must be set
// in the object initializer or attribute constructor.
var newInstance = new MyComponent();
To resolve this, you’ll have to provide RequiredProperty:
public class MyComponent
{
public required string RequiredProperty { get; set; }
}
var newInstance = new MyComponent
{
RequiredProperty = "foo"
};
How does this affect Autofac? Specifically, how does it affect components registered using reflection in 6.x?
public class MyComponent
{
public required ILogger Logger { get; set; }
}
var builder = new ContainerBuilder();
builder.RegisterType<MyComponent>();
var container = builder.Build();
// Logger will be null in the result.
var component = container.Resolve<MyComponent>();
That feels wrong, right? If the compiler says that, in order to create an instance of MyComponent, you must populate Logger, then Autofac probably shouldn’t be able to circumvent that.
The above code runs because required properties are only a compiler check; there is no requirement to populate those properties when creating an instance of a class via
Activator.CreateInstance().
In Autofac 7.0, we now automatically inject all required properties for a component registered by type when we create an instance of said component. These properties are required, so if we can’t resolve them, we throw an error:
public class MyComponent
{
public required ILogger Logger { get; set; }
}
var builder = new ContainerBuilder();
builder.RegisterType<MyComponent>();
var container = builder.Build();
// Throws an exception, ILogger has not been registered.
var component = container.Resolve<MyComponent>();
You do not need to call
PropertiesAutowired()on a registration to make this behaviour kick in, it applies automatically to all reflection-based registrations, including assembly-scanned types.
There are a few additional considerations around required properties, particularly around mixing in constructors, and you can find those in our property injection documentation.
Unloadable Plugins and Autofac
.NET Core introduced the AssemblyLoadContext class, which allows you to load in a set of assemblies that are somewhat isolated from the set of assemblies already loaded. These loaded assemblies can reference each other, and even contain different versions of assemblies already loaded into the “default” load context. You also have the added bonus of potentially being able to unload those assemblies, and reclaim that memory, when they are no longer needed.
If you’re not familiar with what I’m talking about, the dotnet docs on AsssemblyLoadContext are a pretty handy intro.
Now, up until this point, Autofac has been pretty intent on maintaining internal caches of types it has seen, regardless of when it saw them, or where those types were loaded from.
This meant that, if you registered types from a loaded assembly into an Autofac lifetime scope, you would no longer be able to unload that assembly.
In Autofac 6.5 we made some progress towards being able to “forget” about those types by switching from a static reflection cache to an instance cache that can be readily cleared, but there was still more work needed.
In Autofac 7.0, you can now begin a lifetime scope specifically for the purpose of ensuring that a load context can be unloaded even after using it with Autofac.
This is enabled by a new BeginLoadContextLifetimeScope() method, which creates a sort of “load context aware” lifetime scope; we ensure that when the scope is disposed, any internal Autofac references to type or assembly metadata relating to the assemblies loaded from the custom load context is cleaned away, allowing it to be unloaded.
Once you have that lifetime scope, it works like any other scope you’ve used before; IEnumerable<TService> can combine elements from the new scope and any parent scopes, new scopes can be created from it, and so on.
Here’s an example of how you can use it, but you can find more information in our documentation:
//
// In PluginDefinition project.
//
public interface IPlugin
{
void DoSomething();
}
//
// In MyPlugin project.
//
public class MyPlugin : IPlugin
{
public void DoSomething()
{
Console.WriteLine("Hello World");
}
}
//
// In the application.
//
var builder = new ContainerBuilder();
// Components defined in the "Default" load context will be available in load context lifetime scopes.
builder.RegisterType<DefaultComponent>();
var container = builder.Build();
var loadContext = new AssemblyLoadContext("PluginContext", isCollectible: true);
using (var scope = container.BeginLoadContextLifetimeScope(loadContext, builder =>
{
var pluginAssembly = loadContext.LoadFromAssemblyPath("plugins/MyPlugin.dll");
builder.RegisterAssemblyTypes(pluginAssembly).AsImplementedInterfaces();
}))
{
// The application should reference the PluginDefinition project, which means the
// default load context will have loaded the IPlugin interface already. When the
// MyPlugin assembly gets loaded it should share the same type and allow resolution
// with the common interface.
var plugin = scope.Resolve<IPlugin>();
plugin.DoSomething();
}
loadContext.Unload();
Breaking Changes
The introduction of these new features produced a couple of relatively minor breaking changes; you can find the full list of these in the documentation on upgrading from 6.x to 7.x.
We also dropped direct support for .NET 5 (it is now generally out of support by the dotnet team).
Wrapping Up
As always, if you find a problem with this latest release, let us know by raising an issue; we’ll gradually update our various integrations to use the new version of Autofac, but we’d greatly welcome contributions from anyone willing to help us update the ones they’re familiar with.
As with a lot of OSS projects, it’s just a couple of us working on Autofac in our spare time, and we’re always very keen to encourage contribution!