From 8c18c8fa95efa0563dd2818895439ac3a5f552b8 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Sun, 5 Mar 2023 10:51:19 +0000 Subject: [PATCH 01/18] Add docs for required properties --- docs/register/prop-method-injection.rst | 146 +++++++++++++++++++++++- docs/register/registration.rst | 38 ++++++ 2 files changed, 183 insertions(+), 1 deletion(-) diff --git a/docs/register/prop-method-injection.rst b/docs/register/prop-method-injection.rst index 25d747d..2136ea1 100644 --- a/docs/register/prop-method-injection.rst +++ b/docs/register/prop-method-injection.rst @@ -21,7 +21,118 @@ To support :doc:`circular dependencies <../advanced/circular-dependencies>`, use builder.Register(c => new A()).OnActivated(e => e.Instance.B = e.Context.Resolve()); -If the component is a :ref:`reflection component `, use the ``PropertiesAutowired()`` modifier to inject properties: +Required Properties +------------------- + +From Autofac 7.0 onwards, for :ref:`reflection components `, all `required properties `_ 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; } + } + +When the component is resolved, Autofac will populate the ``Logger`` and ``ConfigReader`` properties as if they were constructor parameters. + +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 `_ 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 ` 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 @@ -43,12 +154,45 @@ If the component is a :ref:`reflection component ().PropertiesAutowired(new MyCustomPropSelector()); +.. note:: + + Properties set on a component because they are ``required`` may lead to duplicate injection of a property if ``PropertiesAutowired`` is also used on that component. Consider using ``PropertiesAutowired(PropertyWiringOptions.PreserveSetValues)`` to avoid repeating property injection. + +Manually Specifying Properties +------------------------------ + If you have one specific property and value to wire up, you can use the ``WithProperty()`` modifier: .. sourcecode:: csharp builder.RegisterType().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().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(); + +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 diff --git a/docs/register/registration.rst b/docs/register/registration.rst index 455e3da..87f284e 100644 --- a/docs/register/registration.rst +++ b/docs/register/registration.rst @@ -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 `_ 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(); + builder.RegisterType().As(); + builder.RegisterType().As(); + var container = builder.Build(); + + using(var scope = container.BeginLifetimeScope()) + { + // Logger and ConfigReader will be populated. + var component = scope.Resolve(); + } + +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 `. + Instance Components =================== From a6ffd6d8e77b5bc0ccbc2e5ae15d198cfd810556 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Sun, 5 Mar 2023 11:04:40 +0000 Subject: [PATCH 02/18] Update relationships docs now that RegisterGeneratedFactory is obsolete. --- docs/resolve/relationships.rst | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/resolve/relationships.rst b/docs/resolve/relationships.rst index d780823..b71b184 100644 --- a/docs/resolve/relationships.rst +++ b/docs/resolve/relationships.rst @@ -374,7 +374,19 @@ You might want to register that type and have an auto-generated function factory // Throws a DependencyResolutionException: var obj = func(1, 2, "three"); -In a loosely coupled scenario where the parameters are matched on type, you shouldn't really know about the order of the parameters for a specific object's constructor. If you need to do something like this, you should use a custom delegate type instead: +Delegate Factories +^^^^^^^^^^^^^^^^^^ + +In a loosely coupled scenario where the parameters are matched on type, you shouldn't really know about the order of the parameters for a specific object's constructor. If you need to do something like this, you should use a :doc:`delegate factory, which you can read about in the advanced topics section <../advanced/delegate-factories>`. + +RegisterGeneratedFactory +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. important:: + + ``RegisterGeneratedFactory`` is now marked as obsolete as of Autofac 7.0, this section is included for posterity; if you cannot use the ``Func`` implicit relationship, use :doc:`delegate factories <../advanced/delegate-factories>`. + +The now-obsolete way to handle a loosely coupled scenario where the parameters are matched on type was through the use of ``RegisterGeneratedFactory()``. .. sourcecode:: csharp @@ -394,8 +406,6 @@ Now the function will work: var func = scope.Resolve(); var obj = func(1, 2, "three"); -Another option you have is to use a :doc:`delegate factory, which you can read about in the advanced topics section <../advanced/delegate-factories>`. - Should you decide to use the built-in auto-generated factory behavior (``Func``) and only resolve a factory with one of each type, it will work but you'll get the same input for all constructor parameters of the same type. .. sourcecode:: csharp @@ -406,8 +416,6 @@ Should you decide to use the built-in auto-generated factory behavior (``Func`. - Enumeration (IEnumerable, IList, ICollection) ------------------------------------------------------ Dependencies of an *enumerable type* provide multiple implementations of the same service (interface). This is helpful in cases like message handlers, where a message comes in and more than one handler is registered to process the message. From b8a4bce186f54ac576a8d0473c1da2b1f851bcbd Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Sun, 5 Mar 2023 11:39:01 +0000 Subject: [PATCH 03/18] Add docs for assemblyloadcontext support --- docs/advanced/index.rst | 1 + docs/advanced/scopes-loadcontexts.rst | 71 +++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 docs/advanced/scopes-loadcontexts.rst diff --git a/docs/advanced/index.rst b/docs/advanced/index.rst index f662464..a44f558 100644 --- a/docs/advanced/index.rst +++ b/docs/advanced/index.rst @@ -16,6 +16,7 @@ Advanced Topics constructor-selection.rst concurrency.rst multitenant.rst + scopes-loadcontexts.rst pipelines.rst aggregate-services.rst interceptors.rst diff --git a/docs/advanced/scopes-loadcontexts.rst b/docs/advanced/scopes-loadcontexts.rst new file mode 100644 index 0000000..4ae23b1 --- /dev/null +++ b/docs/advanced/scopes-loadcontexts.rst @@ -0,0 +1,71 @@ +AssemblyLoadContext and Lifetime Scopes +======================================= + +In .NET Core and .NET 5+, the `the AssemblyLoadContext was introduced `_. + +This allowed developers to dynamically load and unload assemblies from their application, and is very useful for developers writing applications with plugin-based architectures. + +In order to unload the assemblies in an ``AssemblyLoadContext`` when a plugin is no longer needed, and reclaim the memory required by that plugin, 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(); + + 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); + })) + { + // Because of default load context behaviour, because we have already loaded + // PluginDefinition into the application to use IPlugin, the loaded MyPlugin assembly + // will share the same type. + var plugin = scope.Resolve(); + + 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 `_ if you run into problems. From ff3b1278c143a697168b3985c54139478e7f2689 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Sun, 5 Mar 2023 12:04:00 +0000 Subject: [PATCH 04/18] Add instructions for upgrading from 6.x to 7.x --- docs/whats-new/index.rst | 1 + docs/whats-new/upgradingfrom6to7.rst | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 docs/whats-new/upgradingfrom6to7.rst diff --git a/docs/whats-new/index.rst b/docs/whats-new/index.rst index bd8b328..fb999f4 100644 --- a/docs/whats-new/index.rst +++ b/docs/whats-new/index.rst @@ -5,5 +5,6 @@ What's New .. toctree:: releasenotes.rst + upgradingfrom6to7.rst upgradingfrom5to6.rst upgradingfrom3to4.rst diff --git a/docs/whats-new/upgradingfrom6to7.rst b/docs/whats-new/upgradingfrom6to7.rst new file mode 100644 index 0000000..8050d32 --- /dev/null +++ b/docs/whats-new/upgradingfrom6to7.rst @@ -0,0 +1,14 @@ +Upgrading from Autofac 6.x to 7.x +================================= + +In the upgrade from Autofac 6.x to 7.x, the amount of breaking change was fairly minimal, and unlikely to impact most users, but there are a couple of distinct changes to make you aware of. + +- The introduction of :doc:`automatic injection for required properties <../register/prop-method-injection>` means that any existing required properties on components will now be automatically injected. + + If a required property *cannot* be resolved during component activation, an exception will now be thrown. + + Equally, if you previously used ``PropertiesAutowired()`` to populate required properties on components, those properties will now be doubly-resolved, so you may wish to remove ``PropertiesAutowired()`` in favour of the default functionality. + +- ``RegisterGeneratedFactory`` has been marked as obsolete. You should update your code to use the ``Func`` `implicit relationship <../resolve/relationships>`_ or `delegate factories <../advanced/delegate-factories>`_. + +- The definition of the ``ILifetimeScope`` interface has been changed to add the ``BeginLoadContextLifetimeScope`` method and its overloads, on .NET 5.0+. If you implement a "fake" lifetime scope for any tests, you will need to add an implementation of these methods. For most test use-cases, it's suitable to call the existing ``BeginLifetimeScope`` methods, ignoring the provided load context. From a3f0ae5f1eeeb5484c50a6fc3a6fdfa2ef57a293 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Sun, 5 Mar 2023 12:16:05 +0000 Subject: [PATCH 05/18] Update the "tag kept up to date with master" --- .github/workflows/update-version-tag.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-version-tag.yaml b/.github/workflows/update-version-tag.yaml index 21a8099..1791b73 100644 --- a/.github/workflows/update-version-tag.yaml +++ b/.github/workflows/update-version-tag.yaml @@ -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: From 9f8ba02a0b676d24fc626fff7d67bfdde5136bab Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Sun, 5 Mar 2023 15:09:11 +0000 Subject: [PATCH 06/18] Update load context docs --- docs/advanced/scopes-loadcontexts.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/advanced/scopes-loadcontexts.rst b/docs/advanced/scopes-loadcontexts.rst index 4ae23b1..d5a5f98 100644 --- a/docs/advanced/scopes-loadcontexts.rst +++ b/docs/advanced/scopes-loadcontexts.rst @@ -55,7 +55,7 @@ You indicate that a lifetime scope is for an ``AssemblyLoadContext`` with the ne })) { // Because of default load context behaviour, because we have already loaded - // PluginDefinition into the application to use IPlugin, the loaded MyPlugin assembly + // PluginDefinition into the *application* so we can reference IPlugin, the loaded MyPlugin assembly // will share the same type. var plugin = scope.Resolve(); @@ -69,3 +69,7 @@ You indicate that a lifetime scope is for an ``AssemblyLoadContext`` with the ne 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 `_ 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". \ No newline at end of file From e1047b61c3a4f9eb83baecfc891fed4266477396 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Sun, 5 Mar 2023 15:36:32 +0000 Subject: [PATCH 07/18] Add reference to dropping .NET5 support. --- docs/whats-new/upgradingfrom6to7.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/whats-new/upgradingfrom6to7.rst b/docs/whats-new/upgradingfrom6to7.rst index 8050d32..5b1dde6 100644 --- a/docs/whats-new/upgradingfrom6to7.rst +++ b/docs/whats-new/upgradingfrom6to7.rst @@ -3,6 +3,8 @@ Upgrading from Autofac 6.x to 7.x In the upgrade from Autofac 6.x to 7.x, the amount of breaking change was fairly minimal, and unlikely to impact most users, but there are a couple of distinct changes to make you aware of. +- We have dropped direct support for the `net5.0` TFM (and we recommend you upgrade to `net7.0`), but you can still use this release on .NET 5.0 via our `netstandard2.1` TFM support. + - The introduction of :doc:`automatic injection for required properties <../register/prop-method-injection>` means that any existing required properties on components will now be automatically injected. If a required property *cannot* be resolved during component activation, an exception will now be thrown. From 6a39e31e3611562750fc88833768ab7b5f099709 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Sun, 5 Mar 2023 21:07:46 +0000 Subject: [PATCH 08/18] Update docs/advanced/scopes-loadcontexts.rst Co-authored-by: Travis Illig --- docs/advanced/scopes-loadcontexts.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/advanced/scopes-loadcontexts.rst b/docs/advanced/scopes-loadcontexts.rst index d5a5f98..076c82b 100644 --- a/docs/advanced/scopes-loadcontexts.rst +++ b/docs/advanced/scopes-loadcontexts.rst @@ -1,9 +1,7 @@ AssemblyLoadContext and Lifetime Scopes ======================================= -In .NET Core and .NET 5+, the `the AssemblyLoadContext was introduced `_. - -This allowed developers to dynamically load and unload assemblies from their application, and is very useful for developers writing applications with plugin-based architectures. +In .NET Core `the AssemblyLoadContext was introduced `_, 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 a plugin is no longer needed, and reclaim the memory required by that plugin, 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. From 818618abc7dd9603faaac1806ccce3515cbcca0f Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Sun, 5 Mar 2023 21:07:54 +0000 Subject: [PATCH 09/18] Update docs/advanced/scopes-loadcontexts.rst Co-authored-by: Travis Illig --- docs/advanced/scopes-loadcontexts.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced/scopes-loadcontexts.rst b/docs/advanced/scopes-loadcontexts.rst index 076c82b..979cce5 100644 --- a/docs/advanced/scopes-loadcontexts.rst +++ b/docs/advanced/scopes-loadcontexts.rst @@ -3,7 +3,7 @@ AssemblyLoadContext and Lifetime Scopes In .NET Core `the AssemblyLoadContext was introduced `_, 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 a plugin is no longer needed, and reclaim the memory required by that plugin, 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. +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. From e51abb773766442b8d58bbccefedb5936c87c921 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Sun, 5 Mar 2023 21:08:19 +0000 Subject: [PATCH 10/18] Update docs/advanced/scopes-loadcontexts.rst Co-authored-by: Travis Illig --- docs/advanced/scopes-loadcontexts.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced/scopes-loadcontexts.rst b/docs/advanced/scopes-loadcontexts.rst index 979cce5..c749c71 100644 --- a/docs/advanced/scopes-loadcontexts.rst +++ b/docs/advanced/scopes-loadcontexts.rst @@ -49,7 +49,7 @@ You indicate that a lifetime scope is for an ``AssemblyLoadContext`` with the ne { var pluginAssembly = loadContext.LoadFromAssemblyPath("plugins/MyPlugin.dll"); - builder.RegisterAssemblyTypes(pluginAssembly); + builder.RegisterAssemblyTypes(pluginAssembly).AsImplementedInterfaces(); })) { // Because of default load context behaviour, because we have already loaded From 719a3ceabdd3fcfef582f3eb6779372837579949 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Sun, 5 Mar 2023 21:09:15 +0000 Subject: [PATCH 11/18] Update docs/advanced/scopes-loadcontexts.rst Co-authored-by: Travis Illig --- docs/advanced/scopes-loadcontexts.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/advanced/scopes-loadcontexts.rst b/docs/advanced/scopes-loadcontexts.rst index c749c71..330e815 100644 --- a/docs/advanced/scopes-loadcontexts.rst +++ b/docs/advanced/scopes-loadcontexts.rst @@ -52,9 +52,10 @@ You indicate that a lifetime scope is for an ``AssemblyLoadContext`` with the ne builder.RegisterAssemblyTypes(pluginAssembly).AsImplementedInterfaces(); })) { - // Because of default load context behaviour, because we have already loaded - // PluginDefinition into the *application* so we can reference IPlugin, the loaded MyPlugin assembly - // will share the same type. + // 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(); plugin.DoSomething(); From 16db286bfd5d291f1fc3f183b7fe5907f425b2cd Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Sun, 5 Mar 2023 21:09:25 +0000 Subject: [PATCH 12/18] Update docs/register/prop-method-injection.rst Co-authored-by: Travis Illig --- docs/register/prop-method-injection.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/register/prop-method-injection.rst b/docs/register/prop-method-injection.rst index 2136ea1..ecc5371 100644 --- a/docs/register/prop-method-injection.rst +++ b/docs/register/prop-method-injection.rst @@ -35,9 +35,11 @@ For example, given the following type: 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. +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: From c5618cde65ed5a33d5ed09d56cde382199c171d7 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Sun, 5 Mar 2023 21:11:09 +0000 Subject: [PATCH 13/18] Update docs/whats-new/upgradingfrom6to7.rst Co-authored-by: Travis Illig --- docs/whats-new/upgradingfrom6to7.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/whats-new/upgradingfrom6to7.rst b/docs/whats-new/upgradingfrom6to7.rst index 5b1dde6..1afe560 100644 --- a/docs/whats-new/upgradingfrom6to7.rst +++ b/docs/whats-new/upgradingfrom6to7.rst @@ -3,7 +3,7 @@ Upgrading from Autofac 6.x to 7.x In the upgrade from Autofac 6.x to 7.x, the amount of breaking change was fairly minimal, and unlikely to impact most users, but there are a couple of distinct changes to make you aware of. -- We have dropped direct support for the `net5.0` TFM (and we recommend you upgrade to `net7.0`), but you can still use this release on .NET 5.0 via our `netstandard2.1` TFM support. +- We have dropped direct support for the `net5.0` TFM (and we recommend you upgrade to `net6.0` or higher), but you can still use this release on .NET 5.0 via our `netstandard2.1` TFM support. - The introduction of :doc:`automatic injection for required properties <../register/prop-method-injection>` means that any existing required properties on components will now be automatically injected. From 8695f21f2e6b914a5f78a695840232ab01dd1add Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Mon, 6 Mar 2023 19:06:08 +0000 Subject: [PATCH 14/18] Update now PropertiesAutowired doesn't duplicate. --- docs/register/prop-method-injection.rst | 4 ---- docs/whats-new/upgradingfrom6to7.rst | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/register/prop-method-injection.rst b/docs/register/prop-method-injection.rst index ecc5371..169b632 100644 --- a/docs/register/prop-method-injection.rst +++ b/docs/register/prop-method-injection.rst @@ -156,10 +156,6 @@ You can use the ``PropertiesAutowired()`` modifier at registration time to injec // is important! builder.RegisterType().PropertiesAutowired(new MyCustomPropSelector()); -.. note:: - - Properties set on a component because they are ``required`` may lead to duplicate injection of a property if ``PropertiesAutowired`` is also used on that component. Consider using ``PropertiesAutowired(PropertyWiringOptions.PreserveSetValues)`` to avoid repeating property injection. - Manually Specifying Properties ------------------------------ diff --git a/docs/whats-new/upgradingfrom6to7.rst b/docs/whats-new/upgradingfrom6to7.rst index 1afe560..7e5b384 100644 --- a/docs/whats-new/upgradingfrom6to7.rst +++ b/docs/whats-new/upgradingfrom6to7.rst @@ -9,7 +9,7 @@ In the upgrade from Autofac 6.x to 7.x, the amount of breaking change was fairly If a required property *cannot* be resolved during component activation, an exception will now be thrown. - Equally, if you previously used ``PropertiesAutowired()`` to populate required properties on components, those properties will now be doubly-resolved, so you may wish to remove ``PropertiesAutowired()`` in favour of the default functionality. + Equally, if you previously used ``PropertiesAutowired()`` to populate required properties on components, those properties will now be automatically injected, so ``PropertiesAutowired()`` can be removed provided there are no "not required" properties on the component. - ``RegisterGeneratedFactory`` has been marked as obsolete. You should update your code to use the ``Func`` `implicit relationship <../resolve/relationships>`_ or `delegate factories <../advanced/delegate-factories>`_. From 8358b6fb7daf6f5452bd818e00b3fd6ff6b7e08a Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Mon, 6 Mar 2023 19:08:38 +0000 Subject: [PATCH 15/18] Move Func docs. --- docs/resolve/relationships.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/resolve/relationships.rst b/docs/resolve/relationships.rst index b71b184..77d221c 100644 --- a/docs/resolve/relationships.rst +++ b/docs/resolve/relationships.rst @@ -374,6 +374,16 @@ You might want to register that type and have an auto-generated function factory // Throws a DependencyResolutionException: var obj = func(1, 2, "three"); +Should you decide to use the built-in auto-generated factory behavior (``Func``) and only resolve a factory with one of each type, it will work but you'll get the same input for all constructor parameters of the same type. + +.. sourcecode:: csharp + + var func = container.Resolve>(); + + // This works and is the same as calling + // new DuplicateTypes(1, 1, "three") + var obj = func(1, "three"); + Delegate Factories ^^^^^^^^^^^^^^^^^^ @@ -406,16 +416,6 @@ Now the function will work: var func = scope.Resolve(); var obj = func(1, 2, "three"); -Should you decide to use the built-in auto-generated factory behavior (``Func``) and only resolve a factory with one of each type, it will work but you'll get the same input for all constructor parameters of the same type. - -.. sourcecode:: csharp - - var func = container.Resolve>(); - - // This works and is the same as calling - // new DuplicateTypes(1, 1, "three") - var obj = func(1, "three"); - Enumeration (IEnumerable, IList, ICollection) ------------------------------------------------------ Dependencies of an *enumerable type* provide multiple implementations of the same service (interface). This is helpful in cases like message handlers, where a message comes in and more than one handler is registered to process the message. From b3a996cf4e6a70cde8d4005c9f724a2268378746 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 6 Mar 2023 11:23:15 -0800 Subject: [PATCH 16/18] Apply suggestions from code review --- docs/register/prop-method-injection.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/register/prop-method-injection.rst b/docs/register/prop-method-injection.rst index 169b632..4e65124 100644 --- a/docs/register/prop-method-injection.rst +++ b/docs/register/prop-method-injection.rst @@ -166,7 +166,7 @@ If you have one specific property and value to wire up, you can use the ``WithPr builder.RegisterType().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: From e9d8b979f70aa5058d3f0cea511b966832b87105 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 6 Mar 2023 12:09:17 -0800 Subject: [PATCH 17/18] Minor cleanup on factory docs. --- docs/advanced/delegate-factories.rst | 28 ++++++++ docs/resolve/relationships.rst | 101 ++++++++++----------------- 2 files changed, 64 insertions(+), 65 deletions(-) diff --git a/docs/advanced/delegate-factories.rst b/docs/advanced/delegate-factories.rst index f1ed5ff..44e819d 100644 --- a/docs/advanced/delegate-factories.rst +++ b/docs/advanced/delegate-factories.rst @@ -179,3 +179,31 @@ Lifetime Scopes and Disposal ============================ Just as with the ``Func`` relationships or calling ``Resolve()`` 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(); + builder.RegisterGeneratedFactory(new TypedService(typeof(DuplicateTypes))); + +Now the function will work: + +.. sourcecode:: csharp + + var func = scope.Resolve(); + var obj = func(1, 2, "three"); diff --git a/docs/resolve/relationships.rst b/docs/resolve/relationships.rst index 77d221c..3654947 100644 --- a/docs/resolve/relationships.rst +++ b/docs/resolve/relationships.rst @@ -236,7 +236,7 @@ Lifetime scopes are respected, so you can use that to your advantage. Parameterized Instantiation (Func) ------------------------------------------- -You can also use an *auto-generated factory* to provide parameters when creating an new instance of the object, where the constructor of the object calls for some additional parameters. While the ``Func`` relationship is similar to ``Resolve()``, the ``Func`` relationship is like calling ``Resolve(TypedParameter.From(x), TypedParameter.From(y))`` - a resolve operation that has typed parameters. This is an alternative to :doc:`passing parameters during registration <../register/parameters>` or :doc:`passing during manual resolution <../resolve/parameters>`: +You can use an *auto-generated factory* to provide parameters when creating an new instance of the object, where the constructor of the object calls for some additional parameters. While the ``Func`` relationship is similar to ``Resolve()``, the ``Func`` relationship is like calling ``Resolve(TypedParameter.From(x), TypedParameter.From(y))`` - a resolve operation that has typed parameters. This is an alternative to :doc:`passing parameters during registration <../register/parameters>` or :doc:`passing during manual resolution <../resolve/parameters>`: .. sourcecode:: csharp @@ -294,7 +294,40 @@ Example: } } -Internally, Autofac determines what values to use for the constructor args solely based on the type and behaves as though we've temporarily defined the input values for resolution. A consequence of this is that **auto-generated function factories cannot have duplicate types in the input parameter list.** See below for further notes on this. +Autofac determines what values to use for the constructor args **solely based on the type** (like ``TypedParameter``). A consequence of this is that **auto-generated function factories cannot have duplicate types in the input parameter list.** For example, say you have a type like this: + +.. sourcecode:: csharp + + public class DuplicateTypes + { + public DuplicateTypes(int a, int b, string c) + { + // ... + } + } + +You might want to register that type and have an auto-generated function factory for it. *You will be able to resolve the function, but you won't be able to execute it.* You can try to resolve a factory with one of each type, and that will work but you'll get the same input for all constructor parameters of the same type. + +.. sourcecode:: csharp + + // This auto-generated factory has two parameters of the + // same type - problem! + var funcWithDuplicates = scope.Resolve>(); + + // Throws a DependencyResolutionException because of the + // two int types. + var obj1 = funcWithDuplicates(1, 2, "three"); + + // This auto-generated factory removes the duplicates, BUT... + var funcWithoutDuplicates = container.Resolve>(); + + // ...the int factory parameter will be the same value for + // BOTH constructor parameters. This factory call will work, + // but it's like saying + // var obj2 = new DuplicateTypes(1, 1, "three"); + var obj2 = funcWithoutDuplicates(1, "three"); + +If you really need multiple parameters of the same type, :doc:`check out delegate factories <../advanced/delegate-factories>`. **Lifetime scopes are respected** using this relationship type, just as they are when using ``Func`` or :doc:`delegate factories <../advanced/delegate-factories>`. If you register an object as ``InstancePerDependency()`` and call the ``Func`` multiple times, you'll get a new instance each time. However, if you register an object as ``SingleInstance()`` and call the ``Func`` to resolve the object more than once, you will get *the same object instance every time regardless of the different parameters you pass in.* Just passing different parameters will not break the respect for the lifetime scope: @@ -352,69 +385,7 @@ This shows how lifetime scopes are respected regardless of parameters: Assert.Same(b1, b2); } - -As noted above, ``Func`` treats arguments as ``TypedParameter`` so you can't have duplicate types in the parameter list. For example, say you have a type like this: - -.. sourcecode:: csharp - - public class DuplicateTypes - { - public DuplicateTypes(int a, int b, string c) - { - // ... - } - } - -You might want to register that type and have an auto-generated function factory for it. *You will be able to resolve the function, but you won't be able to execute it.* - -.. sourcecode:: csharp - - var func = scope.Resolve>(); - - // Throws a DependencyResolutionException: - var obj = func(1, 2, "three"); - -Should you decide to use the built-in auto-generated factory behavior (``Func``) and only resolve a factory with one of each type, it will work but you'll get the same input for all constructor parameters of the same type. - -.. sourcecode:: csharp - - var func = container.Resolve>(); - - // This works and is the same as calling - // new DuplicateTypes(1, 1, "three") - var obj = func(1, "three"); - -Delegate Factories -^^^^^^^^^^^^^^^^^^ - -In a loosely coupled scenario where the parameters are matched on type, you shouldn't really know about the order of the parameters for a specific object's constructor. If you need to do something like this, you should use a :doc:`delegate factory, which you can read about in the advanced topics section <../advanced/delegate-factories>`. - -RegisterGeneratedFactory -^^^^^^^^^^^^^^^^^^^^^^^^ - -.. important:: - - ``RegisterGeneratedFactory`` is now marked as obsolete as of Autofac 7.0, this section is included for posterity; if you cannot use the ``Func`` implicit relationship, use :doc:`delegate factories <../advanced/delegate-factories>`. - -The now-obsolete way to handle a loosely coupled scenario where the parameters are matched on type was through the use of ``RegisterGeneratedFactory()``. - -.. sourcecode:: csharp - - public delegate DuplicateTypes FactoryDelegate(int a, int b, string c); - -Then register that delegate using ``RegisterGeneratedFactory()``: - -.. sourcecode:: csharp - - builder.RegisterType(); - builder.RegisterGeneratedFactory(new TypedService(typeof(DuplicateTypes))); - -Now the function will work: - -.. sourcecode:: csharp - - var func = scope.Resolve(); - var obj = func(1, 2, "three"); +**Delegate factories allow you to provide a custom delegate as the signature for your factory** function, which can overcome challenges with relationships like ``Func`` like allowing for multiple parameters of the same time. Delegate factories can be a powerful alternative for factory generation - :doc:`check this feature out in the advanced topics section <../advanced/delegate-factories>`. Enumeration (IEnumerable, IList, ICollection) ------------------------------------------------------ From 83030da0a55c3b4d6bfc9a85c14544e416d9c313 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 6 Mar 2023 12:10:28 -0800 Subject: [PATCH 18/18] Remove blank line. --- docs/advanced/delegate-factories.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/advanced/delegate-factories.rst b/docs/advanced/delegate-factories.rst index 44e819d..0078870 100644 --- a/docs/advanced/delegate-factories.rst +++ b/docs/advanced/delegate-factories.rst @@ -180,7 +180,6 @@ Lifetime Scopes and Disposal Just as with the ``Func`` relationships or calling ``Resolve()`` 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) ===================================