Skip to content

Commit

Permalink
Merge pull request #164 from autofac/release-7.0
Browse files Browse the repository at this point in the history
Release 7.0
  • Loading branch information
tillig committed Mar 6, 2023
2 parents 52f1ff0 + 83030da commit dc3f00a
Show file tree
Hide file tree
Showing 9 changed files with 337 additions and 59 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/update-version-tag.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
env:
# This is the tag that will be kept up to date with master.
DOC_VERSION: v6.0.0
DOC_VERSION: v7.0.0
# Don't update the tag at the same time another update is running.
concurrency: update-version-tag
steps:
Expand Down
27 changes: 27 additions & 0 deletions docs/advanced/delegate-factories.rst
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,30 @@ Lifetime Scopes and Disposal
============================

Just as with the ``Func<T>`` relationships or calling ``Resolve<T>()`` directly, using delegate factories is resolving something from a lifetime scope. If the thing you're resolving is disposable, :doc:`the lifetime scope will track it and dispose of it when the scope is disposed <../lifetime/disposal>`. Resolving directly from the container or from a very long-lived lifetime scope when using disposable components may result in a memory leak as the scope holds references to all the disposable components resolved.

RegisterGeneratedFactory (Obsolete)
===================================

.. important::

``RegisterGeneratedFactory`` is now marked as obsolete as of Autofac 7.0. Delegate factories and the :doc:`function relationships <../resolve/relationships>` have superseded this feature.

The now-obsolete way to handle a loosely coupled scenario where the parameters are matched on type was through the use of ``RegisterGeneratedFactory()``. This worked very similar to delegate factories but required an explicit registration operation.

.. sourcecode:: csharp

public delegate DuplicateTypes FactoryDelegate(int a, int b, string c);

Then register that delegate using ``RegisterGeneratedFactory()``:

.. sourcecode:: csharp

builder.RegisterType<DuplicateTypes>();
builder.RegisterGeneratedFactory<FactoryDelegate>(new TypedService(typeof(DuplicateTypes)));

Now the function will work:

.. sourcecode:: csharp

var func = scope.Resolve<FactoryDelegate>();
var obj = func(1, 2, "three");
1 change: 1 addition & 0 deletions docs/advanced/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Advanced Topics
constructor-selection.rst
concurrency.rst
multitenant.rst
scopes-loadcontexts.rst
pipelines.rst
aggregate-services.rst
interceptors.rst
Expand Down
74 changes: 74 additions & 0 deletions docs/advanced/scopes-loadcontexts.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
AssemblyLoadContext and Lifetime Scopes
=======================================

In .NET Core `the AssemblyLoadContext was introduced <https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/understanding-assemblyloadcontext>`_, allowing developers to dynamically load and unload assemblies from their application. This is very useful for developers writing applications with plugin-based architectures.

In order to unload the assemblies in an ``AssemblyLoadContext`` when an assembly is no longer needed, no references to types in that assembly can be held by anything outside the loaded context. This includes Autofac, which by default holds a variety of internal references and caches for types that have been registered.

As of Autofac 7.0, we've added support for indicating to Autofac that a given lifetime scope represents types loaded for a given ``AssemblyLoadContext``; when a lifetime scope created for a specific ``AssemblyLoadContext`` is unloaded, Autofac will make a *best-effort* attempt to remove all references we hold for types from the loaded context, to allow the ``AssemblyLoadContext`` to be unloaded.

You indicate that a lifetime scope is for an ``AssemblyLoadContext`` with the new ``BeginLoadContextLifetimeScope`` method. Here's a full example:

.. sourcecode:: csharp

//
// 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();

.. note::

If you capture a reference to any resolved components, or any types in the loaded assembly, outside Autofac it's highly likely you won't be able to unload your load context.

AssemblyLoadContexts are tricky to use in such a way that unloading is guaranteed every time (whether using Autofac or not). See the dotnet documentation on `troubleshooting unloadability <https://learn.microsoft.com/en-us/dotnet/standard/assembly/unloadability#troubleshoot-unloadability-issues>`_ if you run into problems.

You can create additional lifetime scopes from your "load context scope" using the regular `BeginLifetimeScope` method, without needing to further track your load context.

That means you can load a plugin, then the plugin can resolve `ILifetimeScope` and create new scopes, with all the assembly metadata being isolated to that initial "load context scope".
144 changes: 143 additions & 1 deletion docs/register/prop-method-injection.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,120 @@ To support :doc:`circular dependencies <../advanced/circular-dependencies>`, use

builder.Register(c => new A()).OnActivated(e => e.Instance.B = e.Context.Resolve<B>());

If the component is a :ref:`reflection component <register-registration-reflection-components>`, use the ``PropertiesAutowired()`` modifier to inject properties:
Required Properties
-------------------

From Autofac 7.0 onwards, for :ref:`reflection components <register-registration-reflection-components>`, all `required properties <https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required>`_ are automatically resolved at the time of object construction, and are generally treated in much the same way as mandatory constructor arguments.

For example, given the following type:

.. sourcecode:: csharp

public class MyComponent
{
public required ILogger Logger { get; set; }

public required IConfigReader ConfigReader { get; set; }

public IDatabaseContext Context { get; set; }
}

When the component is resolved, Autofac will populate the ``Logger`` and ``ConfigReader`` properties as if they were constructor parameters. The ``Context`` property will be treated like a standard property and will not be populated by default.

Required property injection also works automatically in all base classes with required properties:

.. sourcecode:: csharp

public class ComponentBase
{
public required ILogger Logger { get; set; }
}

public class MyComponent : ComponentBase
{
public required IConfigReader ConfigReader { get; set; }
}

In the above example, resolving ``MyComponent`` would populate ``Logger`` in the base class, as well as ``ConfigReader`` in the component itself.

.. important::

Autofac does *not* consider the nullability of the type of a required property to indicate any sort of "optional" required property. If the property is marked as ``required``,
then it is required, and must be injected, or provided via a parameter, regardless of its nullability.

Required Properties and Constructors
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

You can mix-and-match constructors and required properties if you so wish:

.. sourcecode:: csharp

public class MyComponent
{
public MyComponent(ILogger logger)
{
Logger = logger;
}

private ILogger Logger { get; set; }

public required IConfigReader ConfigReader { get; set; }
}

When multiple constructors are available, by default Autofac selects the constructor with the most matching parameters (unless :doc:`custom constructor selection is used <../advanced/constructor-selection>`). This remains the case, and the set of required properties has no impact on the selected constructor.

Autofac has no idea whether or not you set a given required property inside a constructor. Take this example:

.. sourcecode:: csharp

public class MyComponent
{
public MyComponent()
{
}

public MyComponent(ILogger logger)
{
Logger = logger;
}

public required ILogger Logger { get; set; }
}

Here, the constructor that Autofac will pick is going to be the one that takes the ``ILogger`` parameter, which in turn sets the ``Logger`` property. However, since ``Logger`` is marked as a required property, Autofac will resolve ``ILogger`` a second time, and inject it into the required property.

To avoid this, mark constructors that set all your required properties with the `SetsRequiredMembers <https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.setsrequiredmembersattribute>`_ attribute:

.. sourcecode:: csharp

using System.Diagnostics.CodeAnalysis;

public class MyComponent
{
public MyComponent()
{
}

[SetsRequiredMembers]
public MyComponent(ILogger logger)
{
Logger = logger;
}

public required ILogger Logger { get; set; }
}

Since the constructor is marked as setting all required members, no required property injection will occur in Autofac, when *that constructor* is used to create an instance of the component.

Required Properties and Parameters
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Any ``TypedParameter`` provided at :doc:`registration <parameters>` or :doc:`resolve <../resolve/parameters>` will be considered when injecting required properties. However, ``NamedParameter`` and ``PositionalParameter`` are not considered valid parameters for property injection, since they are considered to only apply to constructor parameters.

PropertiesAutowired
-------------------

You can use the ``PropertiesAutowired()`` modifier at registration time to inject properties on any component:

.. sourcecode:: csharp

Expand All @@ -43,12 +156,41 @@ If the component is a :ref:`reflection component <register-registration-reflecti
// is important!
builder.RegisterType<C>().PropertiesAutowired(new MyCustomPropSelector());

Manually Specifying Properties
------------------------------

If you have one specific property and value to wire up, you can use the ``WithProperty()`` modifier:

.. sourcecode:: csharp

builder.RegisterType<A>().WithProperty("PropertyName", propertyValue);

Overriding Required Properties
------------------------------

Any property values provided for required properties using the ``WithProperty`` method when registering a type will override the requirement to inject that property, and Autofac will use the provided value instead:

.. sourcecode:: csharp

public class MyComponent
{
public required ILogger Logger { get; set; }

public required IConfigReader ConfigReader { get; set; }
}

var builder = new ContainerBuilder();
builder.RegisterType<MyComponent>().WithProperty("Logger", new ConsoleLogger());

var container = builder.Build();

// This will not throw, despite ILogger not being registered.
// The Logger property is provided by WithProperty.
container.Resolve<MyComponent>();

Injecting Properties on an Existing Object
------------------------------------------

You can also populate *just the properties* on an object. Do this using the ``InjectUnsetProperties`` extension on a lifetime scope, which will resolve and populate properties that are *public, writable, and not yet set (null)*:

.. sourcecode:: csharp
Expand Down
38 changes: 38 additions & 0 deletions docs/register/registration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,44 @@ Note that you will still need to have the requisite parameters available at reso

.. note:: You can find advanced methods of customising which constructor to use :doc:`here <../advanced/constructor-selection>`.

Required Properties
-------------------

Starting in Autofac 7.0, in a reflection-based component, all `required properties <https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required>`_ are automatically resolved, in the same manner as constructor parameters.

All required properties of the component *must* be resolvable services (or supplied as a :doc:`parameter <../resolve/parameters>`) otherwise an exception will be thrown when trying to resolve the component.

For example, consider a class with these properties:

.. sourcecode:: csharp

public class MyComponent
{
public required ILogger Logger { get; set; }

public required IConfigReader ConfigReader { get; set; }
}

You can register and use this class as you could if it had a constructor:

.. sourcecode:: csharp

var builder = new ContainerBuilder();
builder.RegisterType<MyComponent>();
builder.RegisterType<ConsoleLogger>().As<ILogger>();
builder.RegisterType<ConfigReader>().As<IConfigReader>();
var container = builder.Build();

using(var scope = container.BeginLifetimeScope())
{
// Logger and ConfigReader will be populated.
var component = scope.Resolve<MyComponent>();
}

Required properties are also set automatically on all base classes (if they are present); this makes required properties useful with deep object hierarchies, because it allows you to avoid having to invoke base constructors with the set of services; Autofac will set the base class properties for you.

.. note:: For more details on required property injection, see the dedicated section in the :doc:`property injection documentation <prop-method-injection>`.

Instance Components
===================

Expand Down

0 comments on commit dc3f00a

Please sign in to comment.