From a427ba99818aedc16ae8111f41906adb92ae67e1 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 6 Mar 2023 12:43:22 -0800 Subject: [PATCH] Revert "Release 7.0" --- .github/workflows/update-version-tag.yaml | 2 +- docs/advanced/delegate-factories.rst | 27 ---- docs/advanced/index.rst | 1 - docs/advanced/scopes-loadcontexts.rst | 74 ----------- docs/register/prop-method-injection.rst | 144 +--------------------- docs/register/registration.rst | 38 ------ docs/resolve/relationships.rst | 93 ++++++++------ docs/whats-new/index.rst | 1 - docs/whats-new/upgradingfrom6to7.rst | 16 --- 9 files changed, 59 insertions(+), 337 deletions(-) delete mode 100644 docs/advanced/scopes-loadcontexts.rst delete mode 100644 docs/whats-new/upgradingfrom6to7.rst diff --git a/.github/workflows/update-version-tag.yaml b/.github/workflows/update-version-tag.yaml index 1791b73..21a8099 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: v7.0.0 + DOC_VERSION: v6.0.0 # Don't update the tag at the same time another update is running. concurrency: update-version-tag steps: diff --git a/docs/advanced/delegate-factories.rst b/docs/advanced/delegate-factories.rst index 0078870..f1ed5ff 100644 --- a/docs/advanced/delegate-factories.rst +++ b/docs/advanced/delegate-factories.rst @@ -179,30 +179,3 @@ 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/advanced/index.rst b/docs/advanced/index.rst index a44f558..f662464 100644 --- a/docs/advanced/index.rst +++ b/docs/advanced/index.rst @@ -16,7 +16,6 @@ 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 deleted file mode 100644 index 330e815..0000000 --- a/docs/advanced/scopes-loadcontexts.rst +++ /dev/null @@ -1,74 +0,0 @@ -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 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(); - - 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(); - - 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. - -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 diff --git a/docs/register/prop-method-injection.rst b/docs/register/prop-method-injection.rst index 4e65124..25d747d 100644 --- a/docs/register/prop-method-injection.rst +++ b/docs/register/prop-method-injection.rst @@ -21,120 +21,7 @@ To support :doc:`circular dependencies <../advanced/circular-dependencies>`, use builder.Register(c => new A()).OnActivated(e => e.Instance.B = e.Context.Resolve()); -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; } - - 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 `_ 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: +If the component is a :ref:`reflection component `, use the ``PropertiesAutowired()`` modifier to inject properties: .. sourcecode:: csharp @@ -156,41 +43,12 @@ You can use the ``PropertiesAutowired()`` modifier at registration time to injec // is important! builder.RegisterType().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().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 87f284e..455e3da 100644 --- a/docs/register/registration.rst +++ b/docs/register/registration.rst @@ -96,44 +96,6 @@ 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 =================== diff --git a/docs/resolve/relationships.rst b/docs/resolve/relationships.rst index 3654947..d780823 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 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 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>`: .. sourcecode:: csharp @@ -294,40 +294,7 @@ Example: } } -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>`. +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. **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: @@ -385,7 +352,61 @@ This shows how lifetime scopes are respected regardless of parameters: Assert.Same(b1, b2); } -**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>`. + +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"); + +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: + +.. 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"); + +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 + + var func = container.Resolve>(); + + // This works and is the same as calling + // new DuplicateTypes(1, 1, "three") + var obj = func(1, "three"); + +You can read more about delegate factories and the ``RegisterGeneratedFactory()`` method :doc:`in the advanced topics section <../advanced/delegate-factories>`. Enumeration (IEnumerable, IList, ICollection) ------------------------------------------------------ diff --git a/docs/whats-new/index.rst b/docs/whats-new/index.rst index fb999f4..bd8b328 100644 --- a/docs/whats-new/index.rst +++ b/docs/whats-new/index.rst @@ -5,6 +5,5 @@ 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 deleted file mode 100644 index 7e5b384..0000000 --- a/docs/whats-new/upgradingfrom6to7.rst +++ /dev/null @@ -1,16 +0,0 @@ -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 `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. - - 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 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>`_. - -- 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.